fix(rendering): bound portal resource lifetime
Separate logical ownership, render publication, and GPU retirement across live entities, landblocks, particles, textures, mesh arenas, portal/UI teardown, and per-frame scratch storage. Add bounded DAT/texture caches, upload budgets, three-frame fence retirement, exact-incarnation appearance reconciliation, frame pacing, and extensive lifetime conformance coverage.\n\nThe seven-destination connected route now cuts peak working/private memory roughly in half, returns Caul to 125-153 FPS locally, and produces no WER or AMD reset.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
3971997689
commit
749e8ceeb1
225 changed files with 29107 additions and 3914 deletions
|
|
@ -14,7 +14,79 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
private readonly EmitterDescRegistry _registry;
|
||||
private readonly Random _rng;
|
||||
private readonly Dictionary<int, ParticleEmitter> _byHandle = new();
|
||||
private readonly List<int> _handleOrder = new();
|
||||
// Handles are monotonic, so sorted indexes preserve retail emitter-spawn
|
||||
// order while making every lifecycle edge O(log E). The old List.Remove
|
||||
// hard-stop path was O(E) and became visible when portal routes retained
|
||||
// thousands of finite/fading emitters.
|
||||
private readonly SortedSet<int> _allHandles = [];
|
||||
private readonly SortedSet<int> _simulationHandles = [];
|
||||
private readonly SortedSet<int> _worldSimulationHandles = [];
|
||||
private readonly SortedSet<int>[] _renderableHandlesByPass =
|
||||
[[], [], []];
|
||||
private readonly SortedSet<int>[] _renderableUnattachedHandlesByPass =
|
||||
[[], [], []];
|
||||
private readonly Dictionary<uint, OwnerEmitterBucket>[] _ownerHandlesByPass =
|
||||
[new(), new(), new()];
|
||||
private readonly List<int> _tickSnapshot = [];
|
||||
private readonly List<int> _scopeHandleScratch = [];
|
||||
|
||||
private sealed class OwnerEmitterBucket
|
||||
{
|
||||
public int LogicalCount;
|
||||
public int FirstRenderableHandle;
|
||||
public List<int>? AdditionalRenderableHandles;
|
||||
|
||||
public void AddRenderable(int handle)
|
||||
{
|
||||
if (FirstRenderableHandle == 0)
|
||||
{
|
||||
FirstRenderableHandle = handle;
|
||||
return;
|
||||
}
|
||||
|
||||
if (handle < FirstRenderableHandle)
|
||||
{
|
||||
(FirstRenderableHandle, handle) = (handle, FirstRenderableHandle);
|
||||
}
|
||||
List<int> additional = AdditionalRenderableHandles ??= [];
|
||||
int index = additional.BinarySearch(handle);
|
||||
if (index < 0)
|
||||
additional.Insert(~index, handle);
|
||||
}
|
||||
|
||||
public void RemoveRenderable(int handle)
|
||||
{
|
||||
if (FirstRenderableHandle == handle)
|
||||
{
|
||||
if (AdditionalRenderableHandles is { Count: > 0 } additional)
|
||||
{
|
||||
FirstRenderableHandle = additional[0];
|
||||
additional.RemoveAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
FirstRenderableHandle = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (AdditionalRenderableHandles is { } others)
|
||||
{
|
||||
int index = others.BinarySearch(handle);
|
||||
if (index >= 0)
|
||||
others.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyRenderableHandlesTo(List<int> destination)
|
||||
{
|
||||
if (FirstRenderableHandle == 0)
|
||||
return;
|
||||
destination.Add(FirstRenderableHandle);
|
||||
if (AdditionalRenderableHandles is { Count: > 0 } additional)
|
||||
destination.AddRange(additional);
|
||||
}
|
||||
}
|
||||
|
||||
private int _nextHandle = 1;
|
||||
private float _time;
|
||||
|
|
@ -29,6 +101,10 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
public int ActiveEmitterCount => _byHandle.Count;
|
||||
public int ActiveParticleCount => _activeParticleCount;
|
||||
|
||||
internal int LastTickEmitterVisitCount { get; private set; }
|
||||
internal int LastRetailViewEmitterVisitCount { get; private set; }
|
||||
internal int LastRenderScopeEmitterVisitCount { get; private set; }
|
||||
|
||||
public int SpawnEmitter(
|
||||
EmitterDesc desc,
|
||||
Vector3 anchor,
|
||||
|
|
@ -59,7 +135,11 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
};
|
||||
|
||||
_byHandle[handle] = emitter;
|
||||
_handleOrder.Add(handle);
|
||||
_allHandles.Add(handle);
|
||||
_simulationHandles.Add(handle);
|
||||
if (visibilityPolicy == ParticleVisibilityPolicy.World)
|
||||
_worldSimulationHandles.Add(handle);
|
||||
AddEmitterToRenderIndexes(emitter);
|
||||
|
||||
for (int i = 0; i < desc.InitialParticles; i++)
|
||||
SpawnOne(emitter, allowWhenFull: false);
|
||||
|
|
@ -133,9 +213,7 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
// Retail DestroyParticleEmitter removes the table entry now; it
|
||||
// does not wait for the next update. This is also required for a
|
||||
// hard-stopped emitter whose cell-less simulation is paused.
|
||||
_byHandle.Remove(handle);
|
||||
_handleOrder.Remove(handle);
|
||||
EmitterDied?.Invoke(handle);
|
||||
RemoveEmitter(handle, em);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -180,8 +258,28 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
int handle,
|
||||
ParticleVisibilityPolicy visibilityPolicy)
|
||||
{
|
||||
if (_byHandle.TryGetValue(handle, out ParticleEmitter? emitter))
|
||||
emitter.VisibilityPolicy = visibilityPolicy;
|
||||
if (!_byHandle.TryGetValue(handle, out ParticleEmitter? emitter)
|
||||
|| emitter.VisibilityPolicy == visibilityPolicy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool wasRenderable = IsRenderable(emitter);
|
||||
if (emitter.VisibilityPolicy == ParticleVisibilityPolicy.World)
|
||||
_worldSimulationHandles.Remove(handle);
|
||||
|
||||
emitter.VisibilityPolicy = visibilityPolicy;
|
||||
if (visibilityPolicy == ParticleVisibilityPolicy.World)
|
||||
{
|
||||
if (emitter.SimulationEnabled)
|
||||
_worldSimulationHandles.Add(handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Examination and dedicated-pass objects bypass world PView.
|
||||
emitter.ViewEligible = true;
|
||||
}
|
||||
RefreshRenderableIndex(emitter, wasRenderable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -200,21 +298,19 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
if (!float.IsFinite(rangeMultiplier) || rangeMultiplier <= 0f)
|
||||
rangeMultiplier = 1f;
|
||||
|
||||
foreach (int handle in _handleOrder)
|
||||
LastRetailViewEmitterVisitCount = 0;
|
||||
foreach (int handle in _worldSimulationHandles)
|
||||
{
|
||||
if (!_byHandle.TryGetValue(handle, out ParticleEmitter? emitter))
|
||||
continue;
|
||||
|
||||
if (emitter.VisibilityPolicy != ParticleVisibilityPolicy.World)
|
||||
{
|
||||
emitter.ViewEligible = true;
|
||||
continue;
|
||||
}
|
||||
LastRetailViewEmitterVisitCount++;
|
||||
bool wasRenderable = IsRenderable(emitter);
|
||||
if (!hasCompletedView)
|
||||
{
|
||||
// With no completed world PView (portal space, login, or a
|
||||
// reset frame), a world cell cannot report IsInView.
|
||||
emitter.ViewEligible = false;
|
||||
RefreshRenderableIndex(emitter, wasRenderable);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -227,6 +323,7 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
&& (float.IsNaN(distance)
|
||||
|| float.IsNaN(maxDistance)
|
||||
|| distance <= maxDistance);
|
||||
RefreshRenderableIndex(emitter, wasRenderable);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,8 +332,15 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
/// </summary>
|
||||
public void SetEmitterPresentationVisible(int handle, bool visible)
|
||||
{
|
||||
if (_byHandle.TryGetValue(handle, out ParticleEmitter? emitter))
|
||||
emitter.PresentationVisible = visible;
|
||||
if (!_byHandle.TryGetValue(handle, out ParticleEmitter? emitter)
|
||||
|| emitter.PresentationVisible == visible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool wasRenderable = IsRenderable(emitter);
|
||||
emitter.PresentationVisible = visible;
|
||||
RefreshRenderableIndex(emitter, wasRenderable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -256,7 +360,19 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
|
||||
if (!enabled)
|
||||
{
|
||||
bool wasRenderable = IsRenderable(emitter);
|
||||
emitter.SimulationEnabled = false;
|
||||
_simulationHandles.Remove(handle);
|
||||
_worldSimulationHandles.Remove(handle);
|
||||
if (emitter.VisibilityPolicy == ParticleVisibilityPolicy.World)
|
||||
{
|
||||
// A spatially withdrawn owner has no current CObjCell::IsInView
|
||||
// result. Invalidate the previous PView decision so re-entry
|
||||
// cannot become renderable until ApplyRetailView evaluates the
|
||||
// owner against a freshly completed view.
|
||||
emitter.ViewEligible = false;
|
||||
RefreshRenderableIndex(emitter, wasRenderable);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -266,6 +382,11 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
emitter.EmittedAccumulator = 0f;
|
||||
}
|
||||
emitter.SimulationEnabled = true;
|
||||
_simulationHandles.Add(handle);
|
||||
if (emitter.VisibilityPolicy == ParticleVisibilityPolicy.World)
|
||||
_worldSimulationHandles.Add(handle);
|
||||
else
|
||||
emitter.ViewEligible = true;
|
||||
}
|
||||
|
||||
/// <summary>True when the given handle still maps to a live emitter.</summary>
|
||||
|
|
@ -287,14 +408,24 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
|
||||
_time += dt;
|
||||
_activeParticleCount = 0;
|
||||
LastTickEmitterVisitCount = 0;
|
||||
|
||||
for (int i = 0; i < _handleOrder.Count; i++)
|
||||
// EmitterDied callbacks may synchronously stop another emitter or
|
||||
// create a replacement. Snapshot the current workset into retained
|
||||
// storage so those mutations cannot invalidate traversal; newly
|
||||
// created emitters begin on the following retail object update.
|
||||
_tickSnapshot.Clear();
|
||||
foreach (int handle in _simulationHandles)
|
||||
_tickSnapshot.Add(handle);
|
||||
|
||||
for (int i = 0; i < _tickSnapshot.Count; i++)
|
||||
{
|
||||
int handle = _handleOrder[i];
|
||||
int handle = _tickSnapshot[i];
|
||||
if (!_byHandle.TryGetValue(handle, out var em))
|
||||
continue;
|
||||
if (!em.SimulationEnabled)
|
||||
continue;
|
||||
LastTickEmitterVisitCount++;
|
||||
bool wasFinishedBeforeUpdate = em.Finished;
|
||||
|
||||
if (em.ViewEligible)
|
||||
|
|
@ -321,10 +452,7 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
// branch may return num_particles == 0 and retire the emitter.
|
||||
if (em.Finished && live == 0 && wasFinishedBeforeUpdate)
|
||||
{
|
||||
_byHandle.Remove(handle);
|
||||
_handleOrder.RemoveAt(i);
|
||||
i--;
|
||||
EmitterDied?.Invoke(handle);
|
||||
RemoveEmitter(handle, em);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -357,6 +485,65 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
/// </summary>
|
||||
public LiveEmitterEnumerable EnumerateEmitters() => new(this);
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates only presentation-visible, view-eligible emitters in the
|
||||
/// requested pass, retaining global spawn order. Renderers should use this
|
||||
/// workset instead of rejecting every logical emitter on every pass.
|
||||
/// </summary>
|
||||
public RenderableEmitterEnumerable EnumerateRenderableEmitters(
|
||||
ParticleRenderPass renderPass) => new(this, renderPass);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the renderable emitters owned by a visibility scope without
|
||||
/// walking unrelated emitters in the same render pass. Results retain
|
||||
/// global emitter-spawn order so equal-distance retail alpha ties remain
|
||||
/// identical to the unscoped path.
|
||||
/// </summary>
|
||||
public void CopyRenderableEmittersForOwners(
|
||||
ParticleRenderPass renderPass,
|
||||
IReadOnlySet<uint> attachedOwnerIds,
|
||||
bool includeUnattached,
|
||||
List<ParticleEmitter> destination,
|
||||
IReadOnlySet<uint>? excludedAttachedOwnerIds = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(attachedOwnerIds);
|
||||
ArgumentNullException.ThrowIfNull(destination);
|
||||
destination.Clear();
|
||||
LastRenderScopeEmitterVisitCount = 0;
|
||||
int passIndex = RenderPassIndex(renderPass);
|
||||
|
||||
if (includeUnattached)
|
||||
{
|
||||
foreach (int handle in _renderableUnattachedHandlesByPass[passIndex])
|
||||
{
|
||||
LastRenderScopeEmitterVisitCount++;
|
||||
if (_byHandle.TryGetValue(handle, out ParticleEmitter? emitter))
|
||||
destination.Add(emitter);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<uint, OwnerEmitterBucket> owners = _ownerHandlesByPass[passIndex];
|
||||
foreach (uint ownerId in attachedOwnerIds)
|
||||
{
|
||||
if (excludedAttachedOwnerIds?.Contains(ownerId) == true
|
||||
|| !owners.TryGetValue(ownerId, out OwnerEmitterBucket? bucket))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_scopeHandleScratch.Clear();
|
||||
bucket.CopyRenderableHandlesTo(_scopeHandleScratch);
|
||||
LastRenderScopeEmitterVisitCount += _scopeHandleScratch.Count;
|
||||
foreach (int handle in _scopeHandleScratch)
|
||||
{
|
||||
if (_byHandle.TryGetValue(handle, out ParticleEmitter? emitter))
|
||||
destination.Add(emitter);
|
||||
}
|
||||
}
|
||||
|
||||
destination.Sort(static (left, right) => left.Handle.CompareTo(right.Handle));
|
||||
}
|
||||
|
||||
public readonly struct LiveEmitterEnumerable : IEnumerable<ParticleEmitter>
|
||||
{
|
||||
private readonly ParticleSystem _owner;
|
||||
|
|
@ -372,7 +559,7 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
|
||||
private static IEnumerable<ParticleEmitter> EnumerateBoxed(ParticleSystem owner)
|
||||
{
|
||||
foreach (int handle in owner._handleOrder)
|
||||
foreach (int handle in owner._allHandles)
|
||||
{
|
||||
if (owner._byHandle.TryGetValue(handle, out ParticleEmitter? emitter))
|
||||
yield return emitter;
|
||||
|
|
@ -382,12 +569,12 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
public struct Enumerator
|
||||
{
|
||||
private readonly ParticleSystem _owner;
|
||||
private int _index;
|
||||
private SortedSet<int>.Enumerator _handles;
|
||||
|
||||
internal Enumerator(ParticleSystem owner)
|
||||
{
|
||||
_owner = owner;
|
||||
_index = -1;
|
||||
_handles = owner._allHandles.GetEnumerator();
|
||||
Current = null!;
|
||||
}
|
||||
|
||||
|
|
@ -395,11 +582,68 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
|
||||
public bool MoveNext()
|
||||
{
|
||||
while (++_index < _owner._handleOrder.Count)
|
||||
while (_handles.MoveNext())
|
||||
{
|
||||
if (_owner._byHandle.TryGetValue(
|
||||
_owner._handleOrder[_index],
|
||||
out ParticleEmitter? emitter))
|
||||
if (_owner._byHandle.TryGetValue(_handles.Current, out ParticleEmitter? emitter))
|
||||
{
|
||||
Current = emitter;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct RenderableEmitterEnumerable : IEnumerable<ParticleEmitter>
|
||||
{
|
||||
private readonly ParticleSystem _owner;
|
||||
private readonly int _passIndex;
|
||||
|
||||
internal RenderableEmitterEnumerable(ParticleSystem owner, ParticleRenderPass renderPass)
|
||||
{
|
||||
_owner = owner;
|
||||
_passIndex = RenderPassIndex(renderPass);
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator() => new(_owner, _passIndex);
|
||||
|
||||
IEnumerator<ParticleEmitter> IEnumerable<ParticleEmitter>.GetEnumerator()
|
||||
=> EnumerateBoxed(_owner, _passIndex).GetEnumerator();
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
=> EnumerateBoxed(_owner, _passIndex).GetEnumerator();
|
||||
|
||||
private static IEnumerable<ParticleEmitter> EnumerateBoxed(
|
||||
ParticleSystem owner,
|
||||
int passIndex)
|
||||
{
|
||||
foreach (int handle in owner._renderableHandlesByPass[passIndex])
|
||||
{
|
||||
if (owner._byHandle.TryGetValue(handle, out ParticleEmitter? emitter))
|
||||
yield return emitter;
|
||||
}
|
||||
}
|
||||
|
||||
public struct Enumerator
|
||||
{
|
||||
private readonly ParticleSystem _owner;
|
||||
private SortedSet<int>.Enumerator _handles;
|
||||
|
||||
internal Enumerator(ParticleSystem owner, int passIndex)
|
||||
{
|
||||
_owner = owner;
|
||||
_handles = owner._renderableHandlesByPass[passIndex].GetEnumerator();
|
||||
Current = null!;
|
||||
}
|
||||
|
||||
public ParticleEmitter Current { get; private set; }
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
while (_handles.MoveNext())
|
||||
{
|
||||
if (_owner._byHandle.TryGetValue(_handles.Current, out ParticleEmitter? emitter))
|
||||
{
|
||||
Current = emitter;
|
||||
return true;
|
||||
|
|
@ -432,7 +676,7 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
|
||||
private static IEnumerable<(ParticleEmitter Emitter, int Index)> EnumerateLiveBoxed(ParticleSystem owner)
|
||||
{
|
||||
foreach (var handle in owner._handleOrder)
|
||||
foreach (var handle in owner._allHandles)
|
||||
{
|
||||
if (!owner._byHandle.TryGetValue(handle, out var em))
|
||||
continue;
|
||||
|
|
@ -449,14 +693,14 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
public struct Enumerator
|
||||
{
|
||||
private readonly ParticleSystem _owner;
|
||||
private int _handleIdx;
|
||||
private SortedSet<int>.Enumerator _handles;
|
||||
private ParticleEmitter? _currentEmitter;
|
||||
private int _particleIdx;
|
||||
|
||||
internal Enumerator(ParticleSystem owner)
|
||||
{
|
||||
_owner = owner;
|
||||
_handleIdx = -1;
|
||||
_handles = owner._allHandles.GetEnumerator();
|
||||
_currentEmitter = null;
|
||||
_particleIdx = -1;
|
||||
}
|
||||
|
|
@ -477,11 +721,10 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
_currentEmitter = null;
|
||||
}
|
||||
|
||||
_handleIdx++;
|
||||
if (_handleIdx >= _owner._handleOrder.Count)
|
||||
if (!_handles.MoveNext())
|
||||
return false;
|
||||
|
||||
if (!_owner._byHandle.TryGetValue(_owner._handleOrder[_handleIdx], out var em))
|
||||
if (!_owner._byHandle.TryGetValue(_handles.Current, out var em))
|
||||
continue;
|
||||
|
||||
_currentEmitter = em;
|
||||
|
|
@ -491,6 +734,126 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
}
|
||||
}
|
||||
|
||||
private void RemoveEmitter(int handle, ParticleEmitter emitter)
|
||||
{
|
||||
if (!_byHandle.Remove(handle))
|
||||
return;
|
||||
|
||||
_allHandles.Remove(handle);
|
||||
_simulationHandles.Remove(handle);
|
||||
_worldSimulationHandles.Remove(handle);
|
||||
int passIndex = RenderPassIndex(emitter.RenderPass);
|
||||
_renderableHandlesByPass[passIndex].Remove(handle);
|
||||
if (emitter.AttachedObjectId == 0)
|
||||
{
|
||||
_renderableUnattachedHandlesByPass[passIndex].Remove(handle);
|
||||
}
|
||||
else if (_ownerHandlesByPass[passIndex].TryGetValue(
|
||||
emitter.AttachedObjectId,
|
||||
out OwnerEmitterBucket? bucket))
|
||||
{
|
||||
bucket.RemoveRenderable(handle);
|
||||
bucket.LogicalCount--;
|
||||
if (bucket.LogicalCount == 0)
|
||||
_ownerHandlesByPass[passIndex].Remove(emitter.AttachedObjectId);
|
||||
}
|
||||
NotifyEmitterDied(handle);
|
||||
}
|
||||
|
||||
private void NotifyEmitterDied(int handle)
|
||||
{
|
||||
Action<int>? callbacks = EmitterDied;
|
||||
if (callbacks is null)
|
||||
return;
|
||||
|
||||
List<Exception>? failures = null;
|
||||
foreach (Action<int> callback in callbacks.GetInvocationList().Cast<Action<int>>())
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(handle);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
(failures ??= []).Add(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (failures is not null)
|
||||
throw new AggregateException(
|
||||
$"One or more emitter-death callbacks failed for handle {handle}.",
|
||||
failures);
|
||||
}
|
||||
|
||||
private void AddEmitterToRenderIndexes(ParticleEmitter emitter)
|
||||
{
|
||||
int passIndex = RenderPassIndex(emitter.RenderPass);
|
||||
OwnerEmitterBucket? ownerBucket = null;
|
||||
if (emitter.AttachedObjectId != 0)
|
||||
{
|
||||
if (!_ownerHandlesByPass[passIndex].TryGetValue(
|
||||
emitter.AttachedObjectId,
|
||||
out ownerBucket))
|
||||
{
|
||||
ownerBucket = new OwnerEmitterBucket();
|
||||
_ownerHandlesByPass[passIndex].Add(emitter.AttachedObjectId, ownerBucket);
|
||||
}
|
||||
ownerBucket.LogicalCount++;
|
||||
}
|
||||
|
||||
if (IsRenderable(emitter))
|
||||
{
|
||||
_renderableHandlesByPass[passIndex].Add(emitter.Handle);
|
||||
if (emitter.AttachedObjectId == 0)
|
||||
_renderableUnattachedHandlesByPass[passIndex].Add(emitter.Handle);
|
||||
else
|
||||
ownerBucket!.AddRenderable(emitter.Handle);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshRenderableIndex(ParticleEmitter emitter, bool wasRenderable)
|
||||
{
|
||||
bool isRenderable = IsRenderable(emitter);
|
||||
if (wasRenderable == isRenderable)
|
||||
return;
|
||||
|
||||
SortedSet<int> index =
|
||||
_renderableHandlesByPass[RenderPassIndex(emitter.RenderPass)];
|
||||
if (isRenderable)
|
||||
index.Add(emitter.Handle);
|
||||
else
|
||||
index.Remove(emitter.Handle);
|
||||
|
||||
int passIndex = RenderPassIndex(emitter.RenderPass);
|
||||
if (emitter.AttachedObjectId == 0)
|
||||
{
|
||||
if (isRenderable)
|
||||
_renderableUnattachedHandlesByPass[passIndex].Add(emitter.Handle);
|
||||
else
|
||||
_renderableUnattachedHandlesByPass[passIndex].Remove(emitter.Handle);
|
||||
}
|
||||
else if (_ownerHandlesByPass[passIndex].TryGetValue(
|
||||
emitter.AttachedObjectId,
|
||||
out OwnerEmitterBucket? bucket))
|
||||
{
|
||||
if (isRenderable)
|
||||
bucket.AddRenderable(emitter.Handle);
|
||||
else
|
||||
bucket.RemoveRenderable(emitter.Handle);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsRenderable(ParticleEmitter emitter)
|
||||
=> emitter.PresentationVisible && emitter.ViewEligible;
|
||||
|
||||
private static int RenderPassIndex(ParticleRenderPass renderPass)
|
||||
{
|
||||
int index = (int)renderPass;
|
||||
if ((uint)index >= 3u)
|
||||
throw new ArgumentOutOfRangeException(nameof(renderPass));
|
||||
return index;
|
||||
}
|
||||
|
||||
private void AdvanceEmitter(ParticleEmitter em)
|
||||
{
|
||||
for (int i = 0; i < em.Particles.Length; i++)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue