Files
SVSimServer/SVSim.BattleEngine/Engine/UIDrawCall.cs
gamer147 2d9a6eea4b engine cleanup passes 4-7 + multi-instancing ambient rip
Squashes 146 commits from battle-engine-extraction. Net: 2,045 files changed,
+11,896 / -158,687 lines. Ships engine passes 4-7 (dead-code cull, view-layer
stub, receive-path shrink) plus the Phase-5 AsyncLocal ambient deletion that
turns concurrent battles into a type-system property rather than a scope
contract.

## What landed

**Passes 4-7 (chunks 1-34):** Extended the Phase-4 const-false collapse into a
cascading cull across the skill graph, view layer, and receive-path periphery.
Six mode flags (IsWatchBattle/IsReplayBattle/IsAdmin/IsAdminWatch/IsPuzzleQuest/
IsAINetwork) became `const false`, every guarded block deleted. Field*.cs
subclass ctors + BackGroundBase + ObjectChecker culled to no-ops. Mulligan
family reworked to take a mgr param through IMulliganMgr.InitMulligan.
Emotion/Recovery/Resource clusters null-stubbed. Prediction/OperationSimulator/
skill filters converted from static ambient reads to per-mgr reads via
SkillPrm.ownerCard.SelfBattlePlayer.BattleMgr / ins.BattleMgr / this.BattleMgr.

**Phase-5 ambient rip (chunks 35-47):** Deleted BattleAmbient / BattleAmbient-
Context / TestBattleScope in full. Every per-battle mutable slot now lives on
the mgr instance itself:
  mgr.InstanceIsForecast / InstanceIsRandomDraw / InstanceRecoveryInfo /
  InstanceViewerId / InstanceNetworkAgent / GameMgr
BattleManagerBase.GetIns() returns null unconditionally; the residual static
flags + 3 façades (Certification.ViewerId, Data.BattleRecoveryInfo,
ToolboxGame.RealTimeNetworkAgent) are null-tolerant defaults kept for the
handful of engine-internal readers that still reference their types. Zero
BattleAmbient references anywhere in engine + node + tests.

Added pre-seeded GameMgr ctor overload threaded through the mgr chain
(BattleManagerBase → SingleBattleMgr / NetworkBattleManagerBase → NetworkStandard-
BattleMgr → HeadlessBattleMgr / HeadlessNetworkBattleMgr). Fixtures build a
GameMgr, seed it via HeadlessEngineEnv.SeedCharaIds/SeedNetUser, and pass it
to the mgr's ctor — no ambient reach.

Node side (SVSim.BattleNode/SessionBattleEngine): _ctx replaced with a plain
GameMgr field; 34 `using var _ambient = BattleAmbient.Enter(_ctx)` scope wraps
ripped from every accessor and mutator; EngineGlobalInit.WirePerSessionGameMgr
takes GameMgr as a param and runs from SessionBattleEngine.SetupInternal
BEFORE mgr construction.

Test side: TestBattleScope deleted; 18 fixture [SetUp]s migrated to
`HeadlessEngineEnv.EnsureProcessGlobals()`; MultiInstanceEngineTests rewritten
around per-mgr construction (GetIns() → null is the pinned invariant).

## Regression fixes

- **chunk-48** (MulliganCtrl): chunk-35's `= null` stubs on card lookups broke
  the live receive-driven Deal path (BattlePlayerBase.DrawCard NRE'd downstream
  of NetworkPlayerMulliganCtrl.StartMulliganVfx). Restored the three lookups
  via `_battlePlayer.BattleMgr.GetBattleCardIdx`. Engine tests were satisfied
  by the WireMulliganPhase seam; unit tests exposed the live-path gap.

## Ship state

- SVSim.BattleEngine.Tests: 56/56 pass, 2 skip
- SVSim.UnitTests: 1554/1554 pass (was 1523/31-fail before chunk 48)
- Solution build: 0 source warnings (40 pre-existing NU1902 MessagePack CVEs
  in SVSim.EmulatedEntrypoint, unrelated)
