合并 origin/master
合入 master 的钱包退款 outbox、拼图后台编译互斥与公开链路更新 保留当前分支外部生成 worker 队列语义,并对齐拼图首图 claim 释放顺序
This commit is contained in:
@@ -20,8 +20,8 @@ use crate::match3d::tables::{
|
||||
match_3_d_work_profile, match3d_agent_message, match3d_agent_session, match3d_runtime_run,
|
||||
};
|
||||
use crate::puzzle::{
|
||||
puzzle_agent_message, puzzle_agent_session, puzzle_event, puzzle_leaderboard_entry,
|
||||
puzzle_runtime_run, puzzle_work_profile,
|
||||
puzzle_agent_message, puzzle_agent_session, puzzle_background_compile_task, puzzle_event,
|
||||
puzzle_leaderboard_entry, puzzle_runtime_run, puzzle_work_profile,
|
||||
};
|
||||
use crate::puzzle_clear::tables::{
|
||||
puzzle_clear_agent_session, puzzle_clear_event, puzzle_clear_runtime_run,
|
||||
@@ -230,6 +230,7 @@ macro_rules! migration_tables {
|
||||
asset_entity_binding,
|
||||
asset_event,
|
||||
puzzle_agent_session,
|
||||
puzzle_background_compile_task,
|
||||
puzzle_agent_message,
|
||||
puzzle_work_profile,
|
||||
puzzle_event,
|
||||
|
||||
@@ -10,19 +10,20 @@ use module_puzzle::{
|
||||
PUZZLE_NEXT_LEVEL_MODE_SIMILAR_WORKS, PuzzleAgentMessageFinalizeInput, PuzzleAgentMessageKind,
|
||||
PuzzleAgentMessageRole, PuzzleAgentMessageSnapshot, PuzzleAgentSessionCreateInput,
|
||||
PuzzleAgentSessionGetInput, PuzzleAgentSessionProcedureResult, PuzzleAgentSessionSnapshot,
|
||||
PuzzleAgentStage, PuzzleAnchorPack, PuzzleDraftCompileFailureInput, PuzzleDraftCompileInput,
|
||||
PuzzleFormDraftSaveInput, PuzzleGeneratedImageCandidate, PuzzleGeneratedImagesSaveInput,
|
||||
PuzzleLeaderboardEntry, PuzzleLeaderboardSubmitInput, PuzzleLevelGenerationFailureInput,
|
||||
PuzzlePublicationStatus, PuzzlePublishInput, PuzzleRecommendedNextWork, PuzzleResultDraft,
|
||||
PuzzleRunDragInput, PuzzleRunGetInput, PuzzleRunNextLevelInput, PuzzleRunPauseInput,
|
||||
PuzzleRunProcedureResult, PuzzleRunPropInput, PuzzleRunSnapshot, PuzzleRunStartInput,
|
||||
PuzzleRunSwapInput, PuzzleRuntimeLevelStatus, PuzzleSelectCoverImageInput,
|
||||
PuzzleUiBackgroundSaveInput, PuzzleWorkDeleteInput, PuzzleWorkGetInput,
|
||||
PuzzleWorkLikeRecordInput as PuzzleWorkLikeInput, PuzzleWorkPointIncentiveClaimInput,
|
||||
PuzzleWorkProcedureResult, PuzzleWorkProfile, PuzzleWorkRemixInput, PuzzleWorkUpsertInput,
|
||||
PuzzleWorksListInput, PuzzleWorksProcedureResult, apply_publish_overrides_to_draft,
|
||||
apply_selected_candidate, build_form_draft_from_seed, build_result_preview,
|
||||
compile_result_draft_from_seed, create_work_profile, infer_anchor_pack,
|
||||
PuzzleAgentStage, PuzzleAnchorPack, PuzzleBackgroundCompileTaskClaimInput,
|
||||
PuzzleBackgroundCompileTaskProcedureResult, PuzzleBackgroundCompileTaskReleaseInput,
|
||||
PuzzleDraftCompileFailureInput, PuzzleDraftCompileInput, PuzzleFormDraftSaveInput,
|
||||
PuzzleGeneratedImageCandidate, PuzzleGeneratedImagesSaveInput, PuzzleLeaderboardEntry,
|
||||
PuzzleLeaderboardSubmitInput, PuzzleLevelGenerationFailureInput, PuzzlePublicationStatus,
|
||||
PuzzlePublishInput, PuzzleRecommendedNextWork, PuzzleResultDraft, PuzzleRunDragInput,
|
||||
PuzzleRunGetInput, PuzzleRunNextLevelInput, PuzzleRunPauseInput, PuzzleRunProcedureResult,
|
||||
PuzzleRunPropInput, PuzzleRunSnapshot, PuzzleRunStartInput, PuzzleRunSwapInput,
|
||||
PuzzleRuntimeLevelStatus, PuzzleSelectCoverImageInput, PuzzleUiBackgroundSaveInput,
|
||||
PuzzleWorkDeleteInput, PuzzleWorkGetInput, PuzzleWorkLikeRecordInput as PuzzleWorkLikeInput,
|
||||
PuzzleWorkPointIncentiveClaimInput, PuzzleWorkProcedureResult, PuzzleWorkProfile,
|
||||
PuzzleWorkRemixInput, PuzzleWorkUpsertInput, PuzzleWorksListInput, PuzzleWorksProcedureResult,
|
||||
apply_publish_overrides_to_draft, apply_selected_candidate, build_form_draft_from_seed,
|
||||
build_result_preview, compile_result_draft_from_seed, create_work_profile, infer_anchor_pack,
|
||||
mark_failed_puzzle_result_draft_generation, normalize_puzzle_draft, normalize_puzzle_levels,
|
||||
normalize_theme_tags, publish_work_profile, replace_puzzle_level, select_next_profiles,
|
||||
selected_profile_level_after_runtime_level, selected_puzzle_level, tag_similarity_score,
|
||||
@@ -40,6 +41,7 @@ use crate::auth::user_account;
|
||||
use crate::validate_external_generation_job_lease_for_tx;
|
||||
|
||||
const PUZZLE_POINT_INCENTIVE_DEFAULT_U64: u64 = 0;
|
||||
const PUZZLE_BACKGROUND_COMPILE_TASK_LEASE_MICROS: i64 = 30 * 60 * 1_000_000;
|
||||
const WORK_VISIBLE_DEFAULT: bool = true;
|
||||
const PUZZLE_EXTERNAL_GENERATION_SOURCE_MODULE: &str = "puzzle";
|
||||
const PUZZLE_COMPILE_DRAFT_JOB_KIND: &str = "puzzle_compile_draft";
|
||||
@@ -68,6 +70,22 @@ pub struct PuzzleAgentSessionRow {
|
||||
updated_at: Timestamp,
|
||||
}
|
||||
|
||||
/// 拼图首图后台编译活动任务表。
|
||||
/// 中文注释:该表只保存跨 api-server 实例互斥 claim,不表达最终生成结果。
|
||||
#[spacetimedb::table(
|
||||
accessor = puzzle_background_compile_task,
|
||||
index(accessor = by_puzzle_background_compile_task_session_id, btree(columns = [session_id]))
|
||||
)]
|
||||
pub struct PuzzleBackgroundCompileTaskRow {
|
||||
#[primary_key]
|
||||
task_id: String,
|
||||
claim_id: String,
|
||||
session_id: String,
|
||||
owner_user_id: String,
|
||||
created_at: Timestamp,
|
||||
updated_at: Timestamp,
|
||||
}
|
||||
|
||||
/// 拼图 Agent 消息真相表。
|
||||
#[spacetimedb::table(
|
||||
accessor = puzzle_agent_message,
|
||||
@@ -413,6 +431,43 @@ pub fn mark_puzzle_level_generation_failed(
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn claim_puzzle_background_compile_task(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: PuzzleBackgroundCompileTaskClaimInput,
|
||||
) -> PuzzleBackgroundCompileTaskProcedureResult {
|
||||
match ctx.try_with_tx(|tx| claim_puzzle_background_compile_task_tx(tx, input.clone())) {
|
||||
Ok(claimed) => PuzzleBackgroundCompileTaskProcedureResult {
|
||||
ok: true,
|
||||
claimed,
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => PuzzleBackgroundCompileTaskProcedureResult {
|
||||
ok: false,
|
||||
claimed: false,
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn release_puzzle_background_compile_task(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: PuzzleBackgroundCompileTaskReleaseInput,
|
||||
) -> PuzzleBackgroundCompileTaskProcedureResult {
|
||||
match ctx.try_with_tx(|tx| release_puzzle_background_compile_task_tx(tx, input.clone())) {
|
||||
Ok(released) => PuzzleBackgroundCompileTaskProcedureResult {
|
||||
ok: true,
|
||||
claimed: released,
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => PuzzleBackgroundCompileTaskProcedureResult {
|
||||
ok: false,
|
||||
claimed: false,
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
/// 保存拼图入口表单草稿。
|
||||
/// 中文注释:该 procedure 只更新 session 与创作中心草稿卡,不触发图片生成或发布校验。
|
||||
#[spacetimedb::procedure]
|
||||
@@ -1060,6 +1115,84 @@ fn compile_puzzle_agent_draft_tx(
|
||||
)
|
||||
}
|
||||
|
||||
fn claim_puzzle_background_compile_task_tx(
|
||||
ctx: &TxContext,
|
||||
input: PuzzleBackgroundCompileTaskClaimInput,
|
||||
) -> Result<bool, String> {
|
||||
let task_id = normalize_required_puzzle_task_field(&input.task_id, "拼图后台任务 ID")?;
|
||||
let claim_id = normalize_required_puzzle_task_field(&input.claim_id, "拼图后台任务 claim ID")?;
|
||||
let session_id = normalize_required_puzzle_task_field(&input.session_id, "拼图 session ID")?;
|
||||
let owner_user_id = normalize_required_puzzle_task_field(&input.owner_user_id, "拼图用户 ID")?;
|
||||
let claimed_at = Timestamp::from_micros_since_unix_epoch(input.claimed_at_micros);
|
||||
|
||||
get_owned_session_row(ctx, &session_id, &owner_user_id)?;
|
||||
if let Some(existing) = ctx
|
||||
.db
|
||||
.puzzle_background_compile_task()
|
||||
.task_id()
|
||||
.find(&task_id)
|
||||
{
|
||||
if !is_stale_puzzle_background_compile_task(&existing, input.claimed_at_micros) {
|
||||
return Ok(false);
|
||||
}
|
||||
ctx.db
|
||||
.puzzle_background_compile_task()
|
||||
.task_id()
|
||||
.delete(&task_id);
|
||||
}
|
||||
|
||||
ctx.db
|
||||
.puzzle_background_compile_task()
|
||||
.insert(PuzzleBackgroundCompileTaskRow {
|
||||
task_id,
|
||||
claim_id,
|
||||
session_id,
|
||||
owner_user_id,
|
||||
created_at: claimed_at,
|
||||
updated_at: claimed_at,
|
||||
});
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn release_puzzle_background_compile_task_tx(
|
||||
ctx: &TxContext,
|
||||
input: PuzzleBackgroundCompileTaskReleaseInput,
|
||||
) -> Result<bool, String> {
|
||||
let task_id = normalize_required_puzzle_task_field(&input.task_id, "拼图后台任务 ID")?;
|
||||
let claim_id = normalize_required_puzzle_task_field(&input.claim_id, "拼图后台任务 claim ID")?;
|
||||
let session_id = normalize_required_puzzle_task_field(&input.session_id, "拼图 session ID")?;
|
||||
let owner_user_id = normalize_required_puzzle_task_field(&input.owner_user_id, "拼图用户 ID")?;
|
||||
|
||||
let Some(row) = ctx
|
||||
.db
|
||||
.puzzle_background_compile_task()
|
||||
.task_id()
|
||||
.find(&task_id)
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
if row.session_id != session_id || row.owner_user_id != owner_user_id {
|
||||
return Err("无权释放该拼图后台任务".to_string());
|
||||
}
|
||||
if row.claim_id != claim_id {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
ctx.db
|
||||
.puzzle_background_compile_task()
|
||||
.task_id()
|
||||
.delete(&task_id);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn is_stale_puzzle_background_compile_task(
|
||||
row: &PuzzleBackgroundCompileTaskRow,
|
||||
now_micros: i64,
|
||||
) -> bool {
|
||||
now_micros.saturating_sub(row.updated_at.to_micros_since_unix_epoch())
|
||||
>= PUZZLE_BACKGROUND_COMPILE_TASK_LEASE_MICROS
|
||||
}
|
||||
|
||||
fn mark_puzzle_draft_generation_failed_tx(
|
||||
ctx: &TxContext,
|
||||
input: PuzzleDraftCompileFailureInput,
|
||||
@@ -3162,6 +3295,14 @@ fn get_owned_session_row(
|
||||
Ok(row)
|
||||
}
|
||||
|
||||
fn normalize_required_puzzle_task_field(value: &str, field_name: &str) -> Result<String, String> {
|
||||
let normalized = value.trim();
|
||||
if normalized.is_empty() {
|
||||
return Err(format!("{field_name} 不能为空"));
|
||||
}
|
||||
Ok(normalized.to_string())
|
||||
}
|
||||
|
||||
fn get_owned_run_row(
|
||||
ctx: &TxContext,
|
||||
run_id: &str,
|
||||
|
||||
Reference in New Issue
Block a user