后端:新增批量游玩计数 procedure 与生成绑定

- spacetime-module/game_distribution.rs:新增 GameDistributionPlayCountIncrement(Input) 与 increment_game_distribution_game_play_counts_and_return,事务内只对 published 且有 active_version_id 的游戏 saturating_add,且不 bump updated_at
- 重新执行 npm run spacetime:generate,新增 3 个绑定并同步 module_bindings.rs 等生成产物
This commit is contained in:
2026-10-03 18:04:47 +08:00
parent 7f5ddf8f47
commit cc33c39f24
15 changed files with 258 additions and 86 deletions
@@ -426,6 +426,8 @@ pub mod game_distribution_owner_game_list_input_type;
pub mod game_distribution_owner_game_list_procedure_result_type;
pub mod game_distribution_owner_game_snapshot_type;
pub mod game_distribution_owner_version_input_type;
pub mod game_distribution_play_count_increment_input_type;
pub mod game_distribution_play_count_increment_type;
pub mod game_distribution_procedure_result_type;
pub mod game_distribution_public_game_input_type;
pub mod game_distribution_public_game_list_input_type;
@@ -492,6 +494,7 @@ pub mod import_database_migration_from_chunks_procedure;
pub mod import_database_migration_from_file_procedure;
pub mod import_database_migration_incremental_from_chunks_procedure;
pub mod import_database_migration_incremental_from_file_procedure;
pub mod increment_game_distribution_game_play_counts_and_return_procedure;
pub mod initialize_editor_generation_pricing_config_if_missing_and_return_procedure;
pub mod list_admin_accounts_and_return_procedure;
pub mod list_admin_game_distribution_games_and_return_procedure;
@@ -1229,6 +1232,8 @@ pub use game_distribution_owner_game_list_input_type::GameDistributionOwnerGameL
pub use game_distribution_owner_game_list_procedure_result_type::GameDistributionOwnerGameListProcedureResult;
pub use game_distribution_owner_game_snapshot_type::GameDistributionOwnerGameSnapshot;
pub use game_distribution_owner_version_input_type::GameDistributionOwnerVersionInput;
pub use game_distribution_play_count_increment_input_type::GameDistributionPlayCountIncrementInput;
pub use game_distribution_play_count_increment_type::GameDistributionPlayCountIncrement;
pub use game_distribution_procedure_result_type::GameDistributionProcedureResult;
pub use game_distribution_public_game_input_type::GameDistributionPublicGameInput;
pub use game_distribution_public_game_list_input_type::GameDistributionPublicGameListInput;
@@ -1295,6 +1300,7 @@ pub use import_database_migration_from_chunks_procedure::import_database_migrati
pub use import_database_migration_from_file_procedure::import_database_migration_from_file;
pub use import_database_migration_incremental_from_chunks_procedure::import_database_migration_incremental_from_chunks;
pub use import_database_migration_incremental_from_file_procedure::import_database_migration_incremental_from_file;
pub use increment_game_distribution_game_play_counts_and_return_procedure::increment_game_distribution_game_play_counts_and_return;
pub use initialize_editor_generation_pricing_config_if_missing_and_return_procedure::initialize_editor_generation_pricing_config_if_missing_and_return;
pub use list_admin_accounts_and_return_procedure::list_admin_accounts_and_return;
pub use list_admin_game_distribution_games_and_return_procedure::list_admin_game_distribution_games_and_return;
@@ -3966,19 +3972,19 @@ impl __sdk::SubscriptionHandle for SubscriptionHandle {
/// either a [`DbConnection`] or an [`EventContext`] and operate on either.
pub trait RemoteDbContext:
__sdk::DbContext<
DbView = RemoteTables,
Reducers = RemoteReducers,
SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>,
>
DbView = RemoteTables,
Reducers = RemoteReducers,
SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>,
>
{
}
impl<
Ctx: __sdk::DbContext<
Ctx: __sdk::DbContext<
DbView = RemoteTables,
Reducers = RemoteReducers,
SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>,
>,
> RemoteDbContext for Ctx
> RemoteDbContext for Ctx
{
}
@@ -0,0 +1,17 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::game_distribution_play_count_increment_type::GameDistributionPlayCountIncrement;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct GameDistributionPlayCountIncrementInput {
pub increments: Vec<GameDistributionPlayCountIncrement>,
}
impl __sdk::InModule for GameDistributionPlayCountIncrementInput {
type Module = super::RemoteModule;
}
@@ -0,0 +1,16 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct GameDistributionPlayCountIncrement {
pub game_id: String,
pub delta: u64,
}
impl __sdk::InModule for GameDistributionPlayCountIncrement {
type Module = super::RemoteModule;
}
@@ -34,10 +34,10 @@ pub trait get_admin_game_distribution_user_review_and_return {
input: GameDistributionAdminUserReviewInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
);
}
@@ -47,10 +47,10 @@ impl get_admin_game_distribution_user_review_and_return for super::RemoteProcedu
input: GameDistributionAdminUserReviewInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, GameDistributionAdminUserReviewResult>(
@@ -31,10 +31,10 @@ pub trait get_editor_asset_folder_and_return {
input: EditorAssetFolderGetInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<EditorAssetFolderProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<EditorAssetFolderProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
);
}
@@ -44,10 +44,10 @@ impl get_editor_asset_folder_and_return for super::RemoteProcedures {
input: EditorAssetFolderGetInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<EditorAssetFolderProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<EditorAssetFolderProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, EditorAssetFolderProcedureResult>(
@@ -31,10 +31,10 @@ pub trait get_game_distribution_my_review_and_return {
input: GameDistributionMyReviewInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
);
}
@@ -44,10 +44,10 @@ impl get_game_distribution_my_review_and_return for super::RemoteProcedures {
input: GameDistributionMyReviewInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, GameDistributionUserReviewResult>(
@@ -0,0 +1,62 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::game_distribution_play_count_increment_input_type::GameDistributionPlayCountIncrementInput;
use super::game_distribution_procedure_result_type::GameDistributionProcedureResult;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct IncrementGameDistributionGamePlayCountsAndReturnArgs {
pub input: GameDistributionPlayCountIncrementInput,
}
impl __sdk::InModule for IncrementGameDistributionGamePlayCountsAndReturnArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `increment_game_distribution_game_play_counts_and_return`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait increment_game_distribution_game_play_counts_and_return {
fn increment_game_distribution_game_play_counts_and_return(
&self,
input: GameDistributionPlayCountIncrementInput,
) {
self.increment_game_distribution_game_play_counts_and_return_then(input, |_, _| {});
}
fn increment_game_distribution_game_play_counts_and_return_then(
&self,
input: GameDistributionPlayCountIncrementInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
);
}
impl increment_game_distribution_game_play_counts_and_return for super::RemoteProcedures {
fn increment_game_distribution_game_play_counts_and_return_then(
&self,
input: GameDistributionPlayCountIncrementInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, GameDistributionProcedureResult>(
"increment_game_distribution_game_play_counts_and_return",
IncrementGameDistributionGamePlayCountsAndReturnArgs { input },
__callback,
);
}
}
@@ -34,10 +34,10 @@ pub trait list_admin_game_distribution_user_reviews_and_return {
input: GameDistributionAdminUserReviewListInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
);
}
@@ -47,10 +47,10 @@ impl list_admin_game_distribution_user_reviews_and_return for super::RemoteProce
input: GameDistributionAdminUserReviewListInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, GameDistributionAdminUserReviewResult>(
@@ -28,10 +28,10 @@ pub trait list_agc_tracking_events {
query_json: String,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
);
}
@@ -41,10 +41,10 @@ impl list_agc_tracking_events for super::RemoteProcedures {
query_json: String,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, Result<String, String>>(
@@ -34,10 +34,10 @@ pub trait list_game_distribution_review_games_and_return {
input: GameDistributionReviewGameListInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
);
}
@@ -47,10 +47,10 @@ impl list_game_distribution_review_games_and_return for super::RemoteProcedures
input: GameDistributionReviewGameListInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, GameDistributionAdminUserReviewResult>(
@@ -34,10 +34,10 @@ pub trait list_game_distribution_user_reviews_and_return {
input: GameDistributionUserReviewListInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
);
}
@@ -47,10 +47,10 @@ impl list_game_distribution_user_reviews_and_return for super::RemoteProcedures
input: GameDistributionUserReviewListInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, GameDistributionUserReviewResult>(
@@ -34,10 +34,10 @@ pub trait moderate_game_distribution_user_review_and_return {
input: GameDistributionReviewModerationInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
);
}
@@ -47,10 +47,10 @@ impl moderate_game_distribution_user_review_and_return for super::RemoteProcedur
input: GameDistributionReviewModerationInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionAdminUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, GameDistributionAdminUserReviewResult>(
@@ -31,10 +31,10 @@ pub trait save_game_distribution_my_review_and_return {
input: GameDistributionUserReviewInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
);
}
@@ -44,10 +44,10 @@ impl save_game_distribution_my_review_and_return for super::RemoteProcedures {
input: GameDistributionUserReviewInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<GameDistributionUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<GameDistributionUserReviewResult, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, GameDistributionUserReviewResult>(
@@ -28,10 +28,10 @@ pub trait upload_agc_analytics_batch {
payload_json: String,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
);
}
@@ -41,10 +41,10 @@ impl upload_agc_analytics_batch for super::RemoteProcedures {
payload_json: String,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, Result<String, String>>(
@@ -1062,6 +1062,19 @@ pub struct GameDistributionPublicGameInput {
pub game_id: String,
}
/// 单次游玩计数增量;api-server 已按 `game_id` 聚合,同一输入内不重复。
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
pub struct GameDistributionPlayCountIncrement {
pub game_id: String,
pub delta: u64,
}
/// 一次 flush 的批量游玩计数增量。
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
pub struct GameDistributionPlayCountIncrementInput {
pub increments: Vec<GameDistributionPlayCountIncrement>,
}
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
pub struct GameDistributionOwnerVersionInput {
pub owner_user_id: String,
@@ -1494,6 +1507,34 @@ pub fn restore_game_distribution_game_and_return(
}
}
/// 批量累加已公开游戏的游玩次数。
///
/// 只对当前 `published` 且存在有效 `active_version_id` 的游戏生效,非公开游戏静默跳过;
/// 自增不修改 `updated_at`,避免每次游玩重排作者自有列表。
#[spacetimedb::procedure]
pub fn increment_game_distribution_game_play_counts_and_return(
ctx: &mut ProcedureContext,
input: GameDistributionPlayCountIncrementInput,
) -> GameDistributionProcedureResult {
let caller = ctx.sender();
match ctx.try_with_tx(|tx| {
require_editor_generation_runtime_service_identity(tx, caller)?;
increment_game_distribution_game_play_counts_tx(tx, input.clone())
}) {
Ok(()) => GameDistributionProcedureResult {
ok: true,
replayed: false,
game: None,
games: Vec::new(),
version: None,
versions: Vec::new(),
next_cursor: None,
error_message: None,
},
Err(error) => game_distribution_result_error(error),
}
}
/// 返回作者名下的游戏与最近版本;owner 来自 api-server 的认证主体,调用方不能指定他人。
#[spacetimedb::procedure]
pub fn list_owner_game_distribution_games_and_return(
@@ -2771,6 +2812,36 @@ fn restore_game_distribution_game_tx(
Ok((game_distribution_game_snapshot(&game), false))
}
/// 批量累加游玩次数;只作用于当前公开且有有效公开版本的游戏。
///
/// 同一 `game_id` 的多次增量由 api-server 侧先聚合;这里仍逐条 `find`,对同一行的
/// 多条增量按顺序 `saturating_add`,因此聚合与否都不影响结果。
fn increment_game_distribution_game_play_counts_tx(
ctx: &ReducerContext,
input: GameDistributionPlayCountIncrementInput,
) -> Result<(), String> {
for increment in input.increments {
if increment.delta == 0 {
continue;
}
let game_id = match normalize_game_distribution_optional(increment.game_id) {
Some(game_id) => game_id,
None => continue,
};
let Some(mut game) = ctx.db.game_distribution_game().game_id().find(&game_id) else {
continue;
};
if game.visibility != GAME_DISTRIBUTION_VISIBILITY_PUBLISHED
|| game.active_version_id.is_none()
{
continue;
}
game.play_count = game.play_count.saturating_add(increment.delta);
ctx.db.game_distribution_game().game_id().update(game);
}
Ok(())
}
fn list_admin_game_distribution_games_tx(
ctx: &ReducerContext,
input: GameDistributionAdminGameListInput,