Docs: reduce over-documentation across gameplay scripts
Trim restated narration, dead commented-out code, and oversized class headers; keep all why-comments (authority, timing, edge cases), tooltips, and serialization caveats. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
882d30797e
commit
066e308cde
21 files changed
+478
-902
No files matched your search
@@ -4,42 +4,28 @@ using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Replicates mesh painting across the network as brush-stamp commands (not pixels). The owner
|
||||
/// captures every committed stamp from its <see cref="MeshPainter"/>, batches the frame's stamps and
|
||||
/// forwards them; remote avatars replay each stamp through their own painter onto the identical
|
||||
/// mesh/UVs. Position/animation come from the other sync components; this only carries the tiny paint
|
||||
/// commands (~tens of bytes each), so two players see each other paint live without streaming the
|
||||
/// 12 MB of paint maps.
|
||||
/// Replicates mesh painting across the network as brush-stamp COMMANDS (tens of bytes each), not
|
||||
/// pixels: the owner batches each frame's committed stamps and forwards them; remote avatars replay
|
||||
/// them through their own painter onto the identical mesh/UVs - live painting without streaming maps.
|
||||
///
|
||||
/// Undo/redo replicates as commands too, not pixels: every replica keeps the SAME per-stroke history
|
||||
/// (see MeshPainter.Undo), so the owner just forwards a per-stroke CHECKPOINT marker and an undo/redo
|
||||
/// RESTORE id. Each peer reproduces the move from its own history. A peer that lacks the target id (a
|
||||
/// late joiner asked to undo past its join) heals by pulling a fresh snapshot from the OWNER - the only
|
||||
/// guaranteed-correct source of the post-restore state, so this works for a remote client and for the
|
||||
/// host's own replica alike. Stamps are capped per frame but their overflow carries to the next frame
|
||||
/// rather than being decimated, and checkpoint/restore markers never overtake the stamps they finalize,
|
||||
/// so every replica's history stays aligned and the heal path stays a rare exception, not the norm.
|
||||
/// Undo/redo replicates as commands too: every replica keeps the SAME per-stroke history (see
|
||||
/// MeshPainter.Undo), so the owner forwards per-stroke CHECKPOINT markers and undo/redo RESTORE ids.
|
||||
/// A peer that lacks the target id (undo past its join point) heals by pulling a fresh snapshot from
|
||||
/// the OWNER - the only guaranteed-correct source. Stamps are capped per frame with the overflow
|
||||
/// carried over (never dropped), and markers never overtake the stamps they finalize, so histories
|
||||
/// stay aligned and the heal path stays rare.
|
||||
///
|
||||
/// Late join: a client that connects AFTER painting happened gets the accumulated result. On spawn it
|
||||
/// pulls a one-time compressed snapshot of the whole map set from the server (chunked, throttled to
|
||||
/// respect the transport's reliable window), buffers any live events that arrive during the transfer,
|
||||
/// and after applying the snapshot replays the buffered events newer than the snapshot's cutoff
|
||||
/// sequence - so no stroke is lost or double-applied across the join window. The applied snapshot also
|
||||
/// becomes the joiner's history baseline.
|
||||
/// Late join: on spawn the client pulls a compressed snapshot from the server (chunked, throttled),
|
||||
/// buffers live events during the transfer, and afterwards replays only those newer than the
|
||||
/// snapshot's cutoff sequence - nothing lost or double-applied across the join window.
|
||||
///
|
||||
/// Respawn carry: a scene switch (lobby <-> map) despawns the avatar and spawns a new prefab, which
|
||||
/// would otherwise start blank. On despawn the owner parks a snapshot of its painting on the persistent
|
||||
/// MatchManager; the replacement avatar's owner takes it on spawn and, once its painter is ready, loads it
|
||||
/// and BroadcastLoadedPaint()s it - the exact path a manual load uses - so the painting follows the player
|
||||
/// across the respawn for every peer. A real disconnect clears the parked copy, so sessions don't bleed.
|
||||
/// On respawn this carry-broadcast and the replica's own late-join request both deliver a full snapshot at
|
||||
/// once, so transfers are id-tagged (only the active one reassembles/applies) and cutoff-ordered (an older
|
||||
/// snapshot never overwrites a newer one) - otherwise the two would share a buffer and decode corrupted.
|
||||
/// Respawn carry: a scene switch despawns the avatar, which would otherwise respawn blank. The owner
|
||||
/// parks a snapshot on the persistent MatchManager; the replacement avatar loads and rebroadcasts it
|
||||
/// (the manual-load path). The carry-broadcast and the replica's own late-join request can overlap, so
|
||||
/// transfers are id-tagged and cutoff-ordered - they never mix buffers or regress to older state.
|
||||
///
|
||||
/// Emission intensity is the one paint property that is NOT baked into the maps (it's a global
|
||||
/// multiplier on the material's _EmissionColor), so it can't ride the stamp/snapshot path. It rides its
|
||||
/// own owner-authoritative NetworkVariable; the remote painter's per-frame RefreshEmissionColor picks up
|
||||
/// the applied field, and a late joiner gets the current value on spawn for free.
|
||||
/// Emission intensity is NOT baked into the maps (a global material multiplier), so it rides its own
|
||||
/// owner-authoritative NetworkVariable instead of the stamp/snapshot path.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class PlayerPaintSync : NetworkBehaviour
|
||||
@@ -78,26 +64,21 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
}
|
||||
private readonly List<OutItem> _outgoing = new List<OutItem>();
|
||||
|
||||
// Global emission-intensity multiplier (MeshPainter.emissionIntensity) — a per-player material value,
|
||||
// not part of the painted maps. Owner-authoritative like the rest; the remote applies it to its
|
||||
// painter, whose per-frame RefreshEmissionColor pushes it to the material (no extra material poke).
|
||||
// Global emission-intensity multiplier - a per-player material value, not part of the painted maps.
|
||||
// The remote applies it to its painter; RefreshEmissionColor pushes it to the material per frame.
|
||||
private readonly NetworkVariable<float> _emissionIntensity =
|
||||
new(1f, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
|
||||
|
||||
// Per-player camouflage knobs on the PlayerCamoLit material (x = HalfLambert, y = HalfLambert
|
||||
// brightness, z = specular dampen). Exactly like emission intensity these are per-player material
|
||||
// values, not painted into the maps, so they ride their own owner-authoritative NetworkVariable. The
|
||||
// remote applies them to its material once its painter is ready and again on every change; a late
|
||||
// joiner gets the current value on spawn for free. The default mirrors Player.mat's authored camo so
|
||||
// the brief pre-publish spawn frame shows no flash (the owner re-publishes the real value once ready).
|
||||
// Per-player camo knobs on the PlayerCamoLit material (x = HalfLambert, y = its brightness,
|
||||
// z = specular dampen) - material values like emission intensity, applied once the remote painter
|
||||
// is ready and on every change. The default mirrors Player.mat's authored camo so the brief
|
||||
// pre-publish spawn frame shows no flash.
|
||||
private readonly NetworkVariable<Vector3> _camo =
|
||||
new(new Vector3(0.5f, 0f, 0.1f), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
|
||||
private bool _camoApplyPending; // remote: apply _camo.Value to the material once IsReady / on change
|
||||
|
||||
// Painted-height knobs (x = occlusion AO threshold, y = relief normal intensity, z = parallax
|
||||
// intensity). Same per-player material values as the camo knobs - not baked into the maps - so they
|
||||
// ride their own owner-authoritative NetworkVariable and apply once the remote painter is ready. The
|
||||
// default mirrors the PlayerCamoLit shader defaults (0.2 / 1.4 / 1).
|
||||
// intensity). Same treatment as the camo knobs; the default mirrors the shader defaults.
|
||||
private readonly NetworkVariable<Vector3> _height =
|
||||
new(new Vector3(0.2f, 1.4f, 1f), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
|
||||
private bool _heightApplyPending; // remote: apply _height.Value to the material once IsReady / on change
|
||||
@@ -125,20 +106,16 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
private bool _snapReadyToApply; // last chunk arrived; apply once the painter is ready
|
||||
private readonly List<NetEvent> _buffered = new List<NetEvent>();
|
||||
|
||||
// A scene-switch respawn delivers TWO full snapshots to the same replica at once: this replica's own
|
||||
// late-join RequestSnapshotRpc AND the owner's carry BroadcastLoadedPaint. Without isolation they share
|
||||
// one reassembly buffer and one End() trigger, so a half-filled/mixed Deflate blob gets decoded -> the
|
||||
// "Corrupted data" warning, paint never applies. Each outgoing transfer carries a unique id; only the
|
||||
// active one fills the buffer and applies, and a stale chunk/End from a superseded transfer is ignored.
|
||||
// A scene-switch respawn delivers TWO snapshots at once (the replica's late-join request AND the
|
||||
// owner's carry broadcast); without isolation they'd share one reassembly buffer and decode a mixed
|
||||
// blob. Each transfer carries a unique id; only the active one fills the buffer and applies.
|
||||
private int _activeXferId;
|
||||
// Highest snapshot cutoff accepted so far. A transfer carrying OLDER state is dropped, so a late blank
|
||||
// late-join (server replica still empty at capture) can't overwrite the newer carry. Equal is accepted
|
||||
// so the owner-snapshot heal path (same cutoff, but the diverged peer genuinely needs it) still works.
|
||||
// Highest snapshot cutoff accepted so far: an OLDER transfer is dropped (a late blank late-join must
|
||||
// not overwrite the newer carry); EQUAL is accepted so the owner-snapshot heal path still works.
|
||||
private int _acceptedCutoffFloor = int.MinValue;
|
||||
|
||||
// Owner-only: painting carried over from the avatar a scene switch just despawned (taken from the
|
||||
// persistent MatchManager on spawn). Applied in LateUpdate once this fresh painter is ready, then
|
||||
// rebroadcast to peers via the same path a manual load uses. Null = nothing to carry.
|
||||
// Owner-only: painting carried over from the just-despawned avatar (taken from MatchManager on
|
||||
// spawn), applied in LateUpdate once this fresh painter is ready. Null = nothing to carry.
|
||||
private byte[] _pendingCarry;
|
||||
|
||||
private void Awake() => _painter = GetComponentInChildren<MeshPainter>(true);
|
||||
@@ -154,8 +131,6 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
_painter.OnHistoryCheckpoint += OnLocalCheckpoint;
|
||||
_painter.OnHistoryRestore += OnLocalRestore;
|
||||
|
||||
// Carry the painting from the avatar a scene switch just despawned (if any) onto this fresh one.
|
||||
// Applied in LateUpdate once the painter has finished setup, then broadcast to peers.
|
||||
if (MatchManager.Instance) _pendingCarry = MatchManager.Instance.TakeCarriedPaint();
|
||||
}
|
||||
else
|
||||
@@ -167,8 +142,8 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
_emissionIntensity.OnValueChanged += OnEmissionChanged;
|
||||
_painter.emissionIntensity = _emissionIntensity.Value;
|
||||
|
||||
// Track the owner's camo knobs; apply the current (late-join) value once the material exists
|
||||
// (deferred to LateUpdate - the setter is a no-op before the painter's Start builds the instance).
|
||||
// Deferred to LateUpdate - the camo setters are a no-op before the painter's Start builds
|
||||
// the material instance.
|
||||
_camo.OnValueChanged += OnCamoChanged;
|
||||
_camoApplyPending = true;
|
||||
|
||||
@@ -176,8 +151,8 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
_height.OnValueChanged += OnHeightChanged;
|
||||
_heightApplyPending = true;
|
||||
|
||||
// The server (host) has been present since the start, so its replica already holds the full
|
||||
// paint via the live stream; only a genuine late joiner needs the baseline pulled over.
|
||||
// The host's replica already holds the full paint via the live stream; only a genuine
|
||||
// late joiner needs the baseline pulled over.
|
||||
if (!IsServer)
|
||||
{
|
||||
_awaitingSnapshot = true;
|
||||
@@ -191,9 +166,8 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
{
|
||||
if (_painter && IsOwner)
|
||||
{
|
||||
// Park the current painting so the avatar this scene switch spawns next can restore it. A real
|
||||
// disconnect clears the parked copy (MatchManager.ReturnToMainOnly), so a fresh session never
|
||||
// inherits it. Captured here, the latest possible point, so last-second strokes are included.
|
||||
// Park the painting for the avatar the scene switch spawns next. Captured here, the latest
|
||||
// possible point, so last-second strokes are included (a real disconnect clears the copy).
|
||||
if (MatchManager.Instance && _painter.TryCaptureSnapshot(out byte[] snap))
|
||||
MatchManager.Instance.StoreCarriedPaint(snap);
|
||||
|
||||
@@ -255,8 +229,8 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
_awaitingSnapshot = false;
|
||||
}
|
||||
|
||||
// Remote replica: apply the owner's camo onto this avatar's material once it exists / whenever it
|
||||
// changed. The owner drives its own material via the HUD + persistence, so it never applies here.
|
||||
// Remote replica: apply the owner's camo once the material exists / on change (the owner drives
|
||||
// its own material via HUD + persistence).
|
||||
if (!IsOwner && _camoApplyPending && _painter && _painter.IsReady)
|
||||
{
|
||||
Vector3 c = _camo.Value;
|
||||
@@ -279,9 +253,8 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
if (!IsOwner)
|
||||
return;
|
||||
|
||||
// Restore a carried-over painting onto this fresh avatar once the painter is ready, then propagate
|
||||
// it to peers exactly like a manual load (BroadcastLoadedPaint). One-shot: cleared whether or not it
|
||||
// took, so a failed/empty carry never retries every frame.
|
||||
// Restore a carried-over painting once the painter is ready, then propagate it like a manual
|
||||
// load. One-shot: cleared whether or not it took, so a failed carry never retries every frame.
|
||||
if (_pendingCarry != null && _painter && _painter.IsPaintReady)
|
||||
{
|
||||
byte[] carry = _pendingCarry;
|
||||
@@ -293,21 +266,18 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
// Publish the owner's global emission intensity when the HUD slider changes it (delta-only; the
|
||||
// NetworkVariable dirties only on an actual change, so assigning the same value is a no-op).
|
||||
// Delta-only publish: the NetworkVariable dirties only on an actual change.
|
||||
if (_painter && _emissionIntensity.Value != _painter.emissionIntensity)
|
||||
_emissionIntensity.Value = _painter.emissionIntensity;
|
||||
|
||||
// Publish the owner's camo knobs when the HUD/persistence changes them (delta-only; the
|
||||
// NetworkVariable dirties only on an actual change). Gated on IsReady so the pre-Start 0-read is
|
||||
// never published - that would briefly flatten remotes' lighting until the real value lands.
|
||||
// Camo/height publish, delta-only and gated on IsReady so the pre-Start 0-read is never
|
||||
// published (it would briefly flatten remotes' lighting until the real value lands).
|
||||
if (_painter && _painter.IsReady)
|
||||
{
|
||||
var camo = new Vector3(_painter.CamoHalfLambert, _painter.CamoHalfLambertBrightness, _painter.CamoSpecularDampen);
|
||||
if (_camo.Value != camo)
|
||||
_camo.Value = camo;
|
||||
|
||||
// Publish the painted-height knobs the same way (delta-only, ready-gated).
|
||||
var height = new Vector3(_painter.OcclusionThreshold, _painter.HeightNormalIntensity, _painter.ParallaxIntensity);
|
||||
if (_height.Value != height)
|
||||
_height.Value = height;
|
||||
@@ -494,10 +464,9 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A peer that can't reproduce a restore from its own history asks the OWNER - the only guaranteed
|
||||
/// correct source of the post-restore state - for a fresh snapshot, addressed back to just this peer.
|
||||
/// Works uniformly for a remote client and for the host's own replica (which can't snapshot itself);
|
||||
/// the previous host short-circuit silently dropped the heal and left the host diverged.
|
||||
/// A peer that can't reproduce a restore from its own history asks the OWNER (the only guaranteed
|
||||
/// correct source of the post-restore state) for a fresh snapshot addressed back to just this peer.
|
||||
/// Works uniformly for a remote client and for the host's own replica.
|
||||
/// </summary>
|
||||
private void RequestSnapshotFallback()
|
||||
{
|
||||
@@ -533,9 +502,8 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
if (!_painter || !_painter.TryCaptureSnapshot(out byte[] data))
|
||||
return;
|
||||
|
||||
// Everything the captured maps already contain: the owner's send counter for our own avatar,
|
||||
// otherwise the last message this server replica has applied. The maps represent the painter's
|
||||
// current history entry, so its checkpoint id tags the baseline the joiner adopts.
|
||||
// Cutoff = everything the captured maps already contain (owner: its send counter; otherwise the
|
||||
// last message this replica applied). The checkpoint id tags the baseline the joiner adopts.
|
||||
int capturedSeq = IsOwner ? _seq : _seqHorizon;
|
||||
int baselineId = _painter.CurrentCheckpointId;
|
||||
LogSync($"serving late-join snapshot to client {p.Receive.SenderClientId} (cutoff {capturedSeq}, baseline {baselineId}, {data.Length} bytes)");
|
||||
@@ -612,12 +580,10 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
|
||||
// ---- Load re-sync (owner -> server -> every other peer) ----
|
||||
//
|
||||
// Loading a saved painting replaces the owner's whole map set at once (no stamps) and clears its undo
|
||||
// history, so the live stream cannot carry it. The owner captures a snapshot, uploads it to the server
|
||||
// chunked (same throttling as the download), and the server re-broadcasts it to every other peer
|
||||
// through the existing snapshot client RPCs - including the host's own replica via its client-id
|
||||
// target. The snapshot's baseline id makes every peer reset its history to the loaded baseline too. A
|
||||
// cutoff sequence drops any in-flight/buffered events already superseded by the loaded baseline.
|
||||
// Loading a saved painting replaces the whole map set and clears undo history, so the live stream
|
||||
// cannot carry it. The owner uploads a snapshot to the server (chunked), which re-broadcasts it to
|
||||
// every other peer; the baseline id resets each peer's history and the cutoff sequence drops any
|
||||
// in-flight/buffered events the loaded baseline supersedes.
|
||||
|
||||
private byte[] _uploadBuffer; // server-side reassembly of the owner's uploaded snapshot
|
||||
private int _uploadCutoff;
|
||||
@@ -687,9 +653,8 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
{
|
||||
if (_uploadBuffer == null) return;
|
||||
|
||||
// Load broadcast: fan out to every connected peer except the owner (who already loaded it
|
||||
// locally). Fallback heal: send only to the single requester. The host, when it is not the owner,
|
||||
// is included by its client id and applies it like any other remote.
|
||||
// Load broadcast: every peer except the owner (who already loaded it locally). Fallback heal:
|
||||
// only the single requester. A non-owner host is included by its client id like any remote.
|
||||
ulong[] targets = _uploadBroadcast ? OtherClientIds(OwnerClientId) : new[] { _uploadTarget };
|
||||
if (targets.Length > 0)
|
||||
StartCoroutine(SendSnapshot(targets, _uploadBuffer, _uploadCutoff, _uploadBaselineId));
|
||||
|
||||
Reference in new issue
Block a user