Skip to content

Commit

Permalink
Fixes to make simple game works again
Browse files Browse the repository at this point in the history
  • Loading branch information
DogLooksGood committed Feb 27, 2024
1 parent fd04f51 commit c3ca736
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 100 deletions.
2 changes: 0 additions & 2 deletions api/src/effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,6 @@ pub struct Effect {
pub init_random_states: Vec<RandomSpec>,
pub revealed: HashMap<RandomId, HashMap<usize, String>>,
pub answered: HashMap<DecisionId, String>,
pub is_checkpoint: bool,
pub checkpoint: Option<Vec<u8>>,
pub settles: Vec<Settle>,
pub handler_state: Option<Vec<u8>>,
Expand Down Expand Up @@ -463,7 +462,6 @@ mod tests {
error: Some(HandleError::NoEnoughPlayers),
allow_exit: true,
transfers: vec![],
is_checkpoint: false,
checkpoint: None,
launch_sub_games: vec![],
bridge_events: vec![],
Expand Down
17 changes: 8 additions & 9 deletions core/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ impl GameContext {
.as_ref()
.ok_or(Error::GameNotServed)?;

let nodes = game_account
let mut nodes: Vec<Node> = game_account
.servers
.iter()
.map(|s| {
Expand All @@ -203,13 +203,12 @@ impl GameContext {
)
})
.collect();

let players = game_account
.players
.iter()
.filter(|p| p.access_version <= game_account.access_version)
.map(|p| GamePlayer::new(p.access_version, p.balance, p.position))
.collect();
let mut players = vec![];
game_account.players.iter()
.for_each(|p| {
players.push(GamePlayer::new(p.access_version, p.balance, p.position));
nodes.push(Node::new_pending(p.addr.clone(), p.access_version, ClientMode::Player))
});

Ok(Self {
game_addr: game_account.addr.clone(),
Expand All @@ -234,6 +233,7 @@ impl GameContext {
}

pub fn id_to_addr(&self, id: u64) -> Result<String> {
println!("Nodes: {:?}", self.nodes);
self.nodes
.iter()
.find(|n| n.id == id)
Expand Down Expand Up @@ -716,7 +716,6 @@ impl GameContext {
init_random_states: Vec::new(),
revealed,
answered,
is_checkpoint: false,
checkpoint: None,
settles: Vec::new(),
handler_state: Some(self.handler_state.clone()),
Expand Down
140 changes: 77 additions & 63 deletions js/sdk-core/src/base-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { PlayerConfirming } from './tx-state';
import { Client } from './client';
import { Custom, GameEvent, ICustomEvent } from './events';
import { DecryptionCache } from './decryption-cache';
import { ConnectionStateCallbackFunction, ErrorCallbackFunction, ErrorKind, EventCallbackFunction, GameInfo, LoadProfileCallbackFunction, MessageCallbackFunction, TxStateCallbackFunction } from './types';
import { ConnectionStateCallbackFunction, ErrorCallbackFunction, ErrorKind, EventCallbackFlags, EventCallbackFunction, GameInfo, LoadProfileCallbackFunction, MessageCallbackFunction, TxStateCallbackFunction } from './types';

const MAX_RETRIES = 3;

Expand Down Expand Up @@ -194,7 +194,7 @@ export class BaseClient {
console.log('Game context created,', this.__gameContext);
for (const p of gameAccount.players) this.__onLoadProfile(p.accessVersion, p.addr);
await this.__connection.connect(new SubscribeEventParams({ settleVersion: this.__gameContext.settleVersion }));
await this.__initializeState(initAccount);
await this.__initializeState(initAccount, true);
} catch (e) {
console.error('Attaching game failed', e);
this.__invokeErrorCallback('attach-failed')
Expand All @@ -213,17 +213,20 @@ export class BaseClient {
}
}

async __invokeEventCallback(event: GameEvent | undefined, isHistory: boolean) {
async __invokeEventCallback(event: GameEvent | undefined, flags: EventCallbackFlags) {
const snapshot = new GameContextSnapshot(this.__gameContext);
const state = this.__gameContext.handlerState;
this.__onEvent(snapshot, state, event, isHistory);
this.__onEvent(snapshot, state, event, flags);
}

async __initializeState(initAccount: InitAccount): Promise<void> {
async __initializeState(initAccount: InitAccount, isHistory: boolean): Promise<void> {
console.log('Initialize state with', initAccount);
await this.__handler.initState(this.__gameContext, initAccount);
const effects = await this.__handler.initState(this.__gameContext, initAccount);
console.log('State initialized');
await this.__invokeEventCallback(undefined, true);
await this.__invokeEventCallback(undefined, {
isCheckpoint: effects.checkpoint !== undefined,
isHistory,
});
}

async __getGameAccount(): Promise<GameAccount> {
Expand Down Expand Up @@ -262,6 +265,71 @@ export class BaseClient {
}
}

async __handleBroadcastFrameEvent(frame: BroadcastFrameEvent) {

const { event, timestamp } = frame;
console.groupCollapsed('Handle event: ' + event.kind() + ' at timestamp: ' + new Date(Number(timestamp)).toLocaleString());
console.log('Event: ', event);
let state: Uint8Array | undefined;
let err: ErrorKind | undefined;
let effects: EventEffects | undefined;
const isHistory = frame.remain !== 0;

try { // For log group
try {
this.__gameContext.prepareForNextEvent(timestamp);
console.log('Game context before:', this.__gameContext);
effects = await this.__handler.handleEvent(this.__gameContext, event);
console.log('Game context after:', this.__gameContext);
console.log('Output:', effects);
state = this.__gameContext.handlerState;
const sha = await sha256(this.__gameContext.handlerState)
console.log('SHA:', sha);
if (sha !== frame.stateSha) {
const remoteState = await this.__connection.getState();
console.log('Remote state:', remoteState);
console.log('Local state:', state);
err = 'state-sha-mismatch'
}
} catch (e: any) {
console.error(e);
err = 'handle-event-error';
}

if (!err) {
await this.__invokeEventCallback(event, {
isCheckpoint: effects?.checkpoint !== undefined,
isHistory,
});
}

if ((!err) && effects?.checkpoint) {
console.log('Rebuild state for checkpoint:', effects.checkpoint);
const initData = this.__gameContext.initData;
if (initData === undefined) {
err = 'state-sha-mismatch'
} else {
const initAccount = new InitAccount({
entryType: this.__gameContext.entryType,
data: initData,
players: this.__gameContext.players,
checkpoint: effects.checkpoint,
maxPlayers: this.__gameContext.maxPlayers,
});
await this.__initializeState(initAccount, isHistory);
}
}

if (err) {
this.__invokeErrorCallback(err, state);
throw new Error(`An error occurred in event loop: ${err}`);
}

} finally {
console.groupEnd()
}
}

async __handleBroadcastFrame(frame: BroadcastFrame) {
if (frame instanceof BroadcastFrameMessage) {
console.groupCollapsed('Receive message broadcast');
Expand Down Expand Up @@ -304,61 +372,7 @@ export class BaseClient {
console.groupEnd();
}
} else if (frame instanceof BroadcastFrameEvent) {
const { event, timestamp } = frame;
console.groupCollapsed('Handle event: ' + event.kind() + ' at timestamp: ' + new Date(Number(timestamp)).toLocaleString());
console.log('Event: ', event);
let state: Uint8Array | undefined;
let err: ErrorKind | undefined;
let effects: EventEffects | undefined;

try { // For log group

try {
this.__gameContext.prepareForNextEvent(timestamp);
effects = await this.__handler.handleEvent(this.__gameContext, event);
state = this.__gameContext.handlerState;
const sha = await sha256(this.__gameContext.handlerState)
if (sha !== frame.stateSha) {
const remoteState = await this.__connection.getState();
console.log('Remote state:', remoteState);
console.log('Local state:', state);
err = 'state-sha-mismatch'
}
} catch (e: any) {
console.error(e);
err = 'handle-event-error';
}

if (!err) {
await this.__invokeEventCallback(event, frame.remain !== 0);
}

if ((!err) && effects?.checkpoint) {
console.log('Rebuild state for checkpoint');
const initData = this.__gameContext.initData;
if (initData === undefined) {
err = 'state-sha-mismatch'
} else {
const initAccount = new InitAccount({
entryType: this.__gameContext.entryType,
data: initData,
players: this.__gameContext.players,
checkpoint: effects.checkpoint,
maxPlayers: this.__gameContext.maxPlayers,
});
await this.__initializeState(initAccount);
await this.__invokeEventCallback(event, frame.remain !== 0);
}
}

if (err) {
this.__invokeErrorCallback(err, state);
throw new Error(`An error occurred in event loop: ${err}`);
}

} finally {
console.groupEnd()
}
await this.__handleBroadcastFrameEvent(frame);
}
}

Expand All @@ -373,7 +387,7 @@ export class BaseClient {
const initAccount = InitAccount.createFromGameAccount(gameAccount);
this.__gameContext.applyCheckpoint(gameAccount.checkpointAccessVersion, this.__gameContext.settleVersion);
await this.__connection.connect(new SubscribeEventParams({ settleVersion: this.__gameContext.settleVersion }));
await this.__initializeState(initAccount);
await this.__initializeState(initAccount, true);
} else if (state === 'connected') {
if (this.__onConnectionState !== undefined) {
this.__onConnectionState('connected')
Expand Down
13 changes: 10 additions & 3 deletions js/sdk-core/src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export class BroadcastFrameEvent extends BroadcastFrame {
constructor(fields: any) {
super();
Object.assign(this, fields);
Object.setPrototypeOf(this, BroadcastFrameEvent);
}
}

Expand All @@ -109,6 +110,7 @@ export class BroadcastFrameMessage extends BroadcastFrame {
constructor(fields: any) {
super();
Object.assign(this, fields);
Object.setPrototypeOf(this, BroadcastFrameMessage);
}
}

Expand All @@ -119,6 +121,7 @@ export class BroadcastFrameTxState extends BroadcastFrame {
constructor(fields: any) {
super();
Object.assign(this, fields);
Object.setPrototypeOf(this, BroadcastFrameTxState);
}
}

Expand All @@ -135,6 +138,7 @@ export class BroadcastFrameSync extends BroadcastFrame {
constructor(fields: any) {
super();
Object.assign(this, fields)
Object.setPrototypeOf(this, BroadcastFrameSync);
}
}

Expand Down Expand Up @@ -339,11 +343,14 @@ export class Connection implements IConnection {
await this.waitSocketReady();
this.streamMessagePromise = new Promise(r => (this.streamResolve = r));
while (true) {
if (this.streamMessageQueue.length > 0) {
yield this.streamMessageQueue.shift()!;
while (this.streamMessageQueue.length > 0) {
yield this.streamMessageQueue.shift();
}
if (this.streamResolve === undefined) {
this.streamMessagePromise = new Promise(r => (this.streamResolve = r));
yield this.streamMessagePromise;
} else {
yield this.streamMessagePromise;
this.streamMessagePromise = new Promise(r => (this.streamResolve = r));
}
}
}
Expand Down
5 changes: 0 additions & 5 deletions js/sdk-core/src/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,6 @@ export class Effect {
@field(map('usize', 'string'))
answered!: Map<number, string>;

@field('bool')
isCheckpoint!: boolean;

@field(option('u8-array'))
checkpoint!: Uint8Array | undefined;

Expand Down Expand Up @@ -255,7 +252,6 @@ export class Effect {
const releases: Release[] = [];
const reveals: Reveal[] = [];
const initRandomStates: RandomSpec[] = [];
const isCheckpoint = false;
const checkpoint = undefined;
const settles: Settle[] = [];
const handlerState = context.handlerState;
Expand All @@ -281,7 +277,6 @@ export class Effect {
initRandomStates,
revealed,
answered,
isCheckpoint,
checkpoint,
settles,
handlerState,
Expand Down
15 changes: 12 additions & 3 deletions js/sdk-core/src/game-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,8 @@ export class GameContext {
}

applyEffect(effect: Effect): EventEffects {
console.log('Apply effect:', effect);

if (effect.startGame) {
this.startGame();
} else if (effect.stopGame) {
Expand Down Expand Up @@ -435,26 +437,33 @@ export class GameContext {

let settles: Settle[] = [];

if (effect.isCheckpoint) {
if (effect.checkpoint !== undefined) {
// Reset random states
this.randomStates = [];
this.decisionStates = [];

// Sort settles and track player states
settles.push(...effect.settles);
settles = effect.settles;
settles = settles.sort((s1, s2) => s1.compare(s2));
for (let s of settles) {
if (s.op instanceof SettleAdd) {
this.playerSubBalance(s.id, s.op.amount);
} else if (s.op instanceof SettleSub) {
this.playerAddBalance(s.id, s.op.amount);
} else if (s.op instanceof SettleSub) {
this.playerSubBalance(s.id, s.op.amount);
} else if (s.op instanceof SettleEject) {
this.removePlayer(s.id);
}
}

this.checkpoint = effect.checkpoint;
this.status = 'idle';
}

if (effect.handlerState !== undefined) {
this.handlerState = effect.handlerState;
} else {
console.warn('Effect has no handler state');
}

this.subGames.push(...effect.launchSubGames);
Expand Down
2 changes: 1 addition & 1 deletion js/sdk-core/src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class Handler implements IHandler {
const addr = context.idToAddr(sender);
context.lock(addr, randomId, ciphertextsAndDigests);
} else if (event instanceof Join) {
// No op here
event.players.forEach(p => context.addPlayer(p));
} else if (event instanceof Leave) {
if (!context.allowExit) {
throw new Error('Leave is not allowed')
Expand Down
2 changes: 1 addition & 1 deletion js/sdk-core/src/sub-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class SubClient extends BaseClient {
await this.__client.attachGame();
sub = this.__connection.subscribeEvents();
await this.__connection.connect(new SubscribeEventParams({ settleVersion: this.__gameContext.settleVersion }));
await this.__initializeState(this.__initAccount);
await this.__initializeState(this.__initAccount, true);
} catch (e) {
console.error('Attaching game failed', e);
throw e;
Expand Down
Loading

0 comments on commit c3ca736

Please sign in to comment.