acdream/src/AcDream.App/Rendering/StaticLiveRootCommitter.cs

87 lines
3.3 KiB
C#

using AcDream.App.Physics;
using AcDream.App.Rendering.Vfx;
using AcDream.App.World;
using AcDream.Core.Physics;
using AcDream.Core.World;
namespace AcDream.App.Rendering;
/// <summary>
/// Commits a changed root produced by retail's
/// <c>CPhysicsObj::animate_static_object</c> to the retained effect pose and
/// collision-shadow projections of the same live object incarnation.
/// </summary>
internal sealed class StaticLiveRootCommitter
{
private readonly ILiveEntityRuntimeSource _runtime;
private readonly ShadowObjectRegistry _shadows;
private readonly LiveWorldOriginState _origin;
private readonly EntityEffectPoseRegistry _effectPoses;
public StaticLiveRootCommitter(
ILiveEntityRuntimeSource runtime,
ShadowObjectRegistry shadows,
LiveWorldOriginState origin,
EntityEffectPoseRegistry effectPoses)
{
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_shadows = shadows ?? throw new ArgumentNullException(nameof(shadows));
_origin = origin ?? throw new ArgumentNullException(nameof(origin));
_effectPoses = effectPoses ?? throw new ArgumentNullException(nameof(effectPoses));
}
public bool Commit(WorldEntity entity, PhysicsBody body)
{
ArgumentNullException.ThrowIfNull(entity);
ArgumentNullException.ThrowIfNull(body);
LiveEntityRuntime? runtime = _runtime.Current;
if (runtime is null
|| !runtime.TryGetRecord(entity.ServerGuid, out LiveEntityRecord record)
|| !ReferenceEquals(record.WorldEntity, entity)
|| !ReferenceEquals(record.PhysicsBody, body))
{
return false;
}
// The retained pose owner follows the CPhysicsObj root even while its
// mesh is hidden. Hidden is presentation state, not logical teardown.
_effectPoses.UpdateRoot(entity);
// EffectPoseChanged observers run synchronously and may delete this
// incarnation (or replace the GUID) before returning. Do not let the
// stale root restore collision shadows for an owner which no longer
// exists. This is the same callback-boundary lifetime rule used by the
// live schedulers and movement controllers.
if (!runtime.TryGetRecord(entity.ServerGuid, out LiveEntityRecord current)
|| !ReferenceEquals(current, record)
|| !ReferenceEquals(current.WorldEntity, entity)
|| !ReferenceEquals(current.PhysicsBody, body))
{
return false;
}
// CObjCell::hide_object removes shadows. Never let a later static
// animation tick re-register collision for a hidden, withdrawn, or
// pending projection; UnHide/re-entry owns restoration.
if (record.ProjectionKind is not LiveEntityProjectionKind.World
|| !record.IsSpatiallyProjected
|| !record.IsSpatiallyVisible
|| runtime.IsHidden(entity.ServerGuid))
{
return true;
}
uint cellId = body.CellPosition.ObjCellId != 0
? body.CellPosition.ObjCellId
: record.FullCellId;
ShadowPositionSynchronizer.Sync(
_shadows,
entity.Id,
body.Position,
body.Orientation,
cellId,
_origin.CenterX,
_origin.CenterY);
return true;
}
}