Files
SVSimServer/SVSim.BattleEngine/Engine/UIScrollView.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

608 lines
14 KiB
C#

using System;
using UnityEngine;
[ExecuteInEditMode]
[RequireComponent(typeof(UIPanel))]
[AddComponentMenu("NGUI/Interaction/Scroll View")]
public class UIScrollView : MonoBehaviour
{
public enum Movement
{
Horizontal,
Vertical,
Unrestricted,
Custom
}
public enum DragEffect
{
None,
MomentumAndSpring
}
public enum ShowCondition
{
}
public delegate void OnDragNotification();
public static BetterList<UIScrollView> list = new BetterList<UIScrollView>();
public Movement movement;
public DragEffect dragEffect = DragEffect.MomentumAndSpring;
public bool restrictWithinPanel = true;
public bool disableDragIfFits;
public bool smoothDragStart = true;
public float momentumAmount = 35f;
public UIProgressBar horizontalScrollBar;
public UIProgressBar verticalScrollBar;
public Vector2 customMovement = new Vector2(1f, 0f);
public UIWidget.Pivot contentPivot;
public OnDragNotification onDragStarted;
public OnDragNotification onDragFinished;
public OnDragNotification onStoppedMoving;
[HideInInspector]
[SerializeField]
private Vector3 scale = new Vector3(1f, 0f, 0f);
[SerializeField]
[HideInInspector]
private Vector2 relativePositionOnReset = Vector2.zero;
public bool considerSoftnessInShouldMove;
protected Transform mTrans;
protected UIPanel mPanel;
protected Plane mPlane;
protected Vector3 mLastPos;
protected bool mPressed;
protected Vector3 mMomentum = Vector3.zero;
protected float mScroll;
protected Bounds mBounds;
protected bool mCalculatedBounds;
protected bool mShouldMove;
protected bool mIgnoreCallbacks;
protected int mDragID = -10;
protected Vector2 mDragStartOffset = Vector2.zero;
protected bool mDragStarted;
[HideInInspector]
public UICenterOnChild centerOnChild;
public UIPanel panel => mPanel;
public bool isDragging
{
get
{
if (mPressed)
{
return mDragStarted;
}
return false;
}
}
public virtual Bounds bounds
{
get
{
if (!mCalculatedBounds)
{
mCalculatedBounds = true;
mTrans = base.transform;
mBounds = NGUIMath.CalculateRelativeWidgetBounds(mTrans, mTrans);
}
return mBounds;
}
}
public bool canMoveHorizontally
{
get
{
if (movement != Movement.Horizontal && movement != Movement.Unrestricted)
{
if (movement == Movement.Custom)
{
return customMovement.x != 0f;
}
return false;
}
return true;
}
}
public bool canMoveVertically
{
get
{
if (movement != Movement.Vertical && movement != Movement.Unrestricted)
{
if (movement == Movement.Custom)
{
return customMovement.y != 0f;
}
return false;
}
return true;
}
}
public virtual bool shouldMoveVertically
{
get
{
float num = bounds.size.y;
if (mPanel.clipping == UIDrawCall.Clipping.SoftClip)
{
num += mPanel.clipSoftness.y * 2f;
}
return Mathf.RoundToInt(num - mPanel.height) > 0;
}
}
protected virtual bool shouldMove
{
get
{
if (!disableDragIfFits)
{
return true;
}
if (mPanel == null)
{
mPanel = GetComponent<UIPanel>();
}
Vector4 finalClipRegion = mPanel.finalClipRegion;
Bounds bounds = this.bounds;
float num = ((finalClipRegion.z == 0f) ? ((float)Screen.width) : (finalClipRegion.z * 0.5f));
float num2 = ((finalClipRegion.w == 0f) ? ((float)Screen.height) : (finalClipRegion.w * 0.5f));
if (considerSoftnessInShouldMove)
{
num -= mPanel.clipSoftness.x;
num2 -= mPanel.clipSoftness.y;
}
if (canMoveHorizontally)
{
if (bounds.min.x < finalClipRegion.x - num)
{
return true;
}
if (bounds.max.x > finalClipRegion.x + num)
{
return true;
}
}
if (canMoveVertically)
{
if (bounds.min.y < finalClipRegion.y - num2)
{
return true;
}
if (bounds.max.y > finalClipRegion.y + num2)
{
return true;
}
}
return false;
}
}
public Vector3 currentMomentum
{
get
{
return mMomentum;
}
set
{
mMomentum = value;
mShouldMove = !disableDragIfFits;
}
}
private void Awake()
{
mTrans = base.transform;
mPanel = GetComponent<UIPanel>();
if (mPanel.clipping == UIDrawCall.Clipping.None)
{
mPanel.clipping = UIDrawCall.Clipping.ConstrainButDontClip;
}
if (movement != Movement.Custom && scale.sqrMagnitude > 0.001f)
{
if (scale.x == 1f && scale.y == 0f)
{
movement = Movement.Horizontal;
}
else if (scale.x == 0f && scale.y == 1f)
{
movement = Movement.Vertical;
}
else if (scale.x == 1f && scale.y == 1f)
{
movement = Movement.Unrestricted;
}
else
{
movement = Movement.Custom;
customMovement.x = scale.x;
customMovement.y = scale.y;
}
scale = Vector3.zero;
}
if (contentPivot == UIWidget.Pivot.TopLeft && relativePositionOnReset != Vector2.zero)
{
contentPivot = NGUIMath.GetPivot(new Vector2(relativePositionOnReset.x, 1f - relativePositionOnReset.y));
relativePositionOnReset = Vector2.zero;
}
}
public bool RestrictWithinBounds(bool instant)
{
return RestrictWithinBounds(instant, horizontal: true, vertical: true);
}
public bool RestrictWithinBounds(bool instant, bool horizontal, bool vertical)
{
if (mPanel == null)
{
return false;
}
Bounds bounds = this.bounds;
Vector3 vector = mPanel.CalculateConstrainOffset(bounds.min, bounds.max);
if (!horizontal)
{
vector.x = 0f;
}
if (!vertical)
{
vector.y = 0f;
}
if (vector.sqrMagnitude > 0.1f)
{
if (!instant && dragEffect == DragEffect.MomentumAndSpring)
{
Vector3 pos = mTrans.localPosition + vector;
pos.x = Mathf.Round(pos.x);
pos.y = Mathf.Round(pos.y);
SpringPanel.Begin(mPanel.gameObject, pos, 13f).strength = 8f;
}
else
{
MoveRelative(vector);
if (Mathf.Abs(vector.x) > 0.01f)
{
mMomentum.x = 0f;
}
if (Mathf.Abs(vector.y) > 0.01f)
{
mMomentum.y = 0f;
}
if (Mathf.Abs(vector.z) > 0.01f)
{
mMomentum.z = 0f;
}
mScroll = 0f;
}
return true;
}
return false;
}
public void DisableSpring()
{
SpringPanel component = GetComponent<SpringPanel>();
if (component != null)
{
component.enabled = false;
}
}
public void UpdateScrollbars()
{
UpdateScrollbars(recalculateBounds: true);
}
public virtual void UpdateScrollbars(bool recalculateBounds)
{
if (mPanel == null)
{
return;
}
if (horizontalScrollBar != null || verticalScrollBar != null)
{
if (recalculateBounds)
{
mCalculatedBounds = false;
mShouldMove = shouldMove;
}
Bounds bounds = this.bounds;
Vector2 vector = bounds.min;
Vector2 vector2 = bounds.max;
if (horizontalScrollBar != null && vector2.x > vector.x)
{
Vector4 finalClipRegion = mPanel.finalClipRegion;
int num = Mathf.RoundToInt(finalClipRegion.z);
if ((num & 1) != 0)
{
num--;
}
float f = (float)num * 0.5f;
f = Mathf.Round(f);
if (mPanel.clipping == UIDrawCall.Clipping.SoftClip)
{
f -= mPanel.clipSoftness.x;
}
float contentSize = vector2.x - vector.x;
float viewSize = f * 2f;
float x = vector.x;
float x2 = vector2.x;
float num2 = finalClipRegion.x - f;
float num3 = finalClipRegion.x + f;
x = num2 - x;
x2 -= num3;
UpdateScrollbars(horizontalScrollBar, x, x2, contentSize, viewSize, inverted: false);
}
if (verticalScrollBar != null && vector2.y > vector.y)
{
Vector4 finalClipRegion2 = mPanel.finalClipRegion;
int num4 = Mathf.RoundToInt(finalClipRegion2.w);
if ((num4 & 1) != 0)
{
num4--;
}
float f2 = (float)num4 * 0.5f;
f2 = Mathf.Round(f2);
if (mPanel.clipping == UIDrawCall.Clipping.SoftClip)
{
f2 -= mPanel.clipSoftness.y;
}
float contentSize2 = vector2.y - vector.y;
float viewSize2 = f2 * 2f;
float y = vector.y;
float y2 = vector2.y;
float num5 = finalClipRegion2.y - f2;
float num6 = finalClipRegion2.y + f2;
y = num5 - y;
y2 -= num6;
UpdateScrollbars(verticalScrollBar, y, y2, contentSize2, viewSize2, inverted: true);
}
}
else if (recalculateBounds)
{
mCalculatedBounds = false;
}
}
protected void UpdateScrollbars(UIProgressBar slider, float contentMin, float contentMax, float contentSize, float viewSize, bool inverted)
{
if (slider == null)
{
return;
}
mIgnoreCallbacks = true;
float num;
if (viewSize < contentSize)
{
contentMin = Mathf.Clamp01(contentMin / contentSize);
contentMax = Mathf.Clamp01(contentMax / contentSize);
num = contentMin + contentMax;
slider.value = ((!inverted) ? ((num > 0.001f) ? (contentMin / num) : 1f) : ((num > 0.001f) ? (1f - contentMin / num) : 0f));
}
else
{
contentMin = Mathf.Clamp01((0f - contentMin) / contentSize);
contentMax = Mathf.Clamp01((0f - contentMax) / contentSize);
num = contentMin + contentMax;
slider.value = ((!inverted) ? ((num > 0.001f) ? (contentMin / num) : 1f) : ((num > 0.001f) ? (1f - contentMin / num) : 0f));
if (contentSize > 0f)
{
contentMin = Mathf.Clamp01(contentMin / contentSize);
contentMax = Mathf.Clamp01(contentMax / contentSize);
num = contentMin + contentMax;
}
}
UIScrollBar uIScrollBar = slider as UIScrollBar;
if (uIScrollBar != null)
{
uIScrollBar.barSize = 1f - num;
}
mIgnoreCallbacks = false;
}
public virtual void SetDragAmount(float x, float y, bool updateScrollbars)
{
if (mPanel == null)
{
mPanel = GetComponent<UIPanel>();
}
DisableSpring();
Bounds bounds = this.bounds;
if (bounds.min.x == bounds.max.x || bounds.min.y == bounds.max.y)
{
return;
}
Vector4 finalClipRegion = mPanel.finalClipRegion;
float num = finalClipRegion.z * 0.5f;
float num2 = finalClipRegion.w * 0.5f;
float num3 = bounds.min.x + num;
float num4 = bounds.max.x - num;
float num5 = bounds.min.y + num2;
float num6 = bounds.max.y - num2;
if (mPanel.clipping == UIDrawCall.Clipping.SoftClip)
{
num3 -= mPanel.clipSoftness.x;
num4 += mPanel.clipSoftness.x;
num5 -= mPanel.clipSoftness.y;
num6 += mPanel.clipSoftness.y;
}
float num7 = Mathf.Lerp(num3, num4, x);
float num8 = Mathf.Lerp(num6, num5, y);
if (!updateScrollbars)
{
Vector3 localPosition = mTrans.localPosition;
if (canMoveHorizontally)
{
localPosition.x += finalClipRegion.x - num7;
}
if (canMoveVertically)
{
localPosition.y += finalClipRegion.y - num8;
}
mTrans.localPosition = localPosition;
}
if (canMoveHorizontally)
{
finalClipRegion.x = num7;
}
if (canMoveVertically)
{
finalClipRegion.y = num8;
}
Vector4 baseClipRegion = mPanel.baseClipRegion;
mPanel.clipOffset = new Vector2(finalClipRegion.x - baseClipRegion.x, finalClipRegion.y - baseClipRegion.y);
if (updateScrollbars)
{
UpdateScrollbars(mDragID == -10);
}
}
[ContextMenu("Reset Clipping Position")]
public void ResetPosition()
{
if (NGUITools.GetActive(this))
{
mScroll = 0f;
mCalculatedBounds = false;
Vector2 pivotOffset = NGUIMath.GetPivotOffset(contentPivot);
SetDragAmount(pivotOffset.x, 1f - pivotOffset.y, updateScrollbars: false);
SetDragAmount(pivotOffset.x, 1f - pivotOffset.y, updateScrollbars: true);
}
}
public void UpdatePosition()
{
if (!mIgnoreCallbacks && (horizontalScrollBar != null || verticalScrollBar != null))
{
mIgnoreCallbacks = true;
mCalculatedBounds = false;
Vector2 pivotOffset = NGUIMath.GetPivotOffset(contentPivot);
float x = ((horizontalScrollBar != null) ? horizontalScrollBar.value : pivotOffset.x);
float y = ((verticalScrollBar != null) ? verticalScrollBar.value : (1f - pivotOffset.y));
SetDragAmount(x, y, updateScrollbars: false);
UpdateScrollbars(recalculateBounds: true);
mIgnoreCallbacks = false;
}
}
public virtual void MoveRelative(Vector3 relative)
{
mTrans.localPosition += relative;
Vector2 clipOffset = mPanel.clipOffset;
clipOffset.x -= relative.x;
clipOffset.y -= relative.y;
mPanel.clipOffset = clipOffset;
UpdateScrollbars(recalculateBounds: false);
}
public void Press(bool pressed)
{
if (UICamera.currentScheme == UICamera.ControlScheme.Controller)
{
return;
}
if (smoothDragStart && pressed)
{
mDragStarted = false;
mDragStartOffset = Vector2.zero;
}
if (!base.enabled || !NGUITools.GetActive(base.gameObject))
{
return;
}
if (!pressed && mDragID == UICamera.currentTouchID)
{
mDragID = -10;
}
mCalculatedBounds = false;
mShouldMove = shouldMove;
if (!mShouldMove)
{
return;
}
mPressed = pressed;
if (pressed)
{
mMomentum = Vector3.zero;
mScroll = 0f;
DisableSpring();
mLastPos = UICamera.lastWorldPosition;
mPlane = new Plane(mTrans.rotation * Vector3.back, mLastPos);
Vector2 clipOffset = mPanel.clipOffset;
clipOffset.x = Mathf.Round(clipOffset.x);
clipOffset.y = Mathf.Round(clipOffset.y);
mPanel.clipOffset = clipOffset;
Vector3 localPosition = mTrans.localPosition;
localPosition.x = Mathf.Round(localPosition.x);
localPosition.y = Mathf.Round(localPosition.y);
mTrans.localPosition = localPosition;
if (!smoothDragStart)
{
mDragStarted = true;
mDragStartOffset = Vector2.zero;
if (onDragStarted != null)
{
onDragStarted();
}
}
}
else if ((bool)centerOnChild)
{
centerOnChild.Recenter();
}
else
{
if (restrictWithinPanel && mPanel.clipping != UIDrawCall.Clipping.None)
{
RestrictWithinBounds(dragEffect == DragEffect.None, canMoveHorizontally, canMoveVertically);
}
if (mDragStarted && onDragFinished != null)
{
onDragFinished();
}
if (!mShouldMove && onStoppedMoving != null)
{
onStoppedMoving();
}
}
}
}