feat(upserver): 保存M2客户端KV迁移至本人统计写检查点

保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。
保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。

验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。
包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
This commit is contained in:
Liubing\LB
2026-09-30 19:19:05 +08:00
parent 1c0cdf7a99
commit 6813da149a
511 changed files with 151858 additions and 2376 deletions

View File

@@ -3,6 +3,8 @@ using System.Threading.Tasks;
using asap.core;
using Game;
using UnityEngine;
using System;
using game.PlayerDataStorage;
namespace game
{
@@ -27,6 +29,61 @@ namespace game
int _unitOfWeight = 1;
bool? _showNotice;
readonly bool requiresFormalKV;
PlayerKVGameSessionHandle promptOwner;
internal PlayerKVEditResult LastPromptEdit { get; private set; }
internal bool HasKVPromptOwner => promptOwner != null;
// Preserve only our key/owner/error metadata across void UI boundaries, never platform payloads.
internal static InvalidOperationException PromptFailure(string message)
{
var error = new InvalidOperationException(message);
error.Data["B01PromptDiagnostic"] = message;
return error;
}
internal static void LogPromptFailure(Exception error) => Debug.LogError(
error.Data["B01PromptDiagnostic"] as string ?? "Notification prompt stopped: " + error.GetType().Name);
public DefaultSettingService() : this(false) { }
internal DefaultSettingService(bool formalKV) { requiresFormalKV = formalKV; }
internal DefaultSettingService(PlayerKVGameSessionHandle owner) : this(true)
{
promptOwner = owner ?? throw new ArgumentNullException(nameof(owner));
RequirePromptOwner();
}
// The frozen startup selection is supplied before login; this instance can never change owners.
internal void PreflightFormalPromptOwner(PlayerKVGameSessionHandle owner)
{
if (!requiresFormalKV || owner == null || (promptOwner != null && !ReferenceEquals(promptOwner, owner)))
throw PromptFailure("ShowNotice: formal owner mismatch.");
var read = KVMgr.ReadKV(owner, "ShowNotice");
if (!read.Success) throw PromptFailure($"ShowNotice owner={owner.OwnerInstanceId}: {read.Error}");
}
internal void BindFormalPromptOwner(PlayerKVGameSessionHandle owner)
{
PreflightFormalPromptOwner(owner);
if (promptOwner == null) promptOwner = owner;
RequirePromptOwner();
}
internal bool HasFormalPromptOwner(PlayerKVGameSessionHandle owner) =>
requiresFormalKV && ReferenceEquals(promptOwner, owner);
internal void RequirePromptOwner()
{
if (promptOwner != null) ReadPrompt();
else if (requiresFormalKV) throw PromptFailure("ShowNotice: formal owner not handed off.");
}
PlayerKVKeyReadResult ReadPrompt()
{
if (LastPromptEdit != null && !LastPromptEdit.Accepted)
throw PromptFailure($"ShowNotice owner={promptOwner.OwnerInstanceId}: {LastPromptEdit.StateResult.Error}; uncertain={LastPromptEdit.StateResult.LocalCommitMayHaveSucceeded}");
var read = KVMgr.ReadKV(promptOwner, "ShowNotice");
if (!read.Success)
throw PromptFailure($"ShowNotice owner={promptOwner.OwnerInstanceId}: {read.Error}");
return read;
}
public bool Music
{
set
@@ -105,11 +162,33 @@ namespace game
{
get
{
if (requiresFormalKV && promptOwner == null) RequirePromptOwner();
if (promptOwner != null)
{
var read = ReadPrompt();
_showNotice = read.Found == true && read.Value == "1";
return _showNotice.Value;
}
_showNotice ??= PlayFabMgr.Instance.GetLocalData("ShowNotice") == "1";
return _showNotice.Value;
}
set
{
if (requiresFormalKV && promptOwner == null) RequirePromptOwner();
if (promptOwner != null)
{
var read = ReadPrompt();
var serialized = value ? "1" : "0";
if (read.Found != true || read.Value != serialized)
{
LastPromptEdit = KVMgr.WriteKV(promptOwner, "ShowNotice", serialized);
if (!LastPromptEdit.Accepted)
throw PromptFailure($"ShowNotice owner={promptOwner.OwnerInstanceId}: {LastPromptEdit.StateResult.Error}; uncertain={LastPromptEdit.StateResult.LocalCommitMayHaveSucceeded}");
}
RequirePromptOwner();
_showNotice = value;
return;
}
if (value != _showNotice)
{
_showNotice = value;
@@ -156,7 +235,9 @@ namespace game
{
return false;
}
if (GContext.container.Resolve<ILocalNotificationService>().IsNoticeOpen)
var notification = GContext.container.Resolve<ILocalNotificationService>();
if (notification is LocalNotificationService local) local.RequirePromptOwner(this);
if (notification.IsNoticeOpen)
{
ShowNotice = true;
return false;