test(upserver): 归档存档迁移与玩法闭环回归工具
This commit is contained in:
341
CodexOutput/upserver/ReviewLifecycleTests/Generated.cs
Normal file
341
CodexOutput/upserver/ReviewLifecycleTests/Generated.cs
Normal file
@@ -0,0 +1,341 @@
|
||||
using System;using System.Linq;using System.Collections.Generic;using System.Threading;using System.Threading.Tasks;
|
||||
public partial class GetUiSubject {
|
||||
public async Task GetUI()
|
||||
{
|
||||
if (this == null) return;
|
||||
var container = GContext.container;
|
||||
var loginLifetime = container.Resolve<IUserService>().LoginLifetime;
|
||||
HomePanel homePanel = null;
|
||||
if (!fishingData.isMapBuff && !IsHideHomeMap)
|
||||
{
|
||||
var go = await UIManager.Instance.RestartShowUI(UITypes.HomePanel);
|
||||
if (this == null || loginLifetime.IsCancellationRequested || !ReferenceEquals(container, GContext.container)) return;
|
||||
homePanel = go.GetComponent<HomePanel>();
|
||||
}
|
||||
//实例
|
||||
container.Resolve<IDeferredRewardStashService>();
|
||||
//这里确认补单
|
||||
await container.Resolve<PlayerShopData>().ReleaseReplenishment();
|
||||
if (this == null || loginLifetime.IsCancellationRequested || !ReferenceEquals(container, GContext.container)) return;
|
||||
await Awaiters.SecondsRealtime(0.5f);
|
||||
if (this == null || loginLifetime.IsCancellationRequested || !ReferenceEquals(container, GContext.container)) return;
|
||||
container.Resolve<FishingEventData>().ShowRankTierChange();
|
||||
if (fishingData.isMapBuff)
|
||||
{
|
||||
fishingData.isMapBuff = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
fishingBehavior?.RodBehaviour.gameObject.SetActive(true);
|
||||
fishingBehavior.cameraAnimator.speed = 1;
|
||||
GContext.Publish(new ShowHandEvent() { isShow = true });
|
||||
SetMapShow(!IsHideHomeMap);
|
||||
if (IsHideHomeMap)
|
||||
{
|
||||
IsHideHomeMap = false;
|
||||
}
|
||||
else if (homePanel != null)
|
||||
{
|
||||
homePanel.OnStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
public async Task NewEnterMap(PlayableDirector camera_timeline)
|
||||
{
|
||||
if (this == null) return;
|
||||
var container = GContext.container;
|
||||
var loginLifetime = container.Resolve<IUserService>().LoginLifetime;
|
||||
camera_timeline.gameObject.SetActive(true);
|
||||
fishingBehavior.cameraAnimator.enabled = false;
|
||||
fishingBehavior.cameraAnimator.transform.localEulerAngles = Vector3.zero;
|
||||
fishingBehavior.cameraAnimator.transform.localPosition = Vector3.zero;
|
||||
await Awaiters.Seconds((float)camera_timeline.duration);
|
||||
if (this == null || loginLifetime.IsCancellationRequested || !ReferenceEquals(container, GContext.container)) return;
|
||||
camera_timeline.gameObject.SetActive(false);
|
||||
fishingData.mainCamera.transform.localEulerAngles = Vector3.zero;
|
||||
fishingData.mainCamera.transform.localPosition = Vector3.zero;
|
||||
fishingBehavior.cameraAnimator.enabled = true;
|
||||
GContext.Publish(new EventBGMSound(_curMapData.BGM));
|
||||
fishingBehavior.cameraAnimator.Play("EnterMap", -1, 0);
|
||||
await OnCameraSpeedEvent();
|
||||
if (this == null || loginLifetime.IsCancellationRequested || !ReferenceEquals(container, GContext.container)) return;
|
||||
var go = await UIManager.Instance.RestartShowUI(UITypes.HomePanel);
|
||||
if (this == null || loginLifetime.IsCancellationRequested || !ReferenceEquals(container, GContext.container)) return;
|
||||
HomePanel homePanel = go.GetComponent<HomePanel>();
|
||||
homePanel.OnStart();
|
||||
}
|
||||
public async Task OnCameraSpeedEvent()
|
||||
{
|
||||
if (this == null) return;
|
||||
var container = GContext.container;
|
||||
var loginLifetime = container.Resolve<IUserService>().LoginLifetime;
|
||||
fishingBehavior.cameraAnimator.speed = 1;
|
||||
await Awaiters.Seconds(fishingData.fishingBehaviorConf.rodEnterMapDelay);
|
||||
if (this == null || loginLifetime.IsCancellationRequested || !ReferenceEquals(container, GContext.container)) return;
|
||||
fishingBehavior?.RodBehaviour.gameObject.SetActive(true);
|
||||
GContext.Publish(new ShowHandEvent() { isShow = true });
|
||||
}
|
||||
}
|
||||
public partial class ManagerSubject {
|
||||
public async Task Init(System.Action<float> progress)
|
||||
{
|
||||
if (residentClip != null || disposables != null) Release();
|
||||
var version = ++initializationVersion;
|
||||
var container = GContext.container;
|
||||
_tables = container.Resolve<cfg.Tables>();
|
||||
var opMap = new Dictionary<string, AsyncOperationHandle<AudioClip>>();
|
||||
CompositeDisposable pendingSubscriptions = null;
|
||||
bool transferred = false;
|
||||
try
|
||||
{
|
||||
await soundService.InitAsync();
|
||||
if (version != initializationVersion || !ReferenceEquals(container, GContext.container)) return;
|
||||
foreach (var addr in residentFish)
|
||||
{
|
||||
if (!opMap.ContainsKey(addr)) opMap.Add(addr, Addressables.LoadAssetAsync<AudioClip>(addr));
|
||||
}
|
||||
var tasks = opMap.Values.Select(_ => _.Task).ToArray();
|
||||
var total = (float)tasks.Length;
|
||||
while (true)
|
||||
{
|
||||
var completed = tasks.Count(_ => _.IsCompleted);
|
||||
if (completed == total)
|
||||
{
|
||||
progress.Invoke(1f);
|
||||
break;
|
||||
}
|
||||
progress.Invoke(completed / total);
|
||||
await Awaiters.NextFrame;
|
||||
if (version != initializationVersion || !ReferenceEquals(container, GContext.container)) return;
|
||||
}
|
||||
if (version != initializationVersion || !ReferenceEquals(container, GContext.container)) return;
|
||||
var loadedClips = opMap.ToDictionary(_ => _.Key, _ => _.Value.Result);
|
||||
pendingSubscriptions = new CompositeDisposable();
|
||||
GContext.OnEvent<EventUISound>().Subscribe(PlaySound).AddTo(pendingSubscriptions);
|
||||
GContext.OnEvent<EventRewardSound>().Subscribe(PlayRewardSound).AddTo(pendingSubscriptions);
|
||||
GContext.OnEvent<EventFishingSound>().Subscribe(PlayFishingSound).AddTo(pendingSubscriptions);
|
||||
GContext.OnEvent<EventBGMSound>().Subscribe(PlayMusic).AddTo(pendingSubscriptions);
|
||||
GContext.OnEvent<FadeOutEvent>().Subscribe(FadeOutEvent).AddTo(pendingSubscriptions);
|
||||
GContext.OnEvent<AssetReferenceAudioClip>().Subscribe(PlaySoundByReference).AddTo(pendingSubscriptions);
|
||||
GContext.OnEvent<AssetReferenceAudioVoiceClip>().Subscribe(PlayVoiceSoundByReference).AddTo(pendingSubscriptions);
|
||||
|
||||
GContext.OnEvent<ChangeRodDataEvent>().Subscribe(OnChangeRodData).AddTo(pendingSubscriptions);
|
||||
GContext.OnEvent<SoundMuteEvent>().Subscribe(OnSoundMute).AddTo(pendingSubscriptions);
|
||||
GContext.OnEvent<OnSoundStateEvent>().Subscribe(OnSoundStateEvent).AddTo(pendingSubscriptions);
|
||||
soundService.BGMVol = container.Resolve<ISettingService>().Music ? 80 : 0;
|
||||
soundService.SFXVol = container.Resolve<ISettingService>().Sound ? 80 : 0;
|
||||
soundService.VoiceVol = container.Resolve<ISettingService>().Sound ? 80 : 0;
|
||||
residentClip = loadedClips;
|
||||
disposables = pendingSubscriptions;
|
||||
transferred = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!transferred)
|
||||
{
|
||||
pendingSubscriptions?.Dispose();
|
||||
if (version == initializationVersion) Release();
|
||||
foreach (var handle in opMap.Values)
|
||||
if (handle.IsValid()) Addressables.Release(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void Release()
|
||||
{
|
||||
initializationVersion++;
|
||||
if (disposables != null)
|
||||
{
|
||||
disposables.Dispose();
|
||||
disposables = null;
|
||||
}
|
||||
|
||||
if (residentClip != null)
|
||||
{
|
||||
foreach (var item in residentClip)
|
||||
{
|
||||
if (item.Value != null)
|
||||
{
|
||||
Addressables.Release(item.Value);
|
||||
}
|
||||
}
|
||||
residentClip.Clear();
|
||||
}
|
||||
|
||||
if (rewardSoundDic != null)
|
||||
{
|
||||
rewardSoundDic.Clear();
|
||||
}
|
||||
|
||||
if (soundService != null)
|
||||
{
|
||||
foreach (var s in _activeFishingLoopSounds.ToList())
|
||||
{
|
||||
if (s != null)
|
||||
{
|
||||
s.audioSource.Stop();
|
||||
soundService.ReturnSound(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
_activeFishingLoopSounds.Clear();
|
||||
|
||||
if (soundService != null)
|
||||
{
|
||||
soundService.Release();
|
||||
}
|
||||
|
||||
if (BGM_Clip != null)
|
||||
{
|
||||
Addressables.Release(BGM_Clip);
|
||||
BGM_Clip = null;
|
||||
}
|
||||
|
||||
if (BGM_Sound != null && BGM_Sound.gameObject != null)
|
||||
{
|
||||
GameObject.Destroy(BGM_Sound.gameObject);
|
||||
BGM_Sound = null;
|
||||
}
|
||||
|
||||
BGMName = null;
|
||||
}
|
||||
}
|
||||
public partial class ServiceSubject {
|
||||
public async Task InitAsync(string mixerAddr = "AudioMixer", string soundPrefabAddr = "sound",
|
||||
string bgmGroupId = "BGM", string fishingGroupId = "Fishing", string uiGroupId = "UI", string voiceGroupId = "Voice")
|
||||
{
|
||||
if (mixerHandle.IsValid() || soundPrefabHandle.IsValid() || lowpassEvtDisposable != null) Release();
|
||||
var version = ++initializationVersion;
|
||||
SoundFadeTaskDic = new Dictionary<Sound, CancellationTokenSource>();
|
||||
AsyncOperationHandle<AudioMixer> pendingMixer = default;
|
||||
AsyncOperationHandle<GameObject> pendingPrefab = default;
|
||||
IDisposable pendingSubscription = null;
|
||||
bool transferred = false;
|
||||
try
|
||||
{
|
||||
pendingMixer = Addressables.LoadAssetAsync<AudioMixer>(mixerAddr);
|
||||
var loadedMixer = await pendingMixer.Task;
|
||||
if (version != initializationVersion) return;
|
||||
mixer = loadedMixer;
|
||||
Assert.IsNotNull(mixer);
|
||||
mixer.GetFloat(Bgm_Vol_Param_Name, out bgmVol);
|
||||
mixer.GetFloat(Fishing_Vol_Param_Name, out fishingVol);
|
||||
|
||||
bgmGroup = FindMixerGroup(bgmGroupId);
|
||||
fishingGroup = FindMixerGroup(fishingGroupId);
|
||||
uiGroup = FindMixerGroup(uiGroupId);
|
||||
voiceGroup = FindMixerGroup(voiceGroupId);
|
||||
normal = mixer.FindSnapshot("Snapshot");
|
||||
Assert.IsNotNull(bgmGroup);
|
||||
Assert.IsNotNull(fishingGroup);
|
||||
Assert.IsNotNull(uiGroup);
|
||||
|
||||
pendingPrefab = Addressables.LoadAssetAsync<GameObject>(soundPrefabAddr);
|
||||
var soundPrefab = await pendingPrefab.Task;
|
||||
if (version != initializationVersion) return;
|
||||
Assert.IsNotNull(soundPrefab);
|
||||
var sound = soundPrefab.GetComponent<Sound>();
|
||||
Assert.IsNotNull(sound);
|
||||
|
||||
poolService.CreatePool<Sound>(sound, 10, 50);
|
||||
pendingSubscription = GContext.OnEvent<LowpassEffectEvt>().Subscribe(OnLowpassEffectEvt);
|
||||
mixerHandle = pendingMixer;
|
||||
soundPrefabHandle = pendingPrefab;
|
||||
lowpassEvtDisposable = pendingSubscription;
|
||||
transferred = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!transferred)
|
||||
{
|
||||
pendingSubscription?.Dispose();
|
||||
if (version == initializationVersion) Release();
|
||||
if (pendingPrefab.IsValid()) Addressables.Release(pendingPrefab);
|
||||
if (pendingMixer.IsValid()) Addressables.Release(pendingMixer);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void Release()
|
||||
{
|
||||
initializationVersion++;
|
||||
if (SoundFadeTaskDic != null)
|
||||
{
|
||||
foreach (var source in SoundFadeTaskDic.Values)
|
||||
{
|
||||
source.Cancel();
|
||||
}
|
||||
SoundFadeTaskDic.Clear();
|
||||
SoundFadeTaskDic = null;
|
||||
}
|
||||
|
||||
poolService?.DestroyPool<Sound>();
|
||||
if (mixerHandle.IsValid()) Addressables.Release(mixerHandle);
|
||||
mixerHandle = default;
|
||||
if (soundPrefabHandle.IsValid()) Addressables.Release(soundPrefabHandle);
|
||||
soundPrefabHandle = default;
|
||||
|
||||
lowpassEvtDisposable?.Dispose();
|
||||
lowpassEvtDisposable = null;
|
||||
}
|
||||
}
|
||||
public partial class StageSubject : StageBase {
|
||||
public async Task RunPrefix()
|
||||
{
|
||||
if (stopping || this == null) return;
|
||||
var loginLifetime = UserService.LoginLifetime;
|
||||
LoadingScreen.SetProgress(0.54f);
|
||||
await Awaiters.NextFrame;
|
||||
if (stopping || this == null || loginLifetime.IsCancellationRequested) return;
|
||||
|
||||
// wait authorize , 5 sec max
|
||||
var checkAuthorTimes = 0;
|
||||
while (checkAuthorTimes < 10 && !UserService.IsAuthorized)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
if (stopping || this == null || loginLifetime.IsCancellationRequested) return;
|
||||
checkAuthorTimes++;
|
||||
}
|
||||
|
||||
if (!UserService.IsAuthorized)
|
||||
{
|
||||
Debug.LogWarning("[FishingStage::startAsync] Api Svr not author");
|
||||
}
|
||||
|
||||
if (GContext.container.Resolve<IConfig>().Get("DEBUG", "0") == "1")
|
||||
{
|
||||
var console = await UnityEngine.AddressableAssets.Addressables.InstantiateAsync("IngameDebugConsole").Task;
|
||||
if (stopping || this == null || loginLifetime.IsCancellationRequested)
|
||||
{
|
||||
if (console != null) UnityEngine.AddressableAssets.Addressables.ReleaseInstance(console);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await GContext.container.Resolve<SoundMgr>().Init((progress) =>
|
||||
{
|
||||
if (!stopping && this != null && !loginLifetime.IsCancellationRequested) LoadingScreen.SetProgress(0.55f + progress * 0.2f);
|
||||
});
|
||||
if (stopping || this == null || loginLifetime.IsCancellationRequested) return;
|
||||
Applied=true; }
|
||||
public override async Task Stop()
|
||||
{
|
||||
stopping = true;
|
||||
GContext.container.Unregister<IStageData>();
|
||||
GContext.container.Unregister<SurveyService>();
|
||||
GContext.container.Resolve<IDeferredRewardStashService>()?.DisposeSubscriptions();
|
||||
GContext.container.Unregister<IDeferredRewardStashService>();
|
||||
disposable?.Dispose();
|
||||
disposable = null;
|
||||
_autoRestart?.Dispose();
|
||||
_autoRestart = null;
|
||||
await base.Stop();
|
||||
}
|
||||
protected override void OnDestroy() { stopping = true; }
|
||||
public async Task RunCatch(){ var loginLifetime=UserService.LoginLifetime;
|
||||
try { await base.StartAsync(); }
|
||||
catch (System.Exception ex) when (stopping || this == null || loginLifetime.IsCancellationRequested)
|
||||
{
|
||||
Debug.LogWarning($"[FishingStage] Startup stopped, cancelled or stage destroyed: {ex.GetType().Name}: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
if (stopping || this == null || loginLifetime.IsCancellationRequested) return;
|
||||
CatchCompleted=true; }
|
||||
}
|
||||
43
CodexOutput/upserver/ReviewLifecycleTests/Program.cs
Normal file
43
CodexOutput/upserver/ReviewLifecycleTests/Program.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.Text.Json;
|
||||
var mode=args.Contains("--normal")?"normal":args.Contains("--regression")?"regression":args.Contains("--stage")?"stage":"extended";
|
||||
var results=new List<object>();var failures=0;
|
||||
bool NoLeaks()=>Addressables.All.All(o=>!o.Valid&&o.Releases==1);
|
||||
async Task Case(string name,Func<Task<bool>> body){Probe.Reset();bool ok=false;string error=null;try{ok=await body();}catch(Exception ex){error=ex.ToString();}if(!ok)failures++;var observation=new{Probe.RankCalls,Probe.HomeCalls,Probe.CameraWrites,Probe.UiCalls,Probe.Published,Probe.PoolCreates,Probe.Subscriptions,Probe.VolumeAfterRelease,refs=Addressables.All.Select(o=>new{o.Key,o.Valid,o.Releases}).ToArray(),error};results.Add(new{name,passed=ok,observation});Console.WriteLine($"{(ok?"PASS":"FAIL")} {name}: {JsonSerializer.Serialize(observation)}");}
|
||||
void Invalidate(GetUiSubject s,string how){if(how=="destroy")s.Destroyed=true;else if(how=="cancel")GContext.container.Resolve<IUserService>().Lifetime.Cancel();else GContext.container=new();}
|
||||
void ClipsReady(){foreach(var op in Addressables.All.OfType<Operation<AudioClip>>().Where(o=>o.Valid&&!o.Completion.Task.IsCompleted))op.Completion.SetResult(new());}
|
||||
async Task ServiceReady(ServiceSubject s){var task=s.InitAsync();Addressables.Complete("AudioMixer",new AudioMixer());Addressables.Complete("sound",new GameObject());await task;}
|
||||
async Task<bool> Fails(Task t){try{await t;return false;}catch{return true;}}
|
||||
if(mode=="normal"){
|
||||
await Case("GetUI normal",async()=>{var s=new GetUiSubject();UIManager.Instance.Ready.SetResult(new());var t=s.GetUI();Probe.Delay.SetResult(true);await t;return Probe.RankCalls==1&&Probe.HomeCalls==1&&Probe.CameraWrites==1;});
|
||||
await Case("SoundMgr normal Init/Release",async()=>{var s=new ManagerSubject();var t=s.Init(_=>{});ClipsReady();Probe.Frame.SetResult(true);await t;var count=Probe.Subscriptions;s.Release();return count==10&&Probe.Subscriptions==0&&Probe.VolumeAfterRelease==0&&NoLeaks();});
|
||||
foreach(var name in new[]{"mixer","prefab"})await Case("SoundService normal "+name,async()=>{var s=new ServiceSubject();await ServiceReady(s);var count=Probe.PoolCreates;s.Release();s.Release();return count==1&&Probe.Subscriptions==0&&NoLeaks();});
|
||||
}else if(mode=="regression"){
|
||||
await Case("GetUI exits during delay",async()=>{var s=new GetUiSubject();UIManager.Instance.Ready.SetResult(new());var t=s.GetUI();s.Destroyed=true;GContext.container=new();Probe.Delay.SetResult(true);await t;return Probe.RankCalls==0&&Probe.HomeCalls==0&&Probe.CameraWrites==0;});
|
||||
await Case("SoundMgr pending Init then Release",async()=>{var s=new ManagerSubject();var t=s.Init(_=>{});s.Release();GContext.container=new();ClipsReady();Probe.Frame.SetResult(true);await t;return Probe.Subscriptions==0&&Probe.VolumeAfterRelease==0&&(s.residentClip==null||s.residentClip.Count==0)&&NoLeaks();});
|
||||
foreach(var boundary in new[]{"mixer","prefab"})await Case("SoundService Release during "+boundary,async()=>{var s=new ServiceSubject();var t=s.InitAsync();if(boundary=="prefab")Addressables.Complete("AudioMixer",new AudioMixer());s.Release();if(boundary=="mixer")Addressables.Complete("AudioMixer",new AudioMixer());else Addressables.Complete("sound",new GameObject());await t;return Probe.PoolCreates==0&&Probe.Subscriptions==0&&NoLeaks();});
|
||||
}else if(mode=="stage"){
|
||||
await Case("Stage normal prefix",async()=>{var s=new StageSubject();var t=s.RunPrefix();Probe.Frame.SetResult(true);GContext.container.Resolve<SoundMgr>().Ready.SetResult(true);await t;return s.Applied;});
|
||||
await Case("Stage stopped before Start, alive uncancelled",async()=>{var s=new StageSubject();await s.Stop();await s.RunPrefix();return !s.Applied&&!s.Destroyed&&!s.UserService.LoginLifetime.IsCancellationRequested;});
|
||||
await Case("Stage Stop first statement blocks pending NextFrame",async()=>{var s=new StageSubject{StopReady=new()};var t=s.RunPrefix();var stop=s.Stop();Probe.Frame.SetResult(true);await t;var blocked=!s.Applied&&!s.Destroyed&&!s.UserService.LoginLifetime.IsCancellationRequested&&!stop.IsCompleted;s.StopReady.SetResult(true);await stop;return blocked;});
|
||||
await Case("Stage Stop during SoundMgr wait and stale progress",async()=>{var s=new StageSubject();var t=s.RunPrefix();Probe.Frame.SetResult(true);var sound=GContext.container.Resolve<SoundMgr>();await s.Stop();var writes=LoadingScreen.Writes;sound.Progress(.5f);sound.Ready.SetResult(true);await t;await s.Stop();return !s.Applied&&!s.Destroyed&&!s.UserService.LoginLifetime.IsCancellationRequested&&LoadingScreen.Writes==writes;});
|
||||
await Case("Stage OnDestroy invalidates pending initialization",async()=>{var s=new StageSubject();var t=s.RunPrefix();s.DestroyStage();Probe.Frame.SetResult(true);await t;return !s.Applied;});
|
||||
foreach(var stopped in new[]{false,true})await Case("Stage base startup failure, stopped="+stopped,async()=>{var s=new StageSubject();var t=s.RunCatch();if(stopped)await s.Stop();var warnings=Debug.Warnings;s.StartReady.SetException(new Exception("base startup failure"));var failed=await Fails(t);return stopped?!failed&&!s.CatchCompleted&&Debug.Warnings==warnings+1:failed&&!s.CatchCompleted&&Debug.Warnings==warnings;});
|
||||
foreach(var stopped in new[]{false,true})await Case("Stage base startup completion, stopped="+stopped,async()=>{var s=new StageSubject();var t=s.RunCatch();if(stopped)await s.Stop();s.StartReady.SetResult(true);await t;return s.CatchCompleted==!stopped;});
|
||||
}else{
|
||||
foreach(var boundary in new[]{"ui","replenishment","delay"})foreach(var how in new[]{"destroy","cancel","container"})await Case("GetUI "+boundary+"/"+how,async()=>{var s=new GetUiSubject();var shop=GContext.container.Resolve<PlayerShopData>();shop.Pending=new();if(boundary!="ui")UIManager.Instance.Ready.SetResult(new());if(boundary=="delay")shop.Pending.SetResult(true);var t=s.GetUI();Invalidate(s,how);if(boundary=="ui")UIManager.Instance.Ready.SetResult(new());else if(boundary=="replenishment")shop.Pending.SetResult(true);else Probe.Delay.SetResult(true);await t;return Probe.RankCalls==0&&Probe.HomeCalls==0&&Probe.CameraWrites==0;});
|
||||
foreach(var boundary in new[]{"timeline","rod","ui"})foreach(var how in new[]{"destroy","cancel","container"})await Case("NewEnterMap "+boundary+"/"+how,async()=>{var s=new GetUiSubject();var t=s.NewEnterMap(new());if(boundary!="timeline")Probe.Seconds[0].SetResult(true);if(boundary=="ui")Probe.Seconds[1].SetResult(true);var camera=Probe.CameraWrites;var published=Probe.Published;var active=s.fishingBehavior.RodBehaviour.gameObject.ActiveWrites;Invalidate(s,how);if(boundary=="timeline")Probe.Seconds[0].SetResult(true);else if(boundary=="rod")Probe.Seconds[1].SetResult(true);else UIManager.Instance.Ready.SetResult(new());await t;return Probe.HomeCalls==0&&Probe.CameraWrites==camera&&Probe.Published==published&&s.fishingBehavior.RodBehaviour.gameObject.ActiveWrites==active;});
|
||||
foreach(var how in new[]{"destroy","cancel","container"})await Case("OnCameraSpeedEvent "+how,async()=>{var s=new GetUiSubject();var t=s.OnCameraSpeedEvent();Invalidate(s,how);Probe.Seconds[0].SetResult(true);await t;return Probe.Published==0&&s.fishingBehavior.RodBehaviour.gameObject.ActiveWrites==0;});
|
||||
await Case("NewEnterMap normal",async()=>{var s=new GetUiSubject();var t=s.NewEnterMap(new());Probe.Seconds[0].SetResult(true);Probe.Seconds[1].SetResult(true);UIManager.Instance.Ready.SetResult(new());await t;return Probe.HomeCalls==1&&Probe.Published==2&&s.fishingBehavior.RodBehaviour.gameObject.ActiveWrites==1;});
|
||||
await Case("GetUI entry already destroyed",async()=>{var s=new GetUiSubject{Destroyed=true};await s.GetUI();return Probe.UiCalls==0;});
|
||||
foreach(var how in new[]{"release","container"})await Case("SoundMgr bottom service wait/"+how,async()=>{var s=new ManagerSubject();s.soundService.Pending=new();var t=s.Init(_=>{});if(how=="release")s.Release();else GContext.container=new();s.soundService.Pending.SetResult(true);await t;return Probe.Subscriptions==0&&Addressables.All.Count==0&&s.soundService.Released;});
|
||||
await Case("SoundMgr frame/container",async()=>{var s=new ManagerSubject();var t=s.Init(_=>{});GContext.container=new();Probe.Frame.SetResult(true);await t;return Probe.Subscriptions==0&&NoLeaks();});
|
||||
foreach(var failure in new[]{"clip","subscription","volume"})await Case("SoundMgr failure rollback/"+failure,async()=>{var s=new ManagerSubject();var t=s.Init(_=>{});if(failure=="clip")Addressables.Get<AudioClip>("clip-a").Completion.SetException(new Exception("clip failure"));else if(failure=="subscription")Probe.ThrowSubscriptionAt=3;else Probe.ThrowVolume=true;ClipsReady();Probe.Frame.SetResult(true);var failed=await Fails(t);return failed&&Probe.Subscriptions==0&&s.soundService.Released&&NoLeaks();});
|
||||
await Case("SoundMgr duplicate address + repeated Release",async()=>{var s=new ManagerSubject{residentFish=new[]{"one","one","two"}};var t=s.Init(_=>{});ClipsReady();Probe.Frame.SetResult(true);await t;var n=Addressables.All.Count;s.Release();s.Release();return n==2&&Probe.Subscriptions==0&&NoLeaks();});
|
||||
await Case("SoundMgr repeat successful Init",async()=>{var s=new ManagerSubject();var a=s.Init(_=>{});ClipsReady();Probe.Frame.SetResult(true);await a;Probe.Frame=new();var b=s.Init(_=>{});ClipsReady();Probe.Frame.SetResult(true);await b;var subs=Probe.Subscriptions;s.Release();return subs==10&&Probe.Subscriptions==0&&NoLeaks();});
|
||||
await Case("SoundMgr superseded audio wait",async()=>{var s=new ManagerSubject();var a=s.Init(_=>{});var old=Addressables.All.ToArray();var b=s.Init(_=>{});ClipsReady();Probe.Frame.SetResult(true);await Task.WhenAll(a,b);var oldReleased=old.All(x=>!x.Valid&&x.Releases==1);var subs=Probe.Subscriptions;var current=Addressables.All.Except(old).All(x=>x.Valid&&x.Releases==0);s.Release();return oldReleased&¤t&&subs==10&&Probe.Subscriptions==0&&NoLeaks();});
|
||||
foreach(var boundary in new[]{"mixer","prefab"})await Case("SoundService failure/"+boundary,async()=>{var s=new ServiceSubject();var t=s.InitAsync();if(boundary=="mixer")Addressables.Get<AudioMixer>("AudioMixer").Completion.SetException(new Exception("mixer fail"));else{Addressables.Complete("AudioMixer",new AudioMixer());Addressables.Get<GameObject>("sound").Completion.SetException(new Exception("prefab fail"));}var failed=await Fails(t);return failed&&Probe.Subscriptions==0&&s.poolService.Active==0&&NoLeaks();});
|
||||
foreach(var failure in new[]{"pool","subscription"})await Case("SoundService failure rollback/"+failure,async()=>{var s=new ServiceSubject();var t=s.InitAsync();Addressables.Complete("AudioMixer",new AudioMixer());if(failure=="pool")Probe.ThrowPool=true;else Probe.ThrowSubscriptionAt=1;Addressables.Complete("sound",new GameObject());var failed=await Fails(t);return failed&&Probe.Subscriptions==0&&s.poolService.Active==0&&NoLeaks();});
|
||||
await Case("SoundService repeated successful Init/Release",async()=>{var s=new ServiceSubject();await ServiceReady(s);await ServiceReady(s);var sub=Probe.Subscriptions;s.Release();s.Release();return sub==1&&Probe.Subscriptions==0&&s.poolService.Active==0&&NoLeaks();});
|
||||
foreach(var boundary in new[]{"mixer","prefab"})foreach(var oldFails in new[]{false,true})await Case("SoundService old "+boundary+" completes after new/fault="+oldFails,async()=>{var s=new ServiceSubject();var a=s.InitAsync();var oldMixer=Addressables.Get<AudioMixer>("AudioMixer");Operation<GameObject> oldPrefab=null;if(boundary=="prefab"){oldMixer.Completion.SetResult(new());oldPrefab=Addressables.Get<GameObject>("sound");}await ServiceReady(s);var fresh=Addressables.All.Where(o=>!ReferenceEquals(o,oldMixer)&&!ReferenceEquals(o,oldPrefab)).ToArray();if(boundary=="mixer"){if(oldFails)oldMixer.Completion.SetException(new Exception("old fail"));else oldMixer.Completion.SetResult(new());}else{if(oldFails)oldPrefab.Completion.SetException(new Exception("old fail"));else oldPrefab.Completion.SetResult(new());}if(oldFails){if(!await Fails(a))return false;}else await a;var isolated=fresh.All(o=>o.Valid&&o.Releases==0)&&Probe.Subscriptions==1&&s.poolService.Active==1;s.Release();return isolated&&Probe.Subscriptions==0&&NoLeaks();});
|
||||
}
|
||||
File.WriteAllText(Path.Combine(AppContext.BaseDirectory,"../../../"+$"fixed-{mode}-result.json"),JsonSerializer.Serialize(new{mode,failures,results},new JsonSerializerOptions{WriteIndented=true}));return failures==0?0:1;
|
||||
@@ -0,0 +1 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><RestoreConfigFile>$(MSBuildThisFileDirectory)../MinimalLoginTests/NuGet.Config</RestoreConfigFile></PropertyGroup><ItemGroup><Compile Remove="baseline/**/*.cs" /></ItemGroup></Project>
|
||||
85
CodexOutput/upserver/ReviewLifecycleTests/Stubs.cs
Normal file
85
CodexOutput/upserver/ReviewLifecycleTests/Stubs.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// Dependency substitutes only. Production method bodies are in Generated.cs.
|
||||
public static class Probe {
|
||||
public static int RankCalls,HomeCalls,CameraWrites,VolumeAfterRelease,PoolCreates,Subscriptions,ReleasedHandles,UiCalls,Published,SubscriptionsAttempt,ThrowSubscriptionAt;
|
||||
public static bool ThrowVolume,ThrowPool;
|
||||
public static List<TaskCompletionSource<bool>> Seconds=new();
|
||||
public static TaskCompletionSource<bool> Frame,Delay;
|
||||
public static void Reset(){RankCalls=HomeCalls=CameraWrites=VolumeAfterRelease=PoolCreates=Subscriptions=ReleasedHandles=UiCalls=Published=SubscriptionsAttempt=ThrowSubscriptionAt=0;ThrowVolume=ThrowPool=false;Seconds.Clear();Frame=new();Delay=new();Addressables.Pending.Clear();Addressables.All.Clear();GContext.container=new();UIManager.Instance=new();}
|
||||
}
|
||||
public static class Awaiters {public static Task NextFrame=>Probe.Frame.Task;public static Task SecondsRealtime(float _) => Probe.Delay.Task;public static Task Seconds(float _){var t=new TaskCompletionSource<bool>();Probe.Seconds.Add(t);return t.Task;}}
|
||||
public static class GContext {public static Container container;public static void Publish<T>(T _){Probe.Published++;} public static EventStream<T> OnEvent<T>()=>new();}
|
||||
public class Container {public Dictionary<Type,object> Values=new();public void Unregister<T>(){Values.Remove(typeof(T));} public T Resolve<T>(){if(Values.TryGetValue(typeof(T),out var v))return (T)v; var a=Activator.CreateInstance<T>();Values[typeof(T)]=a;return a;}}
|
||||
public class EventStream<T>{public IDisposable Subscribe(Action<T> action){Probe.SubscriptionsAttempt++;if(Probe.ThrowSubscriptionAt>0&&Probe.SubscriptionsAttempt==Probe.ThrowSubscriptionAt)throw new Exception("subscription failure");Probe.Subscriptions++;return new Subscription();}}
|
||||
public class Subscription:IDisposable{bool disposed;public void Dispose(){if(disposed)return;disposed=true;Probe.Subscriptions--;}}
|
||||
public class CompositeDisposable:IDisposable{public List<IDisposable> Items=new();public void Dispose(){foreach(var x in Items)x.Dispose();Items.Clear();}}
|
||||
public static class RxExtensions{public static void AddTo(this IDisposable d,CompositeDisposable c)=>c.Items.Add(d);}
|
||||
public class GameObject {public bool Destroyed;public Transform transform=new();public int ActiveWrites;public void SetActive(bool _){ActiveWrites++;}public T GetComponent<T>() where T:new()=>new();public static void Destroy(GameObject g){if(g!=null)g.Destroyed=true;}}
|
||||
public class HomePanel {public void OnStart()=>Probe.HomeCalls++;}
|
||||
public class UIManager {public static UIManager Instance;public TaskCompletionSource<GameObject> Ready=new();public Task<GameObject> RestartShowUI(object _) {Probe.UiCalls++;return Ready.Task;}}
|
||||
public static class UITypes {public static object HomePanel=new();}
|
||||
public class IDeferredRewardStashService{public void DisposeSubscriptions(){}}
|
||||
public class PlayerShopData{public TaskCompletionSource<bool> Pending;public Task ReleaseReplenishment()=>Pending?.Task??Task.CompletedTask;}
|
||||
public class FishingEventData{public void ShowRankTierChange()=>Probe.RankCalls++;}
|
||||
public class FishingData{public bool isMapBuff;public GameObject mainCamera=new();public BehaviorConf fishingBehaviorConf=new();}
|
||||
public class BehaviorConf{public float rodEnterMapDelay=.1f;}
|
||||
public class Behavior {public Rod RodBehaviour=new();public Animator cameraAnimator=new();}
|
||||
public class Rod {public GameObject gameObject=new();}
|
||||
public class Animator {public bool enabled;public Transform transform=new();public int speed{set{Probe.CameraWrites++;}}public void Play(string _,int __,int ___){Probe.CameraWrites++;}}
|
||||
public class Transform{public Vector3 localEulerAngles,localPosition;}
|
||||
public struct Vector3{public static Vector3 zero=>new();}
|
||||
public class PlayableDirector{public GameObject gameObject=new();public double duration=1;}
|
||||
public class MapData{public string BGM="bgm";}
|
||||
public class IUserService{public bool IsAuthorized=true;public CancellationTokenSource Lifetime=new();public CancellationToken LoginLifetime=>Lifetime.Token;}
|
||||
public class ShowHandEvent{public bool isShow;}
|
||||
public partial class GetUiSubject {public bool Destroyed;public FishingData fishingData=new();public bool IsHideHomeMap;public Behavior fishingBehavior=new();public MapData _curMapData=new();public void SetMapShow(bool _){} public static bool operator ==(GetUiSubject a,GetUiSubject b)=>ReferenceEquals(a,b)||(ReferenceEquals(a,null)&&!ReferenceEquals(b,null)&&b.Destroyed)||(ReferenceEquals(b,null)&&!ReferenceEquals(a,null)&&a.Destroyed);public static bool operator !=(GetUiSubject a,GetUiSubject b)=>!(a==b);public override bool Equals(object obj)=>ReferenceEquals(this,obj);public override int GetHashCode()=>base.GetHashCode();}
|
||||
|
||||
namespace cfg{public class Tables{}}
|
||||
public class ISettingService{public bool Music=true,Sound=true;}
|
||||
public class AudioClip{}
|
||||
public class AudioMixer{public void GetFloat(string _,out float v){v=0;}public AudioMixerSnapshot FindSnapshot(string _)=>new();}
|
||||
public class AudioMixerSnapshot{}
|
||||
public class AudioMixerGroup{}
|
||||
public class Sound {public GameObject gameObject=new();public AudioSource audioSource=new();}
|
||||
public class AudioSource{public void Stop(){}}
|
||||
public class FakeSoundService {
|
||||
public bool Released;public int Releases;public TaskCompletionSource<bool> Pending;public Task InitAsync(){Released=false;return Pending?.Task??Task.CompletedTask;}public void Release(){Released=true;Releases++;}public void ReturnSound(Sound _){}
|
||||
void Volume(){if(Released)Probe.VolumeAfterRelease++;if(Probe.ThrowVolume)throw new Exception("volume failure");}
|
||||
public int BGMVol{set{Volume();}}public int SFXVol{set{Volume();}}public int VoiceVol{set{Volume();}}
|
||||
}
|
||||
public class EventUISound{} public class EventRewardSound{} public class EventFishingSound{} public class EventBGMSound{public EventBGMSound(string _) {}} public class FadeOutEvent{}
|
||||
public class AssetReferenceAudioClip{} public class AssetReferenceAudioVoiceClip{} public class ChangeRodDataEvent{} public class SoundMuteEvent{} public class OnSoundStateEvent{} public class LowpassEffectEvt{}
|
||||
public partial class ManagerSubject {
|
||||
private int initializationVersion;
|
||||
public FakeSoundService soundService=new();public cfg.Tables _tables;public string[] residentFish={"clip-a","clip-b"};
|
||||
public Dictionary<string,AudioClip> residentClip;public CompositeDisposable disposables;public Dictionary<string,Sound> rewardSoundDic=new();public HashSet<Sound> _activeFishingLoopSounds=new();public AudioClip BGM_Clip;public Sound BGM_Sound;public string BGMName;
|
||||
void PlaySound(EventUISound _){}void PlayRewardSound(EventRewardSound _){}void PlayFishingSound(EventFishingSound _){}void PlayMusic(EventBGMSound _){}void FadeOutEvent(FadeOutEvent _){}void PlaySoundByReference(AssetReferenceAudioClip _){}void PlayVoiceSoundByReference(AssetReferenceAudioVoiceClip _){}void OnChangeRodData(ChangeRodDataEvent _){}void OnSoundMute(SoundMuteEvent _){}void OnSoundStateEvent(OnSoundStateEvent _){}
|
||||
}
|
||||
public interface IOperation{bool Valid{get;set;}int Releases{get;set;}object Asset{get;}string Key{get;set;}}
|
||||
public class Operation<T>:IOperation {public TaskCompletionSource<T> Completion=new(); public bool Valid{get;set;}=true; public int Releases{get;set;}public object Asset=>Completion.Task.IsCompletedSuccessfully?Completion.Task.Result:null;public string Key{get;set;}}
|
||||
public struct AsyncOperationHandle<T>{public Operation<T> Op;public Task<T> Task=>Op.Completion.Task;public T Result{get{if(!IsValid())throw new Exception("read released handle");return Op.Completion.Task.GetAwaiter().GetResult();}}public bool IsValid()=>Op!=null&&Op.Valid;}
|
||||
public static class Addressables {
|
||||
public static List<IOperation> All=new();public static Dictionary<string,object> Pending=new();public static AsyncOperationHandle<T> LoadAssetAsync<T>(string key){var op=new Operation<T>{Key=key};Pending[key]=op;All.Add(op);return new(){Op=op};}
|
||||
public static Operation<T> Get<T>(string key)=>(Operation<T>)Pending[key];public static void Complete<T>(string key,T value)=>Get<T>(key).Completion.SetResult(value);
|
||||
public static void Release<T>(AsyncOperationHandle<T> h){if(!h.IsValid())throw new Exception("invalid/double handle release");h.Op.Valid=false;h.Op.Releases++;Probe.ReleasedHandles++;}
|
||||
public static void Release(object asset) {if(asset==null)return;var op=All.FirstOrDefault(o=>o.Valid&&ReferenceEquals(o.Asset,asset));if(op==null)throw new Exception("unowned/double object release");op.Valid=false;op.Releases++;Probe.ReleasedHandles++;}
|
||||
}
|
||||
public static class Assert{public static void IsNotNull(object value){if(value==null)throw new Exception("Assert null");}}
|
||||
public class Pool{public int Active;public void CreatePool<T>(T _,int a,int b){Active++;Probe.PoolCreates++;if(Probe.ThrowPool)throw new Exception("pool failure");}public void DestroyPool<T>(){Active=0;}}
|
||||
public partial class ServiceSubject {
|
||||
private int initializationVersion;
|
||||
public Dictionary<Sound,CancellationTokenSource> SoundFadeTaskDic;public AsyncOperationHandle<AudioMixer> mixerHandle;public AsyncOperationHandle<GameObject> soundPrefabHandle;
|
||||
public AudioMixer mixer;public float bgmVol,fishingVol;public AudioMixerGroup bgmGroup,fishingGroup,uiGroup,voiceGroup;public AudioMixerSnapshot normal;public IDisposable lowpassEvtDisposable;public Pool poolService=new();
|
||||
const string Bgm_Vol_Param_Name="bgmVol",Fishing_Vol_Param_Name="fishingVol";
|
||||
AudioMixerGroup FindMixerGroup(string _) => new();void OnLowpassEffectEvt(LowpassEffectEvt _){}
|
||||
}
|
||||
public class IStageData{}public class SurveyService{}public class IConfig{public string Get(string key,string fallback)=>fallback;}
|
||||
public static class LoadingScreen{public static int Writes;public static void SetProgress(float _) => Writes++;}
|
||||
public static class Debug{public static int Warnings;public static void LogWarning(string _) => Warnings++;}
|
||||
public class SoundMgr{public Action<float> Progress;public TaskCompletionSource<bool> Ready=new();public Task Init(Action<float> progress){Progress=progress;return Ready.Task;}}
|
||||
namespace UnityEngine.AddressableAssets{public static class Addressables{public static AsyncOperationHandle<GameObject> InstantiateAsync(string _){var op=new Operation<GameObject>();op.Completion.SetResult(new());return new(){Op=op};}public static void ReleaseInstance(GameObject _) {}}}
|
||||
public class StageBase{public TaskCompletionSource<bool> StartReady=new();public TaskCompletionSource<bool> StopReady;public virtual Task StartAsync()=>StartReady.Task;public virtual Task Stop()=>StopReady?.Task??Task.CompletedTask;protected virtual void OnDestroy(){}}
|
||||
public partial class StageSubject{private bool stopping;public bool Destroyed,Applied,CatchCompleted;public IUserService UserService=new();private CompositeDisposable disposable=new();private IDisposable _autoRestart;public void DestroyStage(){Destroyed=true;OnDestroy();}public static bool operator ==(StageSubject a,StageSubject b)=>ReferenceEquals(a,b)||(ReferenceEquals(a,null)&&!ReferenceEquals(b,null)&&b.Destroyed)||(ReferenceEquals(b,null)&&!ReferenceEquals(a,null)&&a.Destroyed);public static bool operator !=(StageSubject a,StageSubject b)=>!(a==b);public override bool Equals(object o)=>ReferenceEquals(this,o);public override int GetHashCode()=>base.GetHashCode();}
|
||||
199
CodexOutput/upserver/ReviewLifecycleTests/baseline/Generated.cs
Normal file
199
CodexOutput/upserver/ReviewLifecycleTests/baseline/Generated.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
using System;using System.Linq;using System.Collections.Generic;using System.Threading;using System.Threading.Tasks;
|
||||
public partial class GetUiSubject {
|
||||
public async Task GetUI()
|
||||
{
|
||||
HomePanel homePanel = null;
|
||||
if (!fishingData.isMapBuff && !IsHideHomeMap)
|
||||
{
|
||||
var go = await UIManager.Instance.RestartShowUI(UITypes.HomePanel);
|
||||
homePanel = go.GetComponent<HomePanel>();
|
||||
}
|
||||
//实例
|
||||
GContext.container.Resolve<IDeferredRewardStashService>();
|
||||
//这里确认补单
|
||||
await GContext.container.Resolve<PlayerShopData>().ReleaseReplenishment();
|
||||
await Awaiters.SecondsRealtime(0.5f);
|
||||
GContext.container.Resolve<FishingEventData>().ShowRankTierChange();
|
||||
if (fishingData.isMapBuff)
|
||||
{
|
||||
fishingData.isMapBuff = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
fishingBehavior?.RodBehaviour.gameObject.SetActive(true);
|
||||
fishingBehavior.cameraAnimator.speed = 1;
|
||||
GContext.Publish(new ShowHandEvent() { isShow = true });
|
||||
SetMapShow(!IsHideHomeMap);
|
||||
if (IsHideHomeMap)
|
||||
{
|
||||
IsHideHomeMap = false;
|
||||
}
|
||||
else if (homePanel != null)
|
||||
{
|
||||
homePanel.OnStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public partial class ManagerSubject {
|
||||
public async Task Init(System.Action<float> progress)
|
||||
{
|
||||
//playerData = GContext.container.Resolve<PlayerData>();
|
||||
_tables = GContext.container.Resolve<cfg.Tables>();
|
||||
//var service = (SoundService)soundService;
|
||||
await soundService.InitAsync();
|
||||
|
||||
var opMap = new Dictionary<string, AsyncOperationHandle<AudioClip>>();
|
||||
|
||||
foreach (var addr in residentFish)
|
||||
{
|
||||
opMap[addr] = Addressables.LoadAssetAsync<AudioClip>(addr);
|
||||
}
|
||||
|
||||
var tasks = opMap.Values.Select(_ => _.Task);
|
||||
var total = (float)tasks.Count();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var completed = tasks.Count(_ => _.IsCompleted);
|
||||
if (completed == total)
|
||||
{
|
||||
progress.Invoke(1f);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
progress.Invoke(completed / total);
|
||||
}
|
||||
await Awaiters.NextFrame;
|
||||
}
|
||||
|
||||
residentClip = opMap.ToDictionary(_ => _.Key, _ => _.Value.Result);
|
||||
disposables = new CompositeDisposable();
|
||||
GContext.OnEvent<EventUISound>().Subscribe(PlaySound).AddTo(disposables);
|
||||
GContext.OnEvent<EventRewardSound>().Subscribe(PlayRewardSound).AddTo(disposables);
|
||||
GContext.OnEvent<EventFishingSound>().Subscribe(PlayFishingSound).AddTo(disposables);
|
||||
GContext.OnEvent<EventBGMSound>().Subscribe(PlayMusic).AddTo(disposables);
|
||||
GContext.OnEvent<FadeOutEvent>().Subscribe(FadeOutEvent).AddTo(disposables);
|
||||
GContext.OnEvent<AssetReferenceAudioClip>().Subscribe(PlaySoundByReference).AddTo(disposables);
|
||||
GContext.OnEvent<AssetReferenceAudioVoiceClip>().Subscribe(PlayVoiceSoundByReference).AddTo(disposables);
|
||||
|
||||
GContext.OnEvent<ChangeRodDataEvent>().Subscribe(OnChangeRodData).AddTo(disposables);
|
||||
GContext.OnEvent<SoundMuteEvent>().Subscribe(OnSoundMute).AddTo(disposables);
|
||||
GContext.OnEvent<OnSoundStateEvent>().Subscribe(OnSoundStateEvent).AddTo(disposables);
|
||||
soundService.BGMVol = GContext.container.Resolve<ISettingService>().Music ? 80 : 0;
|
||||
soundService.SFXVol = GContext.container.Resolve<ISettingService>().Sound ? 80 : 0;
|
||||
soundService.VoiceVol = GContext.container.Resolve<ISettingService>().Sound ? 80 : 0;
|
||||
}
|
||||
public void Release()
|
||||
{
|
||||
if (disposables != null)
|
||||
{
|
||||
disposables.Dispose();
|
||||
disposables = null;
|
||||
}
|
||||
|
||||
if (residentClip != null)
|
||||
{
|
||||
foreach (var item in residentClip)
|
||||
{
|
||||
if (item.Value != null)
|
||||
{
|
||||
Addressables.Release(item.Value);
|
||||
}
|
||||
}
|
||||
residentClip.Clear();
|
||||
}
|
||||
|
||||
if (rewardSoundDic != null)
|
||||
{
|
||||
rewardSoundDic.Clear();
|
||||
}
|
||||
|
||||
if (soundService != null)
|
||||
{
|
||||
foreach (var s in _activeFishingLoopSounds.ToList())
|
||||
{
|
||||
if (s != null)
|
||||
{
|
||||
s.audioSource.Stop();
|
||||
soundService.ReturnSound(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
_activeFishingLoopSounds.Clear();
|
||||
|
||||
if (soundService != null)
|
||||
{
|
||||
soundService.Release();
|
||||
}
|
||||
|
||||
if (BGM_Clip != null)
|
||||
{
|
||||
Addressables.Release(BGM_Clip);
|
||||
BGM_Clip = null;
|
||||
}
|
||||
|
||||
if (BGM_Sound != null && BGM_Sound.gameObject != null)
|
||||
{
|
||||
GameObject.Destroy(BGM_Sound.gameObject);
|
||||
BGM_Sound = null;
|
||||
}
|
||||
|
||||
BGMName = null;
|
||||
}
|
||||
}
|
||||
public partial class ServiceSubject {
|
||||
public async Task InitAsync(string mixerAddr = "AudioMixer", string soundPrefabAddr = "sound",
|
||||
string bgmGroupId = "BGM", string fishingGroupId = "Fishing", string uiGroupId = "UI", string voiceGroupId = "Voice")
|
||||
{
|
||||
SoundFadeTaskDic = new Dictionary<Sound, CancellationTokenSource>();
|
||||
|
||||
mixerHandle = Addressables.LoadAssetAsync<AudioMixer>(mixerAddr);
|
||||
mixer = await mixerHandle.Task;
|
||||
Assert.IsNotNull(mixer);
|
||||
mixer.GetFloat(Bgm_Vol_Param_Name, out bgmVol);
|
||||
mixer.GetFloat(Fishing_Vol_Param_Name, out fishingVol);
|
||||
|
||||
bgmGroup = FindMixerGroup(bgmGroupId);
|
||||
fishingGroup = FindMixerGroup(fishingGroupId);
|
||||
uiGroup = FindMixerGroup(uiGroupId);
|
||||
voiceGroup = FindMixerGroup(voiceGroupId);
|
||||
normal = mixer.FindSnapshot("Snapshot");// ("normal");
|
||||
//lowpass = mixer.FindSnapshot("lowpass");
|
||||
Assert.IsNotNull(bgmGroup);
|
||||
Assert.IsNotNull(fishingGroup);
|
||||
Assert.IsNotNull(uiGroup);
|
||||
|
||||
soundPrefabHandle = Addressables.LoadAssetAsync<GameObject>(soundPrefabAddr);
|
||||
var soundPrefab = await soundPrefabHandle.Task;
|
||||
Assert.IsNotNull(soundPrefab);
|
||||
var sound = soundPrefab.GetComponent<Sound>();
|
||||
Assert.IsNotNull(sound);
|
||||
|
||||
lowpassEvtDisposable = GContext.OnEvent<LowpassEffectEvt>().Subscribe(OnLowpassEffectEvt);
|
||||
|
||||
poolService.CreatePool<Sound>(sound, 10, 50);
|
||||
}
|
||||
public void Release()
|
||||
{
|
||||
if (SoundFadeTaskDic != null)
|
||||
{
|
||||
foreach (var source in SoundFadeTaskDic.Values)
|
||||
{
|
||||
source.Cancel();
|
||||
}
|
||||
SoundFadeTaskDic.Clear();
|
||||
SoundFadeTaskDic = null;
|
||||
}
|
||||
|
||||
poolService?.DestroyPool<Sound>();
|
||||
if (mixerHandle.IsValid()) Addressables.Release(mixerHandle);
|
||||
mixerHandle = default;
|
||||
if (soundPrefabHandle.IsValid()) Addressables.Release(soundPrefabHandle);
|
||||
soundPrefabHandle = default;
|
||||
|
||||
lowpassEvtDisposable?.Dispose();
|
||||
lowpassEvtDisposable = null;
|
||||
}
|
||||
}
|
||||
34
CodexOutput/upserver/ReviewLifecycleTests/generate.py
Normal file
34
CodexOutput/upserver/ReviewLifecycleTests/generate.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from pathlib import Path
|
||||
import hashlib,json
|
||||
root=Path('Client/Assets/Scripts'); out=Path('CodexOutput/upserver/ReviewLifecycleTests')
|
||||
def method(path, signature):
|
||||
text=(root/path).read_text(encoding='utf-8-sig'); start=text.index(signature); opening=text.index('{',start); depth=1; end=opening+1
|
||||
while depth:
|
||||
depth+=(text[end]=='{')-(text[end]=='}'); end+=1
|
||||
return text[start:end]
|
||||
parts=[('GetUiSubject','Stages/FishingAct.cs',['async void GetUI()','async void NewEnterMap(PlayableDirector camera_timeline)','async Task OnCameraSpeedEvent()']),('ManagerSubject','Core/SoundMgr.cs',['public async Task Init(System.Action<float> progress)','public void Release()']),('ServiceSubject','Sound/SoundService.cs',['public async Task InitAsync(string mixerAddr','public void Release()'])]
|
||||
generated='using System;using System.Linq;using System.Collections.Generic;using System.Threading;using System.Threading.Tasks;\n'
|
||||
evidence=[]
|
||||
for name,path,signatures in parts:
|
||||
generated+=f'public partial class {name} {{\n'
|
||||
for signature in signatures:
|
||||
# SoundService also has an interface Release; select class suffix.
|
||||
if name=='ServiceSubject' and signature=='public void Release()':
|
||||
text=(root/path).read_text(encoding='utf-8-sig'); text=text[text.index('public class SoundService'):]
|
||||
start=text.index(signature); opening=text.index('{',start); depth=1; end=opening+1
|
||||
while depth: depth+=(text[end]=='{')-(text[end]=='}'); end+=1
|
||||
raw=text[start:end]
|
||||
else: raw=method(path,signature)
|
||||
extracted=raw.replace('async void GetUI()', 'public async Task GetUI()').replace('async void NewEnterMap(', 'public async Task NewEnterMap(').replace('async Task OnCameraSpeedEvent()', 'public async Task OnCameraSpeedEvent()')
|
||||
generated+=extracted+'\n'
|
||||
evidence.append({'source':str(root/path),'signature':signature,'source_sha256':hashlib.sha256((root/path).read_bytes()).hexdigest(),'method_sha256':hashlib.sha256(raw.encode()).hexdigest(),'transformation':'async void -> public async Task only' if name=='GetUiSubject' else 'none'})
|
||||
generated+='}\n'
|
||||
stage_path='Stages/FishingStage.cs'; stage_text=(root/stage_path).read_text(encoding='utf-8-sig')
|
||||
prefix=stage_text[stage_text.index('public override async Task StartAsync()'):stage_text.index('//GContext.Publish()')].replace('public override async Task StartAsync()', 'public async Task RunPrefix()')
|
||||
stop=method(stage_path,'public override async Task Stop()')
|
||||
destroy=method(stage_path,'protected override void OnDestroy()')
|
||||
catch_start=stage_text.index('try { await base.StartAsync(); }'); catch_end=stage_text.index('//静默加载后续场景',catch_start); guard=stage_text[catch_start:catch_end]
|
||||
generated+='public partial class StageSubject : StageBase {\n'+prefix+'Applied=true; }\n'+stop+'\n'+destroy+'\npublic async Task RunCatch(){ var loginLifetime=UserService.LoginLifetime;\n'+guard+'CatchCompleted=true; }\n}\n'
|
||||
evidence.append({'source':str(root/stage_path),'source_sha256':hashlib.sha256((root/stage_path).read_bytes()).hexdigest(),'transformation':'StartAsync prefix ends before //GContext.Publish(); Stop and OnDestroy complete; actual base.StartAsync catch segment through next-scene comment; only wrapper names/assertion markers added'})
|
||||
(out/'Generated.cs').write_text(generated,encoding='utf-8')
|
||||
(out/'extraction.json').write_text(json.dumps(evidence,ensure_ascii=False,indent=2),encoding='utf-8')
|
||||
Reference in New Issue
Block a user