use crate::big_fish::tables::{ BigFishCreationSession, BigFishRuntimeRun, big_fish_creation_session, big_fish_runtime_run, }; use crate::*; use module_big_fish::{ StartBigFishRunCommand, SubmitBigFishInputCommand, deserialize_runtime_snapshot, serialize_runtime_snapshot, start_big_fish_run as start_big_fish_run_domain, submit_big_fish_input as submit_big_fish_input_domain, }; #[spacetimedb::procedure] pub fn start_big_fish_run( ctx: &mut ProcedureContext, input: BigFishRunStartInput, ) -> BigFishRunProcedureResult { match ctx.try_with_tx(|tx| start_big_fish_run_tx(tx, input.clone())) { Ok(run) => BigFishRunProcedureResult { ok: true, run: Some(run), error_message: None, }, Err(message) => BigFishRunProcedureResult { ok: false, run: None, error_message: Some(message), }, } } #[spacetimedb::procedure] pub fn get_big_fish_run( ctx: &mut ProcedureContext, input: BigFishRunGetInput, ) -> BigFishRunProcedureResult { match ctx.try_with_tx(|tx| get_big_fish_run_tx(tx, input.clone())) { Ok(run) => BigFishRunProcedureResult { ok: true, run: Some(run), error_message: None, }, Err(message) => BigFishRunProcedureResult { ok: false, run: None, error_message: Some(message), }, } } #[spacetimedb::procedure] pub fn submit_big_fish_input( ctx: &mut ProcedureContext, input: BigFishInputSubmitInput, ) -> BigFishRunProcedureResult { match ctx.try_with_tx(|tx| submit_big_fish_input_tx(tx, input.clone())) { Ok(run) => BigFishRunProcedureResult { ok: true, run: Some(run), error_message: None, }, Err(message) => BigFishRunProcedureResult { ok: false, run: None, error_message: Some(message), }, } } pub(crate) fn start_big_fish_run_tx( ctx: &ReducerContext, input: BigFishRunStartInput, ) -> Result { validate_run_start_input(&input).map_err(|error| error.to_string())?; if ctx .db .big_fish_runtime_run() .run_id() .find(&input.run_id) .is_some() { return Err("big_fish_runtime_run.run_id 已存在".to_string()); } let session = ctx .db .big_fish_creation_session() .session_id() .find(&input.session_id) .ok_or_else(|| "big_fish_creation_session 不存在".to_string())?; ensure_big_fish_session_playable(&session, &input.owner_user_id)?; let draft = session .draft_json .as_deref() .map(deserialize_draft) .transpose() .map_err(|error| format!("big_fish.draft_json 非法: {error}"))?; let work_level_count = draft .as_ref() .map(|value| value.runtime_params.level_count) .or_else(|| Some(BIG_FISH_DEFAULT_LEVEL_COUNT)); let result = start_big_fish_run_domain(StartBigFishRunCommand { run_id: input.run_id, session_id: input.session_id, owner_user_id: input.owner_user_id.clone(), draft, work_level_count, started_at_micros: input.started_at_micros, }) .map_err(|error| error.to_string())?; insert_big_fish_runtime_run( ctx, &result.snapshot, &input.owner_user_id, input.started_at_micros, )?; Ok(result.snapshot) } pub(crate) fn get_big_fish_run_tx( ctx: &ReducerContext, input: BigFishRunGetInput, ) -> Result { validate_run_get_input(&input).map_err(|error| error.to_string())?; let row = get_owned_big_fish_run_row(ctx, &input.run_id, &input.owner_user_id)?; deserialize_runtime_snapshot(&row.snapshot_json) .map_err(|error| format!("big_fish.runtime_snapshot_json 非法: {error}")) } pub(crate) fn submit_big_fish_input_tx( ctx: &ReducerContext, input: BigFishInputSubmitInput, ) -> Result { validate_input_submit_input(&input).map_err(|error| error.to_string())?; let row = get_owned_big_fish_run_row(ctx, &input.run_id, &input.owner_user_id)?; let current_snapshot = deserialize_runtime_snapshot(&row.snapshot_json) .map_err(|error| format!("big_fish.runtime_snapshot_json 非法: {error}"))?; let result = submit_big_fish_input_domain(SubmitBigFishInputCommand { owner_user_id: input.owner_user_id, x: input.x, y: input.y, submitted_at_micros: input.submitted_at_micros, current_snapshot, }) .map_err(|error| error.to_string())?; replace_big_fish_runtime_run(ctx, &row, &result.snapshot, input.submitted_at_micros)?; Ok(result.snapshot) } fn ensure_big_fish_session_playable( session: &BigFishCreationSession, player_user_id: &str, ) -> Result<(), String> { if session.owner_user_id == player_user_id { return Ok(()); } if session.stage == BigFishCreationStage::Published { return Ok(()); } Err("未发布的大鱼吃小鱼作品不允许非作者启动运行态".to_string()) } fn get_owned_big_fish_run_row( ctx: &ReducerContext, run_id: &str, owner_user_id: &str, ) -> Result { let row = ctx .db .big_fish_runtime_run() .run_id() .find(&run_id.to_string()) .ok_or_else(|| "big_fish_runtime_run 不存在".to_string())?; if row.owner_user_id != owner_user_id { return Err("无权访问该 big_fish_runtime_run".to_string()); } Ok(row) } fn insert_big_fish_runtime_run( ctx: &ReducerContext, run: &BigFishRuntimeSnapshot, owner_user_id: &str, created_at_micros: i64, ) -> Result<(), String> { let timestamp = Timestamp::from_micros_since_unix_epoch(created_at_micros); ctx.db.big_fish_runtime_run().insert(BigFishRuntimeRun { run_id: run.run_id.clone(), session_id: run.session_id.clone(), owner_user_id: owner_user_id.to_string(), status: run.status, snapshot_json: serialize_runtime_snapshot(run) .map_err(|error| format!("big_fish.runtime_snapshot 序列化失败: {error}"))?, last_input_x: run.last_input.x, last_input_y: run.last_input.y, tick: run.tick, created_at: timestamp, updated_at: timestamp, }); Ok(()) } fn replace_big_fish_runtime_run( ctx: &ReducerContext, current: &BigFishRuntimeRun, run: &BigFishRuntimeSnapshot, updated_at_micros: i64, ) -> Result<(), String> { ctx.db .big_fish_runtime_run() .run_id() .delete(¤t.run_id); ctx.db.big_fish_runtime_run().insert(BigFishRuntimeRun { run_id: run.run_id.clone(), session_id: run.session_id.clone(), owner_user_id: current.owner_user_id.clone(), status: run.status, snapshot_json: serialize_runtime_snapshot(run) .map_err(|error| format!("big_fish.runtime_snapshot 序列化失败: {error}"))?, last_input_x: run.last_input.x, last_input_y: run.last_input.y, tick: run.tick, created_at: current.created_at, updated_at: Timestamp::from_micros_since_unix_epoch(updated_at_micros), }); Ok(()) }