Files
Genarrative/server-rs/crates/spacetime-module/src/big_fish/assets.rs
2026-05-27 19:30:10 +08:00

271 lines
9.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use crate::big_fish::tables::{big_fish_asset_slot, big_fish_creation_session};
use crate::*;
use module_big_fish::{EvaluateBigFishPublishReadinessCommand, evaluate_publish_readiness};
#[spacetimedb::procedure]
pub fn generate_big_fish_asset(
ctx: &mut ProcedureContext,
input: BigFishAssetGenerateInput,
) -> BigFishSessionProcedureResult {
match ctx.try_with_tx(|tx| generate_big_fish_asset_tx(tx, input.clone())) {
Ok(session) => BigFishSessionProcedureResult {
ok: true,
session: Some(session),
error_message: None,
},
Err(message) => BigFishSessionProcedureResult {
ok: false,
session: None,
error_message: Some(message),
},
}
}
#[spacetimedb::procedure]
pub fn publish_big_fish_game(
ctx: &mut ProcedureContext,
input: BigFishPublishInput,
) -> BigFishSessionProcedureResult {
match ctx.try_with_tx(|tx| publish_big_fish_game_tx(tx, input.clone())) {
Ok(session) => BigFishSessionProcedureResult {
ok: true,
session: Some(session),
error_message: None,
},
Err(message) => BigFishSessionProcedureResult {
ok: false,
session: None,
error_message: Some(message),
},
}
}
pub(crate) fn generate_big_fish_asset_tx(
ctx: &ReducerContext,
input: BigFishAssetGenerateInput,
) -> Result<BigFishSessionSnapshot, String> {
let session = ctx
.db
.big_fish_creation_session()
.session_id()
.find(&input.session_id)
.filter(|row| row.owner_user_id == input.owner_user_id)
.ok_or_else(|| "big_fish_creation_session 不存在".to_string())?;
let draft = session
.draft_json
.as_deref()
.ok_or_else(|| "big_fish.draft 尚未编译".to_string())
.and_then(|value| deserialize_draft(value).map_err(|error| error.to_string()))?;
validate_asset_generate_input(&input, &draft).map_err(|error| error.to_string())?;
let slot = build_generated_asset_slot(
&input.session_id,
&draft,
input.asset_kind,
input.level,
input.motion_key.clone(),
input.asset_url.clone(),
input.generated_at_micros,
)
.map_err(|error| error.to_string())?;
upsert_big_fish_asset_slot(ctx, slot);
let asset_slots = list_big_fish_asset_slots(ctx, &session.session_id);
let readiness = evaluate_publish_readiness(
EvaluateBigFishPublishReadinessCommand {
session_id: session.session_id.clone(),
owner_user_id: session.owner_user_id.clone(),
draft: Some(draft.clone()),
evaluated_at_micros: input.generated_at_micros,
},
&asset_slots,
)
.map_err(|error| error.to_string())?;
let coverage = build_asset_coverage(Some(&draft), &asset_slots);
let updated_at = Timestamp::from_micros_since_unix_epoch(input.generated_at_micros);
let uses_placeholder = input
.asset_url
.as_deref()
.map(str::trim)
.is_none_or(str::is_empty);
let reply = match (input.asset_kind, uses_placeholder) {
(BigFishAssetKind::LevelMainImage, true) => "本级主图占位图已生成,可在结果页继续预览。",
(BigFishAssetKind::LevelMainImage, false) => "本级主图已正式生成,可在结果页继续预览。",
(BigFishAssetKind::LevelMotion, true) => "本级动作占位图已生成,可在结果页继续预览。",
(BigFishAssetKind::LevelMotion, false) => "本级动作图已正式生成,可在结果页继续预览。",
(BigFishAssetKind::StageBackground, true) => {
"活动区域背景占位图已生成,可在结果页继续预览。"
}
(BigFishAssetKind::StageBackground, false) => {
"活动区域背景已正式生成,可在结果页继续预览。"
}
}
.to_string();
let next_stage = if readiness.readiness.publish_ready {
BigFishCreationStage::ReadyToPublish
} else {
BigFishCreationStage::AssetRefining
};
let next_session = BigFishCreationSession {
session_id: session.session_id.clone(),
owner_user_id: session.owner_user_id.clone(),
seed_text: session.seed_text.clone(),
current_turn: session.current_turn,
progress_percent: if readiness.readiness.publish_ready {
96
} else {
88
},
stage: next_stage,
anchor_pack_json: session.anchor_pack_json.clone(),
draft_json: session.draft_json.clone(),
asset_coverage_json: serialize_asset_coverage(&coverage)
.map_err(|error| error.to_string())?,
last_assistant_reply: Some(reply.clone()),
publish_ready: readiness.readiness.publish_ready,
play_count: session.play_count,
remix_count: session.remix_count,
like_count: session.like_count,
published_at: session.published_at,
created_at: session.created_at,
updated_at,
visible: session.visible,
};
replace_big_fish_session(ctx, &session, next_session);
for event in readiness.events {
emit_big_fish_publish_readiness_event(ctx, event)?;
}
get_big_fish_session_tx(
ctx,
BigFishSessionGetInput {
session_id: input.session_id,
owner_user_id: input.owner_user_id,
},
)
}
pub(crate) fn publish_big_fish_game_tx(
ctx: &ReducerContext,
input: BigFishPublishInput,
) -> Result<BigFishSessionSnapshot, String> {
validate_publish_input(&input).map_err(|error| error.to_string())?;
let session = ctx
.db
.big_fish_creation_session()
.session_id()
.find(&input.session_id)
.filter(|row| row.owner_user_id == input.owner_user_id)
.ok_or_else(|| "big_fish_creation_session 不存在".to_string())?;
let draft = session
.draft_json
.as_deref()
.ok_or_else(|| "big_fish.draft 尚未编译".to_string())
.and_then(|value| deserialize_draft(value).map_err(|error| error.to_string()))?;
let asset_slots = list_big_fish_asset_slots(ctx, &session.session_id);
let readiness = evaluate_publish_readiness(
EvaluateBigFishPublishReadinessCommand {
session_id: session.session_id.clone(),
owner_user_id: session.owner_user_id.clone(),
draft: Some(draft.clone()),
evaluated_at_micros: input.published_at_micros,
},
&asset_slots,
)
.map_err(|error| error.to_string())?;
let coverage = build_asset_coverage(Some(&draft), &asset_slots);
if !readiness.readiness.publish_ready {
return Err(format!(
"big_fish 发布校验未通过:{}",
readiness.readiness.blockers.join("")
));
}
let published_at = Timestamp::from_micros_since_unix_epoch(input.published_at_micros);
let next_session = BigFishCreationSession {
session_id: session.session_id.clone(),
owner_user_id: session.owner_user_id.clone(),
seed_text: session.seed_text.clone(),
current_turn: session.current_turn,
progress_percent: 100,
stage: BigFishCreationStage::Published,
anchor_pack_json: session.anchor_pack_json.clone(),
draft_json: session.draft_json.clone(),
asset_coverage_json: serialize_asset_coverage(&coverage)
.map_err(|error| error.to_string())?,
last_assistant_reply: Some("玩法已发布,可以进入测试运行态。".to_string()),
publish_ready: true,
play_count: session.play_count,
remix_count: session.remix_count,
like_count: session.like_count,
published_at: Some(published_at),
created_at: session.created_at,
updated_at: published_at,
visible: session.visible,
};
replace_big_fish_session(ctx, &session, next_session);
for event in readiness.events {
emit_big_fish_publish_readiness_event(ctx, event)?;
}
get_big_fish_session_tx(
ctx,
BigFishSessionGetInput {
session_id: input.session_id,
owner_user_id: input.owner_user_id,
},
)
}
pub(crate) fn list_big_fish_asset_slots(
ctx: &ReducerContext,
session_id: &str,
) -> Vec<BigFishAssetSlotSnapshot> {
let mut slots = ctx
.db
.big_fish_asset_slot()
.by_big_fish_asset_session_id()
.filter(&session_id.to_string())
.map(|slot| BigFishAssetSlotSnapshot {
slot_id: slot.slot_id,
session_id: slot.session_id,
asset_kind: slot.asset_kind,
level: slot.level,
motion_key: slot.motion_key,
status: slot.status,
asset_url: slot.asset_url,
prompt_snapshot: slot.prompt_snapshot,
updated_at_micros: slot.updated_at.to_micros_since_unix_epoch(),
})
.collect::<Vec<_>>();
slots.sort_by_key(|slot| {
(
slot.level.unwrap_or(0),
slot.asset_kind.as_str().to_string(),
slot.motion_key.clone().unwrap_or_default(),
slot.slot_id.clone(),
)
});
slots
}
pub(crate) fn upsert_big_fish_asset_slot(ctx: &ReducerContext, slot: BigFishAssetSlotSnapshot) {
if let Some(existing) = ctx.db.big_fish_asset_slot().slot_id().find(&slot.slot_id) {
ctx.db
.big_fish_asset_slot()
.slot_id()
.delete(&existing.slot_id);
}
ctx.db.big_fish_asset_slot().insert(BigFishAssetSlot {
slot_id: slot.slot_id,
session_id: slot.session_id,
asset_kind: slot.asset_kind,
level: slot.level,
motion_key: slot.motion_key,
status: slot.status,
asset_url: slot.asset_url,
prompt_snapshot: slot.prompt_snapshot,
updated_at: Timestamp::from_micros_since_unix_epoch(slot.updated_at_micros),
});
}