diff --git a/jong/src/riichi.rs b/jong/src/riichi.rs index c5dc457..6c9cbf0 100644 --- a/jong/src/riichi.rs +++ b/jong/src/riichi.rs @@ -40,8 +40,8 @@ impl Plugin for Riichi { .init_state::() .add_sub_state::() .add_systems(Startup, subscriptions) - .add_systems(Update, on_connect) - .add_systems(Update, on_disconnect) + .add_observer(on_subscribed) + .add_systems(Update, (on_connect, on_disconnect)) .add_systems(Update, on_lobby_insert_update) .add_systems(Update, on_player_insert_update) .add_systems( @@ -51,6 +51,7 @@ impl Plugin for Riichi { ); } } + fn on_connect(stdb: SpacetimeDB, mut messages: ReadStdbConnectedMessage, _commands: Commands) { for msg in messages.read() { info!("you're now jongline"); @@ -72,9 +73,15 @@ fn on_disconnect(_stdb: SpacetimeDB, mut messages: ReadStdbDisconnectedMessage) } } -fn subscriptions(stdb: SpacetimeDB) { +// TODO we can make this hold more info in the future +#[derive(Event)] +struct Subscribed; + +fn subscriptions(stdb: SpacetimeDB, mut commands: Commands) { + // commands.queue(command); + let (send, recv) = std::sync::mpsc::channel::(); stdb.subscription_builder() - .on_applied(|_| trace!("made all subs!")) + .on_applied(move |_| send.send(Subscribed).unwrap()) .on_error(|_, err| error!("sub failed: {err}")) .subscribe([ // TODO change these to sub/unsub based on being in lobby and some such @@ -87,7 +94,51 @@ fn subscriptions(stdb: SpacetimeDB) { "SELECT * FROM view_hand".to_string(), "SELECT b.* FROM bot b JOIN lobby l ON l.id = b.lobby_id".to_string(), ]); - // .subscribe_to_all_tables(); + + while let Ok(event) = recv.recv() { + commands.trigger(event); + } +} + +/// restores (spawns) entities to be consistent with server state +fn on_subscribed( + _event: On, + + stdb: SpacetimeDB, + + mut commands: Commands, + mut next_gamestate: ResMut>, + mut next_turnstate: ResMut>, +) { + if let Some(player) = stdb.db().player().iter().next() {} + + if let Some(lobby) = stdb.db().lobby().iter().next() { + next_gamestate.set(lobby.game_state.into()); + } + + let hand_ent = commands.spawn(Hand).id(); + let pond_ent = commands.spawn(Pond).id(); + if let Some(player_hand) = stdb.db().view_hand().iter().next() { + let hand_tiles: Vec<_> = player_hand + .hand + .iter() + .map(|dbt| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id()) + .collect(); + let pond_tiles: Vec<_> = player_hand + .pond + .iter() + .map(|dbt| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id()) + .collect(); + commands.entity(pond_ent).add_children(&pond_tiles); + commands.entity(hand_ent).add_children(&hand_tiles); + + if player_hand.turn_state == jong_db::TurnState::Tsumo + && let Some(drawn_dbt) = player_hand.working_tile + { + commands.spawn((Drawn, Tile::from(&drawn_dbt.tile), TileId(drawn_dbt.id))); + } + next_turnstate.set(player_hand.turn_state.into()); + } } fn on_view_hand_update( @@ -95,34 +146,51 @@ fn on_view_hand_update( mut messages: ReadUpdateMessage, mut commands: Commands, - mut hand: Option>>, - drawn: Option>>, - tiles: Query<&Tile>, + hand: Single>, + pond: Single>, + // drawn: Option>>, + tiles: Query<(Entity, &TileId)>, mut next_turnstate: ResMut>, ) { - // let mut hand = hand.and_then(|h| Some(*h)); + // trace!("on_view_hand_update"); - if hand.is_none() - && let Some(player_hand) = stdb.db().view_hand().iter().next() - { - let hand_tiles: Vec<_> = player_hand + // TODO can this and similar run at startup or on play/reconnect? + for msg in messages.read() { + trace!("new hand: {:?}", msg.new); + + let hand_tiles: Vec<_> = msg + .new .hand .iter() - .map(|dbt| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id()) + .map(|dbt| { + tiles + .iter() + .find_map(|(e, t)| if *t == TileId(dbt.id) { Some(e) } else { None }) + .unwrap_or_else(|| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id()) + }) .collect(); - let hand_ent = commands.spawn(Hand).add_children(&hand_tiles).id(); - debug!("hand_tiles: {hand_tiles:?}"); - // hand = Some(hand_ent); - } + commands.entity(*hand).replace_children(&hand_tiles); - for msg in messages.read() { - // trace!("new hand: {:?}", msg.new); + let pond_tiles: Vec<_> = msg + .new + .pond + .iter() + .map(|dbt| { + tiles + .iter() + .find_map(|(e, t)| if *t == TileId(dbt.id) { Some(e) } else { None }) + .unwrap_or_else(|| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id()) + }) + .collect(); + commands.entity(*pond).replace_children(&pond_tiles); match msg.new.turn_state { jong_db::TurnState::None => { + trace!("turnstate none"); // TODO do we reconcile hand state here or in ::End? } jong_db::TurnState::Tsumo => { + trace!("turnstate tsumo"); let dbt = msg .new .working_tile @@ -145,7 +213,18 @@ fn on_player_insert_update( mut messages: ReadInsertUpdateMessage, mut commands: Commands, + + hand: Option>>, + pond: Option>>, ) { + // TODO this should be startup + if hand.is_none() { + commands.spawn(Hand); + } + if pond.is_none() { + commands.spawn(Pond); + } + for msg in messages.read() {} } @@ -153,11 +232,11 @@ fn on_lobby_insert_update( stdb: SpacetimeDB, mut messages: ReadInsertUpdateMessage, - commands: Commands, + mut commands: Commands, mut next_gamestate: ResMut>, ) { for msg in messages.read() { - trace!("on_lobby_insert_update msg:\n{:#?}", msg.new); + // trace!("on_lobby_insert_update msg:\n{:#?}", msg.new); let player = stdb .db() diff --git a/jong/src/tui.rs b/jong/src/tui.rs index 3638bb2..5418c21 100644 --- a/jong/src/tui.rs +++ b/jong/src/tui.rs @@ -107,7 +107,7 @@ fn discard_tile( tiles: Query<&TileId>, ) { // FIXME why is this not consuming the messages? - while let Some(message) = selected.read().last() { + while let Some(message) = selected.read().next() { if let Ok(tile_id) = tiles.get(message.0) { stdb.reducers().discard_tile(tile_id.0).unwrap(); commands.entity(drawn.0).remove::();