Skip to content
Brass Orrery

Orrery Terrain · OrreryTerrainCore module · class

Native::

FJoinStreamWriter

Experimental

The server's half of one late join: a save pinned at S (PlanSave), cut into frames and reordered for the joiner.

Declaration

class FJoinStreamWriter
Header
#include "Native/Net/NetJoin.h"
Module
PublicDependencyModuleNames.Add("OrreryTerrainCore"); in your module’s Build.cs.
Stability
Experimental: supported, may change in a minor version, with a changelog line.
Thread
Construct and Prioritise on the game thread; Prepare on any thread (the save's work), at most once; after it, Produce on any thread, one call at a time, and Read on one thread at a time.

Description

Order. The head; then one start chunk per open stroke tile with a Flatten start, in the head's order; then one mask frame per brush mask the head lists, in its order; then the load set, which is every byte of the save but the sculpt codes (the header, the stack, the paint, the sculpt index with each tile's summary, and the trailer), so the joiner can load and show the sculpted shapes from the summaries; then the codes, a joiner page at a time: first the open strokes', then those the log after S touches (Prioritise), then by distance from the joiner's interest (ties by page row, then column); then the end frame.

Frames. U8 kind, U32 payload length, the payload, then a U64 H5 (HashBytesV5) over kind, length and payload, so any one changed byte is found. Head: the FJoinHead, each tile with a start flag but not its start (at most kJoinMaxHeadBytes). Start: U32 stroke index in the head, U32 tile index in its stroke, then the tile's start (at most kJoinMaxStartBytes in all). Mask: one BrushMask record's full payload (EncodeBrushMaskRecord), compressed, its hash checked after decompression. Bytes: U64 offset in the save, then at most kJoinFrameBytes of it. End: U64 Bytes frames sent, U64 save bytes sent. The head ends with U32 mask count and, per mask, U64 hash and U32 canonical size, then the limits: U32 MaxRadius_mm, I32 MaxStrength_mm, U32 KeepOutMargin_mm, U8 MaxPaintLayer, U8 flags (bit 0 bPaintGroundClass, 1 bPaintHoles, 2 bStamps, the rest 0), U32 MaxStampRadius_mm, I32 MaxStampHeight_mm, U32 MaxStampTiles.

Editing goes on while the join streams: the plan keeps the moment S alive (copy-on-write), and the joiner gets the records after S on the same ordered link.

Producing. Prepare writes the save into a store (FJoinStreamConfig::MakeSaveStore). Produce frames the stream from the store into a ready ring of about FJoinStreamConfig::ReadAheadBytes, and Read copies out of the ring. The frames are the same bytes whichever store holds the save and whenever Produce runs. Only the code pages' order depends on when Prioritise lands relative to Produce, which the reader allows: frames carry their offsets.

Tier 1. Construct and Prioritise on the game thread; Prepare on any thread (the save's work), at most once; after it, Produce on any thread, one call at a time, and Read on one thread at a time. The ring, under its own lock, is all Produce and Read share, and Produce reads the store outside that lock, so Read never waits on the store. Prioritise may run while Prepare, Produce or Read does: it only queues.

Functions

  • FJoinStreamWriter (2 overloads)

    Pin the save and the checkpoint at S.

  • operator=
  • Prepare

    Check the head's size, encode the start chunks, write the save into the store (memory when FJoinStreamConfig::MakeSaveStore is empty), read its header and index back through the store's file and plan the frames.

  • Prioritise

    A record after S is about to go to the joiner: the pages a brush step reads (its window, one tile around) are sent next, so the joiner's replica holds for as short a time as it can.

  • Produce

    Frame the stream ahead of Read: frames go into the ready ring until it holds FJoinStreamConfig::ReadAheadBytes or the end frame is in.

  • WantsProduce

    The ready ring holds under half of FJoinStreamConfig::ReadAheadBytes and frames remain, and the stream is not waiting for the joiner's answer: a Produce is due.

  • WaitsForHeldCodes

    Whether the stream framed everything up to the sculpt codes and waits for the joiner's answer (a HeldCodes stream only).

  • HeldAnswerBytes

    The exact size of the joiner's answer (FJoinStreamReader::CacheAnswer): 0 unless this is a HeldCodes stream.

  • TakeHeldCodes

    Take the joiner's answer, once: the stream then frames only the codes the joiner does not hold.

  • Read

    The next bytes of the stream, at most Capacity, copied out of the ready ring.

  • Done

    The end frame is out (read).

  • Status

    Ok while the stream goes; ReadFailed once a Produce read failed. Any thread.

  • Head

    Complete after Prepare (StateHash, FileSize, FileHash and the code area come from it).

  • StreamBytes

    The whole stream's size, frames included; 0 before Prepare and after ReadFailed. A HeldCodes stream counts every code until its answer is applied.

  • BytesRead

    Read so far.

  • ReadyBytes

    Stream bytes in the ready ring, which Read takes without framing. Any thread.

  • ResidentBytes

    The writer's own heap, bytes: the head, the page plan and the queues, the start chunks not yet framed and the ready ring, plus the save when it is kept in memory.