feat: complete M3 runtime snapshot and profile save archive

This commit is contained in:
2026-04-22 13:22:23 +08:00
parent 997a8daada
commit 209e924403
340 changed files with 9878 additions and 4429 deletions

View File

@@ -49,14 +49,19 @@ use module_npc::{
use module_runtime::{
RuntimeBrowseHistoryRecord, RuntimeBrowseHistoryThemeMode, RuntimePlatformTheme,
RuntimeProfileDashboardRecord, RuntimeProfilePlayStatsRecord,
RuntimeProfileSaveArchiveRecord,
RuntimeProfileWalletLedgerEntryRecord, RuntimeProfileWalletLedgerSourceType,
RuntimeSettingsRecord, build_runtime_browse_history_clear_input,
RuntimeSettingsRecord, RuntimeSnapshotRecord, build_runtime_browse_history_clear_input,
build_runtime_browse_history_list_input, build_runtime_browse_history_record,
build_runtime_browse_history_sync_input, build_runtime_profile_dashboard_get_input,
build_runtime_profile_dashboard_record, build_runtime_profile_play_stats_get_input,
build_runtime_profile_play_stats_record, build_runtime_profile_wallet_ledger_entry_record,
build_runtime_profile_play_stats_record, build_runtime_profile_save_archive_list_input,
build_runtime_profile_save_archive_record, build_runtime_profile_save_archive_resume_input,
build_runtime_profile_wallet_ledger_entry_record,
build_runtime_profile_wallet_ledger_list_input, build_runtime_setting_get_input,
build_runtime_setting_record, build_runtime_setting_upsert_input,
build_runtime_snapshot_delete_input, build_runtime_snapshot_get_input,
build_runtime_snapshot_record, build_runtime_snapshot_upsert_input,
};
use module_runtime_item::{
RuntimeItemEquipmentSlot as DomainRuntimeItemEquipmentSlot,
@@ -160,6 +165,10 @@ use crate::module_bindings::{
RuntimeProfileDashboardGetInput as BindingRuntimeProfileDashboardGetInput,
RuntimeProfileDashboardProcedureResult as BindingRuntimeProfileDashboardProcedureResult,
RuntimeProfileDashboardSnapshot as BindingRuntimeProfileDashboardSnapshot,
RuntimeProfileSaveArchiveListInput as BindingRuntimeProfileSaveArchiveListInput,
RuntimeProfileSaveArchiveProcedureResult as BindingRuntimeProfileSaveArchiveProcedureResult,
RuntimeProfileSaveArchiveResumeInput as BindingRuntimeProfileSaveArchiveResumeInput,
RuntimeProfileSaveArchiveSnapshot as BindingRuntimeProfileSaveArchiveSnapshot,
RuntimeProfilePlayStatsGetInput as BindingRuntimeProfilePlayStatsGetInput,
RuntimeProfilePlayStatsProcedureResult as BindingRuntimeProfilePlayStatsProcedureResult,
RuntimeProfilePlayStatsSnapshot as BindingRuntimeProfilePlayStatsSnapshot,
@@ -172,6 +181,11 @@ use crate::module_bindings::{
RuntimeSettingProcedureResult as BindingRuntimeSettingProcedureResult,
RuntimeSettingSnapshot as BindingRuntimeSettingSnapshot,
RuntimeSettingUpsertInput as BindingRuntimeSettingUpsertInput,
RuntimeSnapshotDeleteInput as BindingRuntimeSnapshotDeleteInput,
RuntimeSnapshotGetInput as BindingRuntimeSnapshotGetInput,
RuntimeSnapshotProcedureResult as BindingRuntimeSnapshotProcedureResult,
RuntimeSnapshot as BindingRuntimeSnapshot,
RuntimeSnapshotUpsertInput as BindingRuntimeSnapshotUpsertInput,
StoryContinueInput as BindingStoryContinueInput, StoryEventKind as BindingStoryEventKind,
StoryEventSnapshot as BindingStoryEventSnapshot, StorySessionInput as BindingStorySessionInput,
StorySessionProcedureResult as BindingStorySessionProcedureResult,
@@ -192,6 +206,7 @@ use crate::module_bindings::{
create_ai_task_and_return_procedure::create_ai_task_and_return as _,
create_battle_state_and_return_procedure::create_battle_state_and_return as _,
create_custom_world_agent_session_procedure::create_custom_world_agent_session as _,
delete_runtime_snapshot_and_return_procedure::delete_runtime_snapshot_and_return as _,
fail_ai_task_and_return_procedure::fail_ai_task_and_return as _,
get_battle_state_procedure::get_battle_state as _,
get_custom_world_agent_operation_procedure::get_custom_world_agent_operation as _,
@@ -202,15 +217,18 @@ use crate::module_bindings::{
get_profile_play_stats_procedure::get_profile_play_stats as _,
get_runtime_inventory_state_procedure::get_runtime_inventory_state as _,
get_runtime_setting_or_default_procedure::get_runtime_setting_or_default as _,
get_runtime_snapshot_procedure::get_runtime_snapshot as _,
get_story_session_state_procedure::get_story_session_state as _,
list_custom_world_gallery_entries_procedure::list_custom_world_gallery_entries as _,
list_custom_world_profiles_procedure::list_custom_world_profiles as _,
list_platform_browse_history_procedure::list_platform_browse_history as _,
list_profile_save_archives_procedure::list_profile_save_archives as _,
list_profile_wallet_ledger_procedure::list_profile_wallet_ledger as _,
publish_custom_world_profile_and_return_procedure::publish_custom_world_profile_and_return as _,
publish_custom_world_world_procedure::publish_custom_world_world as _,
resolve_combat_action_and_return_procedure::resolve_combat_action_and_return as _,
resolve_npc_battle_interaction_and_return_procedure::resolve_npc_battle_interaction_and_return as _,
resume_profile_save_archive_and_return_procedure::resume_profile_save_archive_and_return as _,
start_ai_task_reducer::start_ai_task as _,
start_ai_task_stage_reducer::start_ai_task_stage as _,
submit_custom_world_agent_message_procedure::submit_custom_world_agent_message as _,
@@ -218,6 +236,7 @@ use crate::module_bindings::{
upsert_custom_world_profile_and_return_procedure::upsert_custom_world_profile_and_return as _,
upsert_platform_browse_history_and_return_procedure::upsert_platform_browse_history_and_return as _,
upsert_runtime_setting_and_return_procedure::upsert_runtime_setting_and_return as _,
upsert_runtime_snapshot_and_return_procedure::upsert_runtime_snapshot_and_return as _,
};
#[derive(Clone, Debug)]
@@ -888,6 +907,136 @@ impl SpacetimeClient {
.await
}
pub async fn get_runtime_snapshot(
&self,
user_id: String,
) -> Result<Option<RuntimeSnapshotRecord>, SpacetimeClientError> {
let procedure_input = map_runtime_snapshot_get_input(
build_runtime_snapshot_get_input(user_id)
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?,
);
self.call_after_connect(move |connection, sender| {
connection.procedures().get_runtime_snapshot_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(|error| SpacetimeClientError::Procedure(error.to_string()))
.and_then(map_runtime_snapshot_procedure_result);
send_once(&sender, mapped);
},
);
})
.await
}
pub async fn put_runtime_snapshot(
&self,
user_id: String,
saved_at_micros: i64,
bottom_tab: String,
game_state: serde_json::Value,
current_story: Option<serde_json::Value>,
updated_at_micros: i64,
) -> Result<RuntimeSnapshotRecord, SpacetimeClientError> {
let procedure_input = map_runtime_snapshot_upsert_input(
build_runtime_snapshot_upsert_input(
user_id,
saved_at_micros,
bottom_tab,
game_state,
current_story,
updated_at_micros,
)
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?,
);
self.call_after_connect(move |connection, sender| {
connection.procedures().upsert_runtime_snapshot_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(|error| SpacetimeClientError::Procedure(error.to_string()))
.and_then(map_runtime_snapshot_required_procedure_result);
send_once(&sender, mapped);
},
);
})
.await
}
pub async fn delete_runtime_snapshot(
&self,
user_id: String,
) -> Result<bool, SpacetimeClientError> {
let procedure_input = map_runtime_snapshot_delete_input(
build_runtime_snapshot_delete_input(user_id)
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?,
);
self.call_after_connect(move |connection, sender| {
connection.procedures().delete_runtime_snapshot_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(|error| SpacetimeClientError::Procedure(error.to_string()))
.and_then(map_runtime_snapshot_delete_procedure_result);
send_once(&sender, mapped);
},
);
})
.await
}
pub async fn list_profile_save_archives(
&self,
user_id: String,
) -> Result<Vec<RuntimeProfileSaveArchiveRecord>, SpacetimeClientError> {
let procedure_input = map_runtime_profile_save_archive_list_input(
build_runtime_profile_save_archive_list_input(user_id)
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?,
);
self.call_after_connect(move |connection, sender| {
connection.procedures().list_profile_save_archives_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(|error| SpacetimeClientError::Procedure(error.to_string()))
.and_then(map_runtime_profile_save_archive_list_procedure_result);
send_once(&sender, mapped);
},
);
})
.await
}
pub async fn resume_profile_save_archive(
&self,
user_id: String,
world_key: String,
) -> Result<(RuntimeProfileSaveArchiveRecord, RuntimeSnapshotRecord), SpacetimeClientError> {
let procedure_input = map_runtime_profile_save_archive_resume_input(
build_runtime_profile_save_archive_resume_input(user_id, world_key)
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?,
);
self.call_after_connect(move |connection, sender| {
connection
.procedures()
.resume_profile_save_archive_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(|error| SpacetimeClientError::Procedure(error.to_string()))
.and_then(map_runtime_profile_save_archive_resume_procedure_result);
send_once(&sender, mapped);
},
);
})
.await
}
pub async fn begin_story_session(
&self,
story_session_id: String,
@@ -1411,6 +1560,52 @@ fn map_runtime_profile_play_stats_get_input(
}
}
fn map_runtime_snapshot_get_input(
input: module_runtime::RuntimeSnapshotGetInput,
) -> BindingRuntimeSnapshotGetInput {
BindingRuntimeSnapshotGetInput {
user_id: input.user_id,
}
}
fn map_runtime_snapshot_upsert_input(
input: module_runtime::RuntimeSnapshotUpsertInput,
) -> BindingRuntimeSnapshotUpsertInput {
BindingRuntimeSnapshotUpsertInput {
user_id: input.user_id,
saved_at_micros: input.saved_at_micros,
bottom_tab: input.bottom_tab,
game_state_json: input.game_state_json,
current_story_json: input.current_story_json,
updated_at_micros: input.updated_at_micros,
}
}
fn map_runtime_snapshot_delete_input(
input: module_runtime::RuntimeSnapshotDeleteInput,
) -> BindingRuntimeSnapshotDeleteInput {
BindingRuntimeSnapshotDeleteInput {
user_id: input.user_id,
}
}
fn map_runtime_profile_save_archive_list_input(
input: module_runtime::RuntimeProfileSaveArchiveListInput,
) -> BindingRuntimeProfileSaveArchiveListInput {
BindingRuntimeProfileSaveArchiveListInput {
user_id: input.user_id,
}
}
fn map_runtime_profile_save_archive_resume_input(
input: module_runtime::RuntimeProfileSaveArchiveResumeInput,
) -> BindingRuntimeProfileSaveArchiveResumeInput {
BindingRuntimeProfileSaveArchiveResumeInput {
user_id: input.user_id,
world_key: input.world_key,
}
}
fn map_ai_task_create_input(input: DomainAiTaskCreateInput) -> BindingAiTaskCreateInput {
BindingAiTaskCreateInput {
task_id: input.task_id,
@@ -1786,6 +1981,89 @@ fn map_runtime_profile_play_stats_procedure_result(
))
}
fn map_runtime_snapshot_procedure_result(
result: BindingRuntimeSnapshotProcedureResult,
) -> Result<Option<RuntimeSnapshotRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::Procedure(
result
.error_message
.unwrap_or_else(|| "SpacetimeDB procedure 返回未知错误".to_string()),
));
}
result
.record
.map(|snapshot| {
build_runtime_snapshot_record(map_runtime_snapshot_snapshot(snapshot))
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))
})
.transpose()
}
fn map_runtime_snapshot_required_procedure_result(
result: BindingRuntimeSnapshotProcedureResult,
) -> Result<RuntimeSnapshotRecord, SpacetimeClientError> {
map_runtime_snapshot_procedure_result(result)?.ok_or_else(|| {
SpacetimeClientError::Procedure("SpacetimeDB procedure 未返回 runtime snapshot 快照".to_string())
})
}
fn map_runtime_snapshot_delete_procedure_result(
result: BindingRuntimeSnapshotProcedureResult,
) -> Result<bool, SpacetimeClientError> {
map_runtime_snapshot_procedure_result(result).map(|record| record.is_some())
}
fn map_runtime_profile_save_archive_list_procedure_result(
result: BindingRuntimeProfileSaveArchiveProcedureResult,
) -> Result<Vec<RuntimeProfileSaveArchiveRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::Procedure(
result
.error_message
.unwrap_or_else(|| "SpacetimeDB procedure 返回未知错误".to_string()),
));
}
result
.entries
.into_iter()
.map(|snapshot| {
build_runtime_profile_save_archive_record(
map_runtime_profile_save_archive_snapshot(snapshot),
)
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))
})
.collect()
}
fn map_runtime_profile_save_archive_resume_procedure_result(
result: BindingRuntimeProfileSaveArchiveProcedureResult,
) -> Result<(RuntimeProfileSaveArchiveRecord, RuntimeSnapshotRecord), SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::Procedure(
result
.error_message
.unwrap_or_else(|| "SpacetimeDB procedure 返回未知错误".to_string()),
));
}
let archive = result.record.ok_or_else(|| {
SpacetimeClientError::Procedure("SpacetimeDB procedure 未返回 save archive 快照".to_string())
})?;
let snapshot = result.current_snapshot.ok_or_else(|| {
SpacetimeClientError::Procedure("SpacetimeDB procedure 未返回恢复后的 runtime snapshot".to_string())
})?;
Ok((
build_runtime_profile_save_archive_record(map_runtime_profile_save_archive_snapshot(archive))
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?,
build_runtime_snapshot_record(map_runtime_snapshot_snapshot(snapshot))
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?,
))
}
fn map_ai_task_procedure_result(
result: BindingAiTaskProcedureResult,
) -> Result<AiTaskMutationRecord, SpacetimeClientError> {
@@ -2247,6 +2525,44 @@ fn map_runtime_profile_play_stats_snapshot(
}
}
fn map_runtime_snapshot_snapshot(
snapshot: BindingRuntimeSnapshot,
) -> module_runtime::RuntimeSnapshot {
module_runtime::RuntimeSnapshot {
user_id: snapshot.user_id,
version: snapshot.version,
saved_at_micros: snapshot.saved_at_micros,
bottom_tab: snapshot.bottom_tab,
game_state_json: snapshot.game_state_json,
current_story_json: snapshot.current_story_json,
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
fn map_runtime_profile_save_archive_snapshot(
snapshot: BindingRuntimeProfileSaveArchiveSnapshot,
) -> module_runtime::RuntimeProfileSaveArchiveSnapshot {
module_runtime::RuntimeProfileSaveArchiveSnapshot {
archive_id: snapshot.archive_id,
user_id: snapshot.user_id,
world_key: snapshot.world_key,
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
world_type: snapshot.world_type,
world_name: snapshot.world_name,
subtitle: snapshot.subtitle,
summary_text: snapshot.summary_text,
cover_image_src: snapshot.cover_image_src,
saved_at_micros: snapshot.saved_at_micros,
bottom_tab: snapshot.bottom_tab,
game_state_json: snapshot.game_state_json,
current_story_json: snapshot.current_story_json,
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
fn map_custom_world_library_entry_from_profile_snapshot(
snapshot: BindingCustomWorldProfileSnapshot,
) -> Result<CustomWorldLibraryEntryRecord, SpacetimeClientError> {