diff --git a/api/src/effect.rs b/api/src/effect.rs index 0ed0b2e5..8d589b38 100644 --- a/api/src/effect.rs +++ b/api/src/effect.rs @@ -88,13 +88,19 @@ impl SubGame { pub struct EmitBridgeEvent { pub dest: usize, pub raw: Vec, + pub join_players: Vec, } impl EmitBridgeEvent { - pub fn try_new(dest: usize, bridge_event: E) -> Result { + pub fn try_new( + dest: usize, + bridge_event: E, + join_players: Vec, + ) -> Result { Ok(Self { dest, raw: bridge_event.try_to_vec()?, + join_players, }) } } @@ -216,9 +222,19 @@ pub struct Effect { pub transfers: Vec, pub launch_sub_games: Vec, pub bridge_events: Vec, + pub valid_players: Vec, } impl Effect { + + fn assert_player_id(&self, id: u64) -> Result<()> { + if self.valid_players.iter().find(|p| p.id == id).is_some() { + Ok(()) + } else { + Err(Error::InvalidPlayerId(id, self.valid_players.iter().map(|p| p.id).collect())) + } + } + /// Return the number of nodes, including both the pending and joined. pub fn count_nodes(&self) -> usize { self.nodes_count as usize @@ -233,12 +249,14 @@ impl Effect { } /// Assign some random items to a specific player. - pub fn assign(&mut self, random_id: RandomId, player_id: u64, indexes: Vec) { + pub fn assign(&mut self, random_id: RandomId, player_id: u64, indexes: Vec) -> Result<()> { + self.assert_player_id(player_id)?; self.assigns.push(Assign { random_id, player_id, indexes, - }) + }); + Ok(()) } /// Reveal some random items to the public. @@ -268,11 +286,12 @@ impl Effect { } /// Ask a player for a decision, return the new decision id. - pub fn ask(&mut self, player_id: u64) -> DecisionId { + pub fn ask(&mut self, player_id: u64) -> Result { + self.assert_player_id(player_id)?; self.asks.push(Ask { player_id }); let decision_id = self.curr_decision_id; self.curr_decision_id += 1; - decision_id + Ok(decision_id) } pub fn release(&mut self, decision_id: DecisionId) { @@ -280,8 +299,10 @@ impl Effect { } /// Dispatch action timeout event for a player after certain milliseconds. - pub fn action_timeout(&mut self, player_id: u64, timeout: u64) { + pub fn action_timeout(&mut self, player_id: u64, timeout: u64) -> Result<()> { + self.assert_player_id(player_id)?; self.action_timeout = Some(ActionTimeout { player_id, timeout }); + Ok(()) } /// Return current timestamp. @@ -331,8 +352,10 @@ impl Effect { } /// Submit settlements. - pub fn settle(&mut self, settle: Settle) { + pub fn settle(&mut self, settle: Settle) -> Result<()> { + self.assert_player_id(settle.id)?; self.settles.push(settle); + Ok(()) } /// Transfer the assets to a recipient slot @@ -350,6 +373,10 @@ impl Effect { init_data: D, checkpoint: C, ) -> Result<()> { + for p in players.iter() { + self.assert_player_id(p.id)?; + } + self.launch_sub_games.push(SubGame { id, bundle_addr, @@ -408,11 +435,18 @@ impl Effect { } /// Emit a bridge event. - pub fn bridge_event(&mut self, dest: usize, evt: E) -> Result<()> { - self.bridge_events.push(EmitBridgeEvent { - dest, - raw: evt.try_to_vec()?, - }); + pub fn bridge_event( + &mut self, + dest: usize, + evt: E, + join_players: Vec, + ) -> Result<()> { + for p in join_players.iter() { + self.assert_player_id(p.id)?; + } + + self.bridge_events + .push(EmitBridgeEvent::try_new(dest, evt, join_players)?); Ok(()) } } @@ -469,6 +503,7 @@ mod tests { checkpoint: None, launch_sub_games: vec![], bridge_events: vec![], + valid_players: vec![], }; let bs = effect.try_to_vec()?; diff --git a/api/src/error.rs b/api/src/error.rs index 18033e8a..f28a5712 100644 --- a/api/src/error.rs +++ b/api/src/error.rs @@ -278,6 +278,12 @@ pub enum Error { #[error("Game uninitialized")] GameUninitialized, + + #[error("Invalid bridge event")] + InvalidBridgeEvent, + + #[error("Invalid player id: {0}, availables: {1:?}")] + InvalidPlayerId(u64, Vec), } #[cfg(feature = "serde")] diff --git a/api/src/event.rs b/api/src/event.rs index dc88bbe2..d0d3ce4c 100644 --- a/api/src/event.rs +++ b/api/src/event.rs @@ -129,6 +129,7 @@ pub enum Event { Bridge { dest: usize, raw: Vec, + join_players: Vec, }, } @@ -136,7 +137,23 @@ impl std::fmt::Display for Event { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Event::Custom { sender, raw } => write!(f, "Custom from {}, inner: {:?}", sender, raw), - Event::Bridge { dest, raw } => write!(f, "Bridge to {}, inner: [{}...]", dest, raw[0]), + Event::Bridge { + dest, + raw, + join_players, + } => { + let players = join_players + .iter() + .map(|p| p.id.to_string()) + .collect::>() + .join(","); + + write!( + f, + "Bridge to {}, inner: [{}...], join_players: [{}]", + dest, raw[0], players + ) + } Event::Ready => write!(f, "Ready"), Event::ShareSecrets { sender, shares } => { let repr = shares @@ -165,7 +182,7 @@ impl std::fmt::Display for Event { write!(f, "Join, players: [{}]", players) } Event::Leave { player_id } => write!(f, "Leave from {}", player_id), - Event::GameStart { } => { + Event::GameStart {} => { write!(f, "GameStart") } Event::WaitingTimeout => write!(f, "WaitTimeout"), @@ -183,13 +200,7 @@ impl std::fmt::Display for Event { Event::SecretsReady { random_ids } => { write!(f, "SecretsReady for {:?}", random_ids) } - Event::ServerLeave { - server_id, - } => write!( - f, - "ServerLeave {}", - server_id - ), + Event::ServerLeave { server_id } => write!(f, "ServerLeave {}", server_id), Event::AnswerDecision { decision_id, .. } => { write!(f, "AnswerDecision for {}", decision_id) } @@ -211,10 +222,11 @@ impl Event { } } - pub fn bridge(dest: usize, e: &E) -> Self { + pub fn bridge(dest: usize, e: &E, join_players: Vec) -> Self { Self::Bridge { dest, raw: e.try_to_vec().unwrap(), + join_players, } } } diff --git a/api/src/prelude.rs b/api/src/prelude.rs index 7bacaf87..9d28bbe4 100644 --- a/api/src/prelude.rs +++ b/api/src/prelude.rs @@ -1,4 +1,4 @@ -pub use crate::effect::Effect; +pub use crate::effect::{Effect, SubGame}; pub use crate::engine::{GameHandler, InitAccount}; pub use crate::error::{HandleError, HandleResult}; pub use crate::event::{CustomEvent, Event, BridgeEvent}; diff --git a/core/src/context.rs b/core/src/context.rs index 5566b684..94e83b0b 100644 --- a/core/src/context.rs +++ b/core/src/context.rs @@ -724,6 +724,7 @@ impl GameContext { transfers: Vec::new(), launch_sub_games: Vec::new(), bridge_events: Vec::new(), + valid_players: self.players.clone(), } } diff --git a/core/src/engine.rs b/core/src/engine.rs index ce4d0bc8..559d0398 100644 --- a/core/src/engine.rs +++ b/core/src/engine.rs @@ -19,9 +19,7 @@ pub fn general_handle_event( ) -> Result<(), Error> { // General event handling match event { - Event::Ready => { - Ok(()) - } + Event::Ready => Ok(()), Event::ShareSecrets { sender, shares } => { let addr = context.id_to_addr(*sender)?; @@ -144,6 +142,16 @@ pub fn general_handle_event( Ok(()) } + Event::Bridge { + join_players, + .. + } => { + for p in join_players { + context.add_player(p.to_owned()); + } + Ok(()) + } + _ => Ok(()), } } diff --git a/core/src/types/common.rs b/core/src/types/common.rs index 5fe193c2..31507c57 100644 --- a/core/src/types/common.rs +++ b/core/src/types/common.rs @@ -17,6 +17,14 @@ pub enum ClientMode { Validator, } +#[derive(Debug, PartialEq, Eq, Clone, Copy, BorshSerialize, BorshDeserialize)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub enum GameMode { + Main, + Sub, +} + #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Signature { diff --git a/js/borsh/src/errors.ts b/js/borsh/src/errors.ts index 07a8790e..fb86ca71 100644 --- a/js/borsh/src/errors.ts +++ b/js/borsh/src/errors.ts @@ -1,3 +1,7 @@ +export function invalidCtor(path: string[]) { + throw new Error(`Borsh: Cannot deserialize, missing type annotation at: ${path.join(',')}`); +} + export function invalidByteArrayLength(path: string[], expected: number, actual: number) { throw new Error(`Borsh: Invalid byte array length at: ${path.join(',')}, expected: ${expected}, actual: ${actual}`); } diff --git a/js/borsh/src/index.ts b/js/borsh/src/index.ts index 648bdfc6..c07058e3 100644 --- a/js/borsh/src/index.ts +++ b/js/borsh/src/index.ts @@ -17,7 +17,7 @@ import { } from './types'; import { BinaryWriter } from './writer'; import { BinaryReader } from './reader'; -import { invalidByteArrayLength, extendedWriterNotFound, extendedReaderNotFound, invalidEnumField } from './errors'; +import { invalidByteArrayLength, extendedWriterNotFound, extendedReaderNotFound, invalidEnumField, invalidCtor } from './errors'; class DeserializeError extends Error { cause: Error; @@ -123,7 +123,7 @@ function serializeValue(path: string[], value: any, fieldType: FieldType, writer } else if (kind === 'array') { writer.writeU32(value.length); for (let i = 0; i < value.length; i++) { - serializeValue([...path, ``], value[i], v, writer); + serializeValue([...path, ``], value[i], v, writer); } } else if (kind === 'struct') { serializeStruct(path, value, writer); @@ -187,7 +187,7 @@ function deserializeValue(path: string[], fieldType: FieldType, reader: BinaryRe let arr = []; const length = reader.readU32(); for (let i = 0; i < length; i++) { - let v = deserializeValue([...path, ``], value, reader); + let v = deserializeValue([...path, ``], value, reader); arr.push(v); } return arr; @@ -279,6 +279,7 @@ function deserializeEnum(path: string[], enumClass: Function, reader: BinaryRead } function deserializeStruct(path: string[], ctor: Ctor, reader: BinaryReader): T { + if (ctor === undefined) invalidCtor(path); const prototype = ctor.prototype; const fields = getSchemaFields(prototype); let obj = {}; @@ -288,7 +289,9 @@ function deserializeStruct(path: string[], ctor: Ctor, reader: BinaryReade } } catch (e) { if (e instanceof DeserializeError) { - e.obj = obj; + if (e.obj === undefined) { + e.obj = obj; + } } throw e; } @@ -297,6 +300,7 @@ function deserializeStruct(path: string[], ctor: Ctor, reader: BinaryReade export function field(fieldType: FieldType) { return function (target: any, key: PropertyKey) { + if (target?.constructor?.prototype === undefined) throw new Error(`Invalid field argument for key: ${key.toString()}`) addSchemaField(target.constructor.prototype, key, fieldType); }; } @@ -360,7 +364,7 @@ export function deserialize(classType: Ctor | EnumClass, data: Uint8Arr } } catch (e) { if (e instanceof DeserializeError) { - console.error('Deserialize failed, path:', e.path, ', current object:', e.obj, ', cause:', e.cause); + console.error('Deserialize failed, path:', e.path, ', current object:', e.obj, ', cause:', e.cause, ', data:', data); } throw e; } diff --git a/js/sdk-core/src/accounts.ts b/js/sdk-core/src/accounts.ts index d8c0d29f..6a142b61 100644 --- a/js/sdk-core/src/accounts.ts +++ b/js/sdk-core/src/accounts.ts @@ -205,6 +205,7 @@ export class EntryTypeCash extends EntryType implements IEntryTypeKind { constructor(fields: any) { super(); Object.assign(this, fields); + Object.setPrototypeOf(this, EntryTypeCash.prototype); } kind(): EntryTypeKind { return 'Cash'; @@ -220,6 +221,7 @@ export class EntryTypeTicket extends EntryType implements IEntryTypeKind { constructor(fields: any) { super(); Object.assign(this, fields); + Object.setPrototypeOf(this, EntryTypeTicket.prototype); } kind(): EntryTypeKind { return 'Ticket'; @@ -233,6 +235,7 @@ export class EntryTypeGating extends EntryType implements IEntryTypeKind { constructor(fields: any) { super(); Object.assign(this, fields); + Object.setPrototypeOf(this, EntryTypeGating.prototype); } kind(): EntryTypeKind { return 'Gating'; @@ -243,9 +246,10 @@ export class EntryTypeGating extends EntryType implements IEntryTypeKind { export class EntryTypeDisabled extends EntryType implements IEntryTypeKind { constructor(_: any) { super(); + Object.setPrototypeOf(this, EntryTypeDisabled.prototype); } kind(): EntryTypeKind { - return 'Disabled' + return 'Disabled'; } } diff --git a/js/sdk-core/src/connection.ts b/js/sdk-core/src/connection.ts index e7990d3f..a7cab095 100644 --- a/js/sdk-core/src/connection.ts +++ b/js/sdk-core/src/connection.ts @@ -95,7 +95,7 @@ export class BroadcastFrameEvent extends BroadcastFrame { constructor(fields: any) { super(); Object.assign(this, fields); - Object.setPrototypeOf(this, BroadcastFrameEvent); + Object.setPrototypeOf(this, BroadcastFrameEvent.prototype); } } @@ -108,7 +108,7 @@ export class BroadcastFrameMessage extends BroadcastFrame { constructor(fields: any) { super(); Object.assign(this, fields); - Object.setPrototypeOf(this, BroadcastFrameMessage); + Object.setPrototypeOf(this, BroadcastFrameMessage.prototype); } } @@ -119,7 +119,7 @@ export class BroadcastFrameTxState extends BroadcastFrame { constructor(fields: any) { super(); Object.assign(this, fields); - Object.setPrototypeOf(this, BroadcastFrameTxState); + Object.setPrototypeOf(this, BroadcastFrameTxState.prototype); } } @@ -136,7 +136,7 @@ export class BroadcastFrameSync extends BroadcastFrame { constructor(fields: any) { super(); Object.assign(this, fields) - Object.setPrototypeOf(this, BroadcastFrameSync); + Object.setPrototypeOf(this, BroadcastFrameSync.prototype); } } @@ -144,7 +144,7 @@ export class BroadcastFrameSync extends BroadcastFrame { export class BroadcastFrameEndOfHistory extends BroadcastFrame { constructor(_: any) { super(); - Object.setPrototypeOf(this, BroadcastFrameEndOfHistory); + Object.setPrototypeOf(this, BroadcastFrameEndOfHistory.prototype); } } diff --git a/js/sdk-core/src/effect.ts b/js/sdk-core/src/effect.ts index 91248e5a..ac45c9ac 100644 --- a/js/sdk-core/src/effect.ts +++ b/js/sdk-core/src/effect.ts @@ -3,7 +3,7 @@ import { HandleError } from './error'; import { GameContext } from './game-context'; import { enums, field, map, option, struct, variant, array } from '@race-foundation/borsh'; import { Fields, Id } from './types'; -import { InitAccount } from './init-account'; +import { GamePlayer, InitAccount } from './init-account'; export abstract class SettleOp {} @@ -115,18 +115,6 @@ export class ActionTimeout { } } -export class GamePlayer { - @field('u64') - id!: bigint; - @field('u16') - position!: number; - @field('u64') - balance!: bigint; - constructor(fields: Fields) { - Object.assign(this, fields) - } -} - export class SubGame { @field('usize') subId!: number; @@ -144,6 +132,9 @@ export class EmitBridgeEvent { dest!: number; @field('u8-array') raw!: Uint8Array; + @field(array(struct(GamePlayer))) + joinPlayers!: GamePlayer[]; + constructor(fields: Fields) { Object.assign(this, fields) } @@ -222,6 +213,9 @@ export class Effect { @field(array(struct(EmitBridgeEvent))) bridgeEvents!: EmitBridgeEvent[]; + @field(array(struct(GamePlayer))) + validPlayers!: GamePlayer[]; + constructor(fields: Fields) { Object.assign(this, fields); } @@ -257,6 +251,7 @@ export class Effect { const transfers: Transfer[] = []; const launchSubGames: SubGame[] = []; const bridgeEvents: EmitBridgeEvent[] = []; + const validPlayers = context.players; return new Effect({ actionTimeout, waitTimeout, @@ -282,6 +277,7 @@ export class Effect { transfers, launchSubGames, bridgeEvents, + validPlayers }); } } diff --git a/js/sdk-core/src/events.ts b/js/sdk-core/src/events.ts index 7be3ed09..7d56e5a9 100644 --- a/js/sdk-core/src/events.ts +++ b/js/sdk-core/src/events.ts @@ -1,6 +1,6 @@ import { field, array, enums, option, variant, struct } from '@race-foundation/borsh'; import { Fields, Id } from './types'; -import { GamePlayer } from './effect'; +import { GamePlayer } from './init-account'; type EventFields = Omit, 'kind'>; diff --git a/js/sdk-core/src/game-context.ts b/js/sdk-core/src/game-context.ts index d60cbf88..fec0e2a2 100644 --- a/js/sdk-core/src/game-context.ts +++ b/js/sdk-core/src/game-context.ts @@ -13,7 +13,8 @@ import { Shutdown, WaitingTimeout, } from './events'; -import { Effect, EmitBridgeEvent, SubGame, Settle, Transfer, GamePlayer, SettleOp, SettleAdd, SettleSub, SettleEject } from './effect'; +import { GamePlayer } from './init-account'; +import { Effect, EmitBridgeEvent, SubGame, Settle, Transfer, SettleAdd, SettleSub, SettleEject } from './effect'; import { EntryType, GameAccount } from './accounts'; import { Ciphertext, Digest, Id } from './types'; diff --git a/js/sdk-core/src/init-account.ts b/js/sdk-core/src/init-account.ts index 399a929d..922b27ad 100644 --- a/js/sdk-core/src/init-account.ts +++ b/js/sdk-core/src/init-account.ts @@ -1,6 +1,18 @@ import { array, deserialize, enums, field, serialize, struct } from "@race-foundation/borsh"; -import { GamePlayer } from "./effect"; import { EntryType, GameAccount } from "./accounts"; +import { Fields } from "./types"; + +export class GamePlayer { + @field('u64') + id!: bigint; + @field('u16') + position!: number; + @field('u64') + balance!: bigint; + constructor(fields: Fields) { + Object.assign(this, fields) + } +} /** * A subset of GameAccount, used in handler initialization. @@ -26,11 +38,11 @@ export class InitAccount { readonly checkpoint: Uint8Array; constructor(fields: IInitAccount) { - this.data = fields.data; - this.players = fields.players; this.maxPlayers = fields.maxPlayers; - this.checkpoint = fields.checkpoint; this.entryType = fields.entryType; + this.players = fields.players; + this.data = fields.data; + this.checkpoint = fields.checkpoint; } static createFromGameAccount( diff --git a/js/sdk-core/tests/effect.spec.ts b/js/sdk-core/tests/effect.spec.ts new file mode 100644 index 00000000..b870a576 --- /dev/null +++ b/js/sdk-core/tests/effect.spec.ts @@ -0,0 +1,10 @@ +const data = [0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,92,1,0,0,3,182,122,9,142,1,0,0,2,0,0,0,0,0,0,0,1,4,0,0,0,2,0,0,0,0,0,0,0,1,3,0,0,0,0,0,0,0,2,4,0,0,0,0,0,0,0,1,5,0,0,0,0,0,0,0,2,4,0,0,0,2,0,0,0,0,0,0,0,0,194,235,11,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,194,235,11,0,0,0,0,0,1,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,2,0,0,0,1,1,0,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,32,130,253,5,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,224,63,238,5,0,0,0,0,1,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,0,2,0,0,0,3,0,0,0,0,0,0,0,32,130,253,5,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,224,63,238,5,0,0,0,0,1,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,161,7,0,0,0,0,0,96,234,0,0,0,0,0,0,6,0,0,0,1,0,2,0,2,0,4,0,4,0,8,0,8,0,16,0,16,0,32,0,32,0,64,0,3,0,0,0,50,30,20,0,132,215,23,0,0,0,0,0,225,245,5,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,38,0,0,0,114,97,99,101,104,111,108,100,101,109,116,97,114,103,101,116,114,97,99,101,104,111,108,100,101,109,109,116,116,116,97,98,108,101,119,97,115,109,2,0,3,2,0,0,0,2,0,0,0,0,0,0,0,0,0,32,130,253,5,0,0,0,0,4,0,0,0,0,0,0,0,1,0,224,63,238,5,0,0,0,0,98,0,0,0,1,0,0,0,0,0,0,0,1,32,161,7,0,0,0,0,0,64,66,15,0,0,0,0,0,2,2,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,32,130,253,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,224,63,238,5,0,0,0,0,1,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,38,0,0,0,114,97,99,101,104,111,108,100,101,109,116,97,114,103,101,116,114,97,99,101,104,111,108,100,101,109,109,116,116,116,97,98,108,101,119,97,115,109,2,0,3,2,0,0,0,3,0,0,0,0,0,0,0,0,0,32,130,253,5,0,0,0,0,5,0,0,0,0,0,0,0,1,0,224,63,238,5,0,0,0,0,98,0,0,0,1,0,0,0,0,0,0,0,2,32,161,7,0,0,0,0,0,64,66,15,0,0,0,0,0,2,2,0,0,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,32,130,253,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,224,63,238,5,0,0,0,0,1,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,2,0,0,0,0,0,0,0,0,0,0,225,245,5,0,0,0,0,3,0,0,0,0,0,0,0,1,0,0,225,245,5,0,0,0,0,4,0,0,0,0,0,0,0,2,0,0,225,245,5,0,0,0,0,5,0,0,0,0,0,0,0,3,0,0,225,245,5,0,0,0,0]; + +import { deserialize } from '@race-foundation/borsh'; +import { Effect } from '../src/effect'; + +describe('Deserialize Effect', () => { + it('With launch sub games', () => { + const e = deserialize(Effect, Uint8Array.from(data)); + }); +}) diff --git a/js/sdk-core/tests/events.spec.ts b/js/sdk-core/tests/events.spec.ts index a2bb4e5d..902550f8 100644 --- a/js/sdk-core/tests/events.spec.ts +++ b/js/sdk-core/tests/events.spec.ts @@ -23,8 +23,7 @@ import { } from '../src/events'; import { assert } from 'chai'; import { deserialize, serialize, field } from '@race-foundation/borsh'; -import { ServerJoin, PlayerJoin } from '../src/accounts'; -import { GamePlayer } from '../src/effect'; +import { GamePlayer } from '../src/init-account'; class TestCustom implements ICustomEvent { @field('u32') diff --git a/transactor/src/component/event_loop.rs b/transactor/src/component/event_loop.rs index 06c85c86..5c95abf8 100644 --- a/transactor/src/component/event_loop.rs +++ b/transactor/src/component/event_loop.rs @@ -13,7 +13,7 @@ use crate::component::common::{Component, PipelinePorts}; use crate::component::event_bus::CloseReason; use crate::component::wrapped_handler::WrappedHandler; use crate::frame::EventFrame; -use race_core::types::{ClientMode, GameAccount, GamePlayer, SubGameSpec}; +use race_core::types::{ClientMode, GameAccount, GameMode, GamePlayer, SubGameSpec}; use super::ComponentEnv; @@ -29,7 +29,8 @@ fn log_execution_context(ctx: &GameContext, evt: &Event) { pub struct EventLoopContext { handler: WrappedHandler, game_context: GameContext, - mode: ClientMode, + client_mode: ClientMode, + game_mode: GameMode, } pub trait WrappedGameHandler: Send { @@ -45,7 +46,8 @@ async fn handle_event( game_context: &mut GameContext, event: Event, ports: &PipelinePorts, - mode: ClientMode, + client_mode: ClientMode, + game_mode: GameMode, env: &ComponentEnv, ) -> Option { info!("{} Handle event: {}", env.log_prefix, event); @@ -59,16 +61,18 @@ async fn handle_event( let state_sha = digest(state); // Broacast the event to clients - ports - .send(EventFrame::Broadcast { - event, - access_version, - settle_version, - timestamp: game_context.get_timestamp(), - state: state.to_owned(), - state_sha, - }) - .await; + if client_mode == ClientMode::Transactor { + ports + .send(EventFrame::Broadcast { + event, + access_version, + settle_version, + timestamp: game_context.get_timestamp(), + state: state.to_owned(), + state_sha, + }) + .await; + } // Update the local client ports @@ -78,12 +82,14 @@ async fn handle_event( .await; // Start game - if effects.start_game { - ports - .send(EventFrame::GameStart { - access_version: game_context.access_version(), - }) - .await; + if client_mode == ClientMode::Transactor { + if effects.start_game { + ports + .send(EventFrame::GameStart { + access_version: game_context.access_version(), + }) + .await; + } } // Send the settlement when there's one @@ -106,23 +112,25 @@ async fn handle_event( } // Launch sub games - for launch_sub_game in effects.launch_sub_games { - let ef = EventFrame::LaunchSubGame { - spec: Box::new(SubGameSpec { - game_addr: game_context.get_game_addr().to_owned(), - sub_id: launch_sub_game.id, - bundle_addr: launch_sub_game.bundle_addr, - nodes: game_context.get_nodes().into(), - access_version: game_context.access_version(), - settle_version: game_context.settle_version(), - init_account: launch_sub_game.init_account, - }), - }; - ports.send(ef).await; + if game_mode == GameMode::Main { + for launch_sub_game in effects.launch_sub_games { + let ef = EventFrame::LaunchSubGame { + spec: Box::new(SubGameSpec { + game_addr: game_context.get_game_addr().to_owned(), + sub_id: launch_sub_game.id, + bundle_addr: launch_sub_game.bundle_addr, + nodes: game_context.get_nodes().into(), + access_version: game_context.access_version(), + settle_version: game_context.settle_version(), + init_account: launch_sub_game.init_account, + }), + }; + ports.send(ef).await; + } } // Emit bridge events - if mode == ClientMode::Transactor { + if client_mode == ClientMode::Transactor { for be in effects.bridge_events { info!("Emit bridge event: {:?}", be); let ef = EventFrame::SendBridgeEvent { @@ -130,6 +138,7 @@ async fn handle_event( event: Event::Bridge { dest: be.dest, raw: be.raw, + join_players: be.join_players, }, access_version: game_context.access_version(), settle_version: game_context.settle_version(), @@ -201,7 +210,9 @@ impl Component for EventLoop { let mut game_context = ctx.game_context; // Read games from event bus - while let Some(event_frame) = read_event(&mut ports, &mut game_context, ctx.mode).await { + while let Some(event_frame) = + read_event(&mut ports, &mut game_context, ctx.client_mode).await + { // Set timestamp to current time // Reset some disposable states. game_context.prepare_for_next_event(current_timestamp()); @@ -242,7 +253,7 @@ impl Component for EventLoop { } => { let init_data = match game_context.init_data() { Ok(init_data) => init_data, - Err(e) => return CloseReason::Fault(e) + Err(e) => return CloseReason::Fault(e), }; info!( "{} Rebuild game state from checkpoint, access_version = {}, settle_version = {}, checkpoint: {:?}", @@ -264,14 +275,15 @@ impl Component for EventLoop { EventFrame::GameStart { access_version } => { game_context.set_node_ready(access_version); - if ctx.mode == ClientMode::Transactor { + if ctx.client_mode == ClientMode::Transactor { let event = Event::GameStart; if let Some(close_reason) = handle_event( &mut handler, &mut game_context, event, &ports, - ctx.mode, + ctx.client_mode, + ctx.game_mode, &env, ) .await @@ -310,8 +322,11 @@ impl Component for EventLoop { game_context.add_node(p.addr.clone(), p.access_version, ClientMode::Player); } - // Generate - if ctx.mode == ClientMode::Transactor && !new_players.is_empty() { + // We only generate join event in Transactor & Main mode. + if ctx.client_mode == ClientMode::Transactor + && ctx.game_mode == GameMode::Main + && !new_players.is_empty() + { let event = Event::Join { players: new_players_1, }; @@ -320,7 +335,8 @@ impl Component for EventLoop { &mut game_context, event, &ports, - ctx.mode, + ctx.client_mode, + ctx.game_mode, &env, ) .await @@ -338,7 +354,8 @@ impl Component for EventLoop { &mut game_context, event, &ports, - ctx.mode, + ctx.client_mode, + ctx.game_mode, &env, ) .await @@ -372,7 +389,8 @@ impl Component for EventLoop { &mut game_context, event, &ports, - ctx.mode, + ctx.client_mode, + ctx.game_mode, &env, ) .await @@ -387,7 +405,8 @@ impl Component for EventLoop { &mut game_context, event, &ports, - ctx.mode, + ctx.client_mode, + ctx.game_mode, &env, ) .await @@ -406,7 +425,8 @@ impl Component for EventLoop { &mut game_context, event, &ports, - ctx.mode, + ctx.client_mode, + ctx.game_mode, &env, ) .await @@ -431,14 +451,16 @@ impl EventLoop { pub fn init( handler: WrappedHandler, game_context: GameContext, - mode: ClientMode, + client_mode: ClientMode, + game_mode: GameMode, ) -> (Self, EventLoopContext) { ( Self {}, EventLoopContext { handler, game_context, - mode, + client_mode, + game_mode, }, ) } diff --git a/transactor/src/handle/subgame.rs b/transactor/src/handle/subgame.rs index e0ef420f..ca7e7e7b 100644 --- a/transactor/src/handle/subgame.rs +++ b/transactor/src/handle/subgame.rs @@ -8,7 +8,7 @@ use crate::frame::EventFrame; use race_api::error::{Error, Result}; use race_core::context::GameContext; use race_core::transport::TransportT; -use race_core::types::{ClientMode, ServerAccount, SubGameSpec}; +use race_core::types::{ClientMode, GameMode, ServerAccount, SubGameSpec}; use race_encryptor::Encryptor; #[allow(dead_code)] @@ -29,6 +29,7 @@ impl SubGameHandle { transport: Arc, ) -> Result { println!("Launch sub game, nodes: {:?}", spec.nodes); + println!("Sub game players: {:?}", spec.init_account.players); let game_addr = spec.game_addr.clone(); let sub_id = spec.sub_id.clone(); @@ -42,6 +43,7 @@ impl SubGameHandle { // Build an InitAccount let game_context = GameContext::try_new_with_sub_game_spec(&spec)?; + let access_version = spec.access_version; let settle_version = spec.settle_version; @@ -54,7 +56,7 @@ impl SubGameHandle { let mut bridge_handle = bridge.start(&addr, bridge_ctx); let (event_loop, event_loop_ctx) = - EventLoop::init(handler, game_context, ClientMode::Transactor); + EventLoop::init(handler, game_context, ClientMode::Transactor, GameMode::Sub); let mut event_loop_handle = event_loop.start(&addr, event_loop_ctx); let mut connection = LocalConnection::new(encryptor.clone()); diff --git a/transactor/src/handle/transactor.rs b/transactor/src/handle/transactor.rs index 9462fed3..13c5aa2f 100644 --- a/transactor/src/handle/transactor.rs +++ b/transactor/src/handle/transactor.rs @@ -9,7 +9,7 @@ use race_api::error::{Error, Result}; use race_api::types::{PlayerJoin, ServerJoin}; use race_core::context::GameContext; use race_core::transport::TransportT; -use race_core::types::{ClientMode, GameAccount, GameBundle, ServerAccount}; +use race_core::types::{ClientMode, GameAccount, GameBundle, ServerAccount, GameMode}; use race_encryptor::Encryptor; use tokio::sync::mpsc; use tracing::info; @@ -79,7 +79,7 @@ impl TransactorHandle { let mut bridge_handle = bridge.start(&game_account.addr, bridge_ctx); let (event_loop, event_loop_ctx) = - EventLoop::init(handler, game_context, ClientMode::Transactor); + EventLoop::init(handler, game_context, ClientMode::Transactor, GameMode::Main); let mut event_loop_handle = event_loop.start(&game_account.addr, event_loop_ctx); let (submitter, submitter_ctx) = Submitter::init(game_account, transport.clone()); diff --git a/transactor/src/handle/validator.rs b/transactor/src/handle/validator.rs index 30466f69..7bc8c356 100644 --- a/transactor/src/handle/validator.rs +++ b/transactor/src/handle/validator.rs @@ -8,7 +8,7 @@ use crate::frame::{EventFrame, SignalFrame}; use race_api::error::{Error, Result}; use race_core::context::GameContext; use race_core::transport::TransportT; -use race_core::types::{ClientMode, GameAccount, GameBundle, ServerAccount}; +use race_core::types::{ClientMode, GameAccount, GameBundle, ServerAccount, GameMode}; use race_encryptor::Encryptor; use tokio::sync::mpsc; use tracing::info; @@ -53,7 +53,7 @@ impl ValidatorHandle { let mut bridge_handle = bridge.start(&game_account.addr, bridge_ctx); let (event_loop, event_loop_ctx) = - EventLoop::init(handler, game_context, ClientMode::Validator); + EventLoop::init(handler, game_context, ClientMode::Validator, GameMode::Main); let mut event_loop_handle = event_loop.start(&game_account.addr, event_loop_ctx); let connection = Arc::new(