- Sequential PVP smoke: verified live (two back-to-back battles, no regression
  on cleanup/spinup)
- Concurrent PVP smoke: verified live

Adds tools/engine-port/ClosureAnalyzer/ — the Roslyn transitive-type-closure
analyzer needed to make future cascade cleanup safe (per feedback memory
"Engine cleanup needs closure tool" from the 2026-06-28 pass-3 failure).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-03 19:18:54 -04:00

635 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
[AddComponentMenu("NGUI/Internal/Draw Call")]
public class UIDrawCall : MonoBehaviour
{
public enum Clipping
{
None = 0,
TextureMask = 1,
SoftClip = 3,
ConstrainButDontClip = 4
}
public delegate void OnRenderCallback(Material mat);
private static BetterList<UIDrawCall> mActiveList = new BetterList<UIDrawCall>();
private static BetterList<UIDrawCall> mInactiveList = new BetterList<UIDrawCall>();
[NonSerialized]
[HideInInspector]
public int widgetCount;
[NonSerialized]
[HideInInspector]
public int depthStart = int.MaxValue;
[NonSerialized]
[HideInInspector]
public int depthEnd = int.MinValue;
[NonSerialized]
[HideInInspector]
public UIPanel manager;
[NonSerialized]
[HideInInspector]
public UIPanel panel;
[NonSerialized]
[HideInInspector]
public Texture2D clipTexture;
[NonSerialized]
[HideInInspector]
public bool alwaysOnScreen;
[NonSerialized]
[HideInInspector]
public BetterList<Vector3> verts = new BetterList<Vector3>();
[NonSerialized]
[HideInInspector]
public BetterList<Vector3> norms = new BetterList<Vector3>();
[NonSerialized]
[HideInInspector]
public BetterList<Vector4> tans = new BetterList<Vector4>();
[NonSerialized]
[HideInInspector]
public BetterList<Vector2> uvs = new BetterList<Vector2>();
[NonSerialized]
[HideInInspector]
public BetterList<Color32> cols = new BetterList<Color32>();
private Material mMaterial;
private Texture mTexture;
private Shader mShader;
private int mClipCount;
private Transform mTrans;
private Mesh mMesh;
private MeshFilter mFilter;
private MeshRenderer mRenderer;
private Material mDynamicMat;
private int[] mIndices;
private bool mRebuildMat = true;
private bool mLegacyShader;
private int mRenderQueue = 3000;
private int mTriangles;
[NonSerialized]
public bool isDirty;
[NonSerialized]
private bool mTextureClip;
public OnRenderCallback onRender;
private static List<int[]> mCache = new List<int[]>(10);
private static int[] ClipRange = null;
private static int[] ClipArgs = null;
public int renderQueue
{
get
{
return mRenderQueue;
}
set
{
if (mRenderQueue != value)
{
mRenderQueue = value;
if (mDynamicMat != null)
{
mDynamicMat.renderQueue = value;
}
}
}
}
public int sortingOrder
{
get
{
if (!(mRenderer != null))
{
return 0;
}
return mRenderer.sortingOrder;
}
set
{
if (mRenderer != null && mRenderer.sortingOrder != value)
{
mRenderer.sortingOrder = value;
}
}
}
public Transform cachedTransform
{
get
{
if (mTrans == null)
{
mTrans = base.transform;
}
return mTrans;
}
}
public Material baseMaterial
{
get
{
return mMaterial;
}
set
{
if (mMaterial != value)
{
mMaterial = value;
mRebuildMat = true;
}
}
}
public Texture mainTexture
{
get
{
return mTexture;
}
set
{
mTexture = value;
if (mDynamicMat != null)
{
mDynamicMat.mainTexture = value;
}
}
}
public Shader shader
{
get
{
return mShader;
}
set
{
if (mShader != value)
{
mShader = value;
mRebuildMat = true;
}
}
}
public bool isClipped => mClipCount != 0;
private void CreateMaterial()
{
mTextureClip = false;
mLegacyShader = false;
mClipCount = ((panel != null) ? panel.clipCount : 0);
string text = ((mShader != null) ? mShader.name : ((mMaterial != null) ? mMaterial.shader.name : "Unlit/Transparent Colored"));
text = text.Replace("GUI/Text Shader", "Unlit/Text");
if (text.Length > 2 && text[text.Length - 2] == ' ')
{
int num = text[text.Length - 1];
if (num > 48 && num <= 57)
{
text = text.Substring(0, text.Length - 2);
}
}
if (text.StartsWith("Hidden/"))
{
text = text.Substring(7);
}
text = text.Replace(" (SoftClip)", "");
text = text.Replace(" (TextureClip)", "");
if (panel != null && panel.clipping == Clipping.TextureMask)
{
mTextureClip = true;
shader = Shader.Find("Hidden/" + text + " (TextureClip)");
}
else if (mClipCount != 0)
{
shader = Shader.Find("Hidden/" + text + " " + mClipCount);
if (shader == null)
{
shader = Shader.Find(text + " " + mClipCount);
}
if (shader == null && mClipCount == 1)
{
mLegacyShader = true;
shader = Shader.Find(text + " (SoftClip)");
}
}
else
{
shader = Shader.Find(text);
}
if (shader == null)
{
shader = Shader.Find("Unlit/Transparent Colored");
}
if (mMaterial != null)
{
if (IsWizardMaterial())
{
mDynamicMat = new Material(mMaterial);
mDynamicMat.EnableKeyword("USE_VERTEX_COLOR");
mDynamicMat.SetFloat("_SleeveSrcFactor", 5f);
mDynamicMat.SetFloat("_SleeveDstFactor", 10f);
mDynamicMat.SetFloat("_CullMode", 0f);
mDynamicMat.SetFloat("_ZWriteMode", 0f);
mLegacyShader = false;
if (isClipped)
{
mDynamicMat.EnableKeyword("USE_CLIP");
}
if (IsSleeveMaterial())
{
mDynamicMat.EnableKeyword("USE_SLEEVE");
}
mDynamicMat.shader = Shader.Find(mMaterial.shader.name);
}
else
{
mDynamicMat = new Material(mMaterial);
mDynamicMat.name = "[NGUI] " + mMaterial.name;
mDynamicMat.hideFlags = HideFlags.DontSave | HideFlags.NotEditable;
mDynamicMat.CopyPropertiesFromMaterial(mMaterial);
string[] shaderKeywords = mMaterial.shaderKeywords;
for (int i = 0; i < shaderKeywords.Length; i++)
{
mDynamicMat.EnableKeyword(shaderKeywords[i]);
}
if (shader != null)
{
mDynamicMat.shader = shader;
}
else if (mClipCount != 0)
{
Debug.LogError(text + " shader doesn't have a clipped shader version for " + mClipCount + " clip regions");
}
}
}
else
{
mDynamicMat = new Material(shader);
mDynamicMat.name = "[NGUI] " + shader.name;
mDynamicMat.hideFlags = HideFlags.DontSave | HideFlags.NotEditable;
}
}
private bool IsWizardMaterial()
{
if (mMaterial.shader.name == "Wizard/VariantSleeveShader")
{
return true;
}
return false;
}
private bool IsSleeveMaterial()
{
if (mMaterial.name.Contains("sleeve"))
{
return true;
}
return false;
}
private Material RebuildMaterial()
{
NGUITools.DestroyImmediate(mDynamicMat);
CreateMaterial();
mDynamicMat.renderQueue = mRenderQueue;
if (mTexture != null)
{
mDynamicMat.mainTexture = mTexture;
}
if (mRenderer != null)
{
mRenderer.sharedMaterials = new Material[1] { mDynamicMat };
}
return mDynamicMat;
}
private void UpdateMaterials()
{
if (!(panel == null))
{
if (mRebuildMat || mDynamicMat == null || mClipCount != panel.clipCount || mTextureClip != (panel.clipping == Clipping.TextureMask))
{
RebuildMaterial();
mRebuildMat = false;
}
else if (mRenderer.sharedMaterial != mDynamicMat)
{
mRenderer.sharedMaterials = new Material[1] { mDynamicMat };
}
}
}
public void UpdateGeometry(int widgetCount)
{
this.widgetCount = widgetCount;
int size = verts.size;
if (size > 0 && size == uvs.size && size == cols.size && size % 4 == 0)
{
if (mFilter == null)
{
mFilter = base.gameObject.GetComponent<MeshFilter>();
}
if (mFilter == null)
{
mFilter = base.gameObject.AddComponent<MeshFilter>();
}
if (verts.size < 65000)
{
int num = (size >> 1) * 3;
bool flag = mIndices == null || mIndices.Length != num;
if (mMesh == null)
{
mMesh = new Mesh();
mMesh.hideFlags = HideFlags.DontSave;
mMesh.name = ((mMaterial != null) ? ("[NGUI] " + mMaterial.name) : "[NGUI] Mesh");
mMesh.MarkDynamic();
flag = true;
}
bool flag2 = uvs.buffer.Length != verts.buffer.Length || cols.buffer.Length != verts.buffer.Length || (norms.buffer != null && norms.buffer.Length != verts.buffer.Length) || (tans.buffer != null && tans.buffer.Length != verts.buffer.Length);
if (!flag2 && panel != null && panel.renderQueue != UIPanel.RenderQueue.Automatic)
{
flag2 = mMesh == null || mMesh.vertexCount != verts.buffer.Length;
}
if (!flag2 && verts.size << 1 < verts.buffer.Length)
{
flag2 = true;
}
mTriangles = verts.size >> 1;
if (flag2 || verts.buffer.Length > 65000)
{
if (flag2 || mMesh.vertexCount != verts.size)
{
mMesh.Clear();
flag = true;
}
mMesh.vertices = verts.ToArray();
mMesh.uv = uvs.ToArray();
mMesh.colors32 = cols.ToArray();
if (norms != null)
{
mMesh.normals = norms.ToArray();
}
if (tans != null)
{
mMesh.tangents = tans.ToArray();
}
}
else
{
if (mMesh.vertexCount != verts.buffer.Length)
{
mMesh.Clear();
flag = true;
}
mMesh.vertices = verts.buffer;
mMesh.uv = uvs.buffer;
mMesh.colors32 = cols.buffer;
if (norms != null)
{
mMesh.normals = norms.buffer;
}
if (tans != null)
{
mMesh.tangents = tans.buffer;
}
}
if (flag)
{
mIndices = GenerateCachedIndexBuffer(size, num);
mMesh.triangles = mIndices;
}
if (flag2 || !alwaysOnScreen)
{
mMesh.RecalculateBounds();
}
mFilter.mesh = mMesh;
}
else
{
mTriangles = 0;
if (mFilter.mesh != null)
{
mFilter.mesh.Clear();
}
Debug.LogError("Too many vertices on one panel: " + verts.size);
}
if (mRenderer == null)
{
mRenderer = base.gameObject.GetComponent<MeshRenderer>();
}
if (mRenderer == null)
{
mRenderer = base.gameObject.AddComponent<MeshRenderer>();
}
UpdateMaterials();
}
else
{
if (mFilter.mesh != null)
{
mFilter.mesh.Clear();
}
Debug.LogError("UIWidgets must fill the buffer with 4 vertices per quad. Found " + size);
}
verts.Clear();
uvs.Clear();
cols.Clear();
norms.Clear();
tans.Clear();
}
private int[] GenerateCachedIndexBuffer(int vertexCount, int indexCount)
{
int i = 0;
for (int count = mCache.Count; i < count; i++)
{
int[] array = mCache[i];
if (array != null && array.Length == indexCount)
{
return array;
}
}
int[] array2 = new int[indexCount];
int num = 0;
for (int j = 0; j < vertexCount; j += 4)
{
array2[num++] = j;
array2[num++] = j + 1;
array2[num++] = j + 2;
array2[num++] = j + 2;
array2[num++] = j + 3;
array2[num++] = j;
}
if (mCache.Count > 10)
{
mCache.RemoveAt(0);
}
mCache.Add(array2);
return array2;
}
private void Awake()
{
if (ClipRange == null)
{
ClipRange = new int[4]
{
Shader.PropertyToID("_ClipRange0"),
Shader.PropertyToID("_ClipRange1"),
Shader.PropertyToID("_ClipRange2"),
Shader.PropertyToID("_ClipRange4")
};
}
if (ClipArgs == null)
{
ClipArgs = new int[4]
{
Shader.PropertyToID("_ClipArgs0"),
Shader.PropertyToID("_ClipArgs1"),
Shader.PropertyToID("_ClipArgs2"),
Shader.PropertyToID("_ClipArgs3")
};
}
}
public static UIDrawCall Create(UIPanel panel, Material mat, Texture tex, Shader shader)
{
return Create(null, panel, mat, tex, shader);
}
private static UIDrawCall Create(string name, UIPanel pan, Material mat, Texture tex, Shader shader)
{
UIDrawCall uIDrawCall = Create(name);
uIDrawCall.gameObject.layer = pan.cachedGameObject.layer;
uIDrawCall.baseMaterial = mat;
uIDrawCall.mainTexture = tex;
uIDrawCall.shader = shader;
uIDrawCall.renderQueue = pan.startingRenderQueue;
uIDrawCall.sortingOrder = pan.sortingOrder;
uIDrawCall.manager = pan;
return uIDrawCall;
}
private static UIDrawCall Create(string name)
{
if (mInactiveList.size > 0)
{
UIDrawCall uIDrawCall = mInactiveList.Pop();
mActiveList.Add(uIDrawCall);
if (name != null)
{
uIDrawCall.name = name;
}
NGUITools.SetActive(uIDrawCall.gameObject, state: true);
return uIDrawCall;
}
GameObject obj = new GameObject(name);
UnityEngine.Object.DontDestroyOnLoad(obj);
UIDrawCall uIDrawCall2 = obj.AddComponent<UIDrawCall>();
mActiveList.Add(uIDrawCall2);
return uIDrawCall2;
}
public static void ClearAll()
{
bool isPlaying = Application.isPlaying;
int num = mActiveList.size;
while (num > 0)
{
UIDrawCall uIDrawCall = mActiveList[--num];
if ((bool)uIDrawCall)
{
if (isPlaying)
{
NGUITools.SetActive(uIDrawCall.gameObject, state: false);
}
else
{
NGUITools.DestroyImmediate(uIDrawCall.gameObject);
}
}
}
mActiveList.Clear();
}
public static void ReleaseAll()
{
ClearAll();
ReleaseInactive();
}
public static void ReleaseInactive()
{
int num = mInactiveList.size;
while (num > 0)
{
UIDrawCall uIDrawCall = mInactiveList[--num];
if ((bool)uIDrawCall)
{
NGUITools.DestroyImmediate(uIDrawCall.gameObject);
}
}
mInactiveList.Clear();
}
public static void Destroy(UIDrawCall dc)
{
if (!dc)
{
return;
}
dc.onRender = null;
if (Application.isPlaying)
{
if (mActiveList.Remove(dc))
{
NGUITools.SetActive(dc.gameObject, state: false);
mInactiveList.Add(dc);
}
}
else
{
mActiveList.Remove(dc);
NGUITools.DestroyImmediate(dc.gameObject);
}
}
}