sync method
void
sync()
Brings the picture up to date with the simulation.
Called after a step, once a frame. Nothing here reads the clock or decides anything: what a unit does is settled by the time this runs, and a drawing half that made decisions would be a second simulation disagreeing with the first.
Implementation
void sync() {
// **The fog is what decides how many instances are written**, and that is
// the cheapest form the saving could take: a unit nobody can see is not
// culled after being encoded, it is never encoded. The measurement that
// started this genre put the ceiling at the processor writing instances —
// about 0.07 microseconds each, every frame — so the crowd a side cannot
// see is exactly the part of that bill it should not be paying.
final int? side = viewer;
_filled.fillRange(0, _filled.length, 0);
for (final StrategyUnit unit in simulation.units) {
if (side != null && !_showsUnit(side, unit)) continue;
final List<int>? dressed = _byKind[unit.type.name];
final int which = dressed == null
? 0
: dressed[unit.side % dressed.length];
final InstancedMeshNode batch = _batches[which];
final int drawn = _filled[which];
if (drawn >= batch.capacity) continue;
final Vector3 at = unit.position;
final double lift = which == 0 ? _unitGroundLift : 0.0;
_transform
..setRotationY(_headingOf(unit))
..setTranslationRaw(at.x, at.y + lift, at.z);
batch
..setTransform(drawn, _transform)
..setColor(drawn, _woundOf(unit).toLinearColor());
_filled[which] = drawn + 1;
}
for (var i = 0; i < _batches.length; i++) {
_batches[i].count = _filled[i];
}
_syncProps(side);
_syncDeposits(side);
// Buildings are nodes of their own rather than instances: a batch scales
// its copies uniformly by the engine's own account, and buildings are the
// one thing on this map with sizes of their own.
//
// A building appears the first time its side's ground is uncovered and then
// stays, because that is what "explored" means — a hall seen once is
// remembered where it stood, even after the crowd that saw it walked home.
for (var i = _buildings.length; i < simulation.buildings.length; i++) {
_buildings.add(null);
}
for (var i = 0; i < simulation.buildings.length; i++) {
if (_buildings[i] != null) continue;
final Building building = simulation.buildings[i];
if (side != null &&
building.side != side &&
!simulation.fog.knows(side, building.center.x, building.center.z)) {
continue;
}
// A copy each, not the one material shared. It costs a handful of
// objects and it buys the only thing a picking pass is good for here:
// a hall the cursor is over can be lit on its own. Shared, the
// highlight would light every hall on the map at once.
final List<RenderMaterial>? sides = _buildingSides;
final RenderMaterial material =
(sides == null
? _buildingMaterial
: sides[building.side % sides.length])
.copy();
final double height = unitSize.height * 2.5;
final MeshNode node;
final DeviceMesh? sharedMesh = _buildingMesh;
if (sharedMesh != null) {
final Vector3 natural = _buildingMeshSize!;
final double across = building.width / natural.x;
final double along = building.depth / natural.z;
// A real model already stands on its own feet at Y = 0 — no lift to
// add, only the stretch from its authored size to this building's.
// Upright, it rises by the mean of the two ground stretches, so a
// castle laid out a little narrower than its footprint is not also
// squashed to the height of a shed.
node = MeshNode(sharedMesh, material, name: building.name)
..setScale(
across,
_buildingStandsTall ? (across + along) / 2.0 : height / natural.y,
along,
)
..setPosition(
building.center.x,
building.center.y,
building.center.z,
);
} else {
node =
MeshNode(
DeviceMesh.upload(
_device,
CuboidShape(
size: Vector3(building.width, height, building.depth),
).build(),
),
material,
name: building.name,
)..setPosition(
building.center.x,
building.center.y + unitSize.height * 1.25,
building.center.z,
);
}
_buildings[i] = node;
_scene?.add(node);
}
if (side != null) _syncFog(side);
}