research: region completion gate (L123, no production change)
The 18 ms gate is one game-thread tick, not compile cost (0.25 ms). MPSC handoff keeps ownership but still polls per tick; velocity look-ahead didn't help (near -0.8 pt, p99 11.77 > 11.00). Both kept as a patch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
bae1144ee4
commit
5e7e0eb1c1
5 files changed
+1288
No files matched your search
@@ -0,0 +1,9 @@
|
|||||||
|
# L123: region completion gate
|
||||||
|
|
||||||
|
Five interleaved 30 m/s UE pairs show the gate is a game-thread poll delay. Core compile wall time is p50 0.251 ms (worker queue plus task p50 0.475 ms); request-to-observe is p50 18.89 ms, p95 21.68 ms. In 95% of traced completions, the next scheduler pass drains the MPSC result and resumes admission in that same pass; a small tail waits 2–4 scheduler frames. This is one tick interval, not separate compile, observe, and admit ticks.
|
||||||
|
|
||||||
|
The worker can safely hand off an immutable result through MPSC with its runtime state and `GenerationEpoch`. The game thread validates both before updating state and admitting tiles. Direct worker-side admission would touch game-thread state unsafely. This queue handoff preserves ownership but still polls once per tick; a game-thread wakeup/continuation is the safe way to try removing that delay. A wakeup was not timed in this round.
|
||||||
|
|
||||||
|
One-step velocity lookahead did not remove the wait (gate p50 19.0 ms). Paired near readiness fell 0.79 percentage points; paired far holes rose 0.14 points. Its worst-run p99 was 11.77 ms (limit 11.00) and worst tick 15.11 ms (limit 14.74). After a 90° turn, 252 of 960 retained ahead keys became stale. At 1.4 m/s it compiled 128 ahead tiles per run; paired near arrival was 2.6 ms faster, while far-clear arrival was 14 ms slower. Keep lookahead off.
|
||||||
|
|
||||||
|
The queue-only runs also missed the p99 limit (worst 11.46 ms), though their worst tick was 14.19 ms. Neither measured option meets the full bar. Headless 30 m/s completed with zero generation failures (82.71% near readiness, 14.21% far holes); this path has no UE region-completion gate. Geometry checks passed 24/24 for Clang/GCC W1/W8/W12, and the fast tier passed.
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Compare L123 exact tile hashes with L104 across strata, compilers, and worker counts."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT = ROOT / "Saved/Linux/l123/hashes"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
OLD = ROOT / "Saved/Linux/l104/hashes"
|
||||||
|
BINARIES = {
|
||||||
|
"clang": ROOT / "Saved/Linux/l123/bin/VoxelForgeCoreHeadless-l123-clang",
|
||||||
|
"gcc": ROOT / "Saved/Linux/l123/bin/VoxelForgeCoreHeadless-l123-gcc",
|
||||||
|
}
|
||||||
|
CAMERAS = {
|
||||||
|
"sw": ((8.0, 16.0, -372.2626953125), 2850),
|
||||||
|
"fi": ((25.8369140625, 3.19921875, -737.0), 2249),
|
||||||
|
"tn": ((19.166015625, -14.2001953125, -753.3740234375), 2233),
|
||||||
|
"vs": ((-20.0625, -5.068359375, -900.353515625), 2329),
|
||||||
|
}
|
||||||
|
TILE = re.compile(
|
||||||
|
r"^\[PipelineTileHash\] lod=(-?\d+) x=(-?\d+) y=(-?\d+) z=(-?\d+) "
|
||||||
|
r"density=(0x[0-9a-f]+) mesh=(0x[0-9a-f]+) payload=(0x[0-9a-f]+) "
|
||||||
|
r"topology=(0x[0-9a-f]+)$", re.M)
|
||||||
|
WHOLE = re.compile(
|
||||||
|
r"^\[PipelineWholeViewHash\] tiles=(\d+) density=(0x[0-9a-f]+) "
|
||||||
|
r"mesh=(0x[0-9a-f]+) payload=(0x[0-9a-f]+) topology=(0x[0-9a-f]+)$", re.M)
|
||||||
|
STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]{64})$", re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def parse(path: Path) -> tuple[dict, tuple[str, ...], str]:
|
||||||
|
content = path.read_text(encoding="utf-8", errors="replace")
|
||||||
|
tiles = {(int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))):
|
||||||
|
m.group(5, 6, 7, 8) for m in TILE.finditer(content)}
|
||||||
|
whole, stamp = WHOLE.search(content), STAMP.search(content)
|
||||||
|
if whole is None or stamp is None or len(tiles) != int(whole.group(1)):
|
||||||
|
raise RuntimeError(f"incomplete hash output: {path}")
|
||||||
|
return tiles, whole.groups(), stamp.group(1)
|
||||||
|
|
||||||
|
|
||||||
|
def run(stratum: str, compiler: str, workers: int) -> dict:
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
log = OUT / f"{stratum}-{compiler}-w{workers}.log"
|
||||||
|
camera, expected_tiles = CAMERAS[stratum]
|
||||||
|
camera_arg = ",".join(f"{v:.12f}" for v in camera) + ",0,0"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||||
|
"timeout", "1740s", str(BINARIES[compiler]), str(DATA),
|
||||||
|
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||||
|
f"--workers={workers}", "--clip-radius=3", "--max-clip-level=8",
|
||||||
|
"--max-level=8", f"--camera={camera_arg}", "--stream-hash-tiles",
|
||||||
|
]
|
||||||
|
started = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as stream:
|
||||||
|
process = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
if process.returncode:
|
||||||
|
raise RuntimeError(f"hash process exited {process.returncode}: {log}")
|
||||||
|
tiles, whole, stamp = parse(log)
|
||||||
|
if len(tiles) != expected_tiles:
|
||||||
|
raise RuntimeError(f"expected {expected_tiles} tiles, got {len(tiles)}: {log}")
|
||||||
|
old_tiles, old_whole, _ = parse(OLD / f"{stratum}-{compiler}-w{workers}.log")
|
||||||
|
clang_tiles, clang_whole, _ = parse(OLD / f"{stratum}-clang-w1.log")
|
||||||
|
|
||||||
|
def difference(reference: dict, candidate: dict) -> tuple[int, int]:
|
||||||
|
keys = set(reference) | set(candidate)
|
||||||
|
return sum(reference.get(key) != candidate.get(key) for key in keys), len(keys)
|
||||||
|
|
||||||
|
old_mismatches, old_keys = difference(old_tiles, tiles)
|
||||||
|
cross_mismatches, cross_keys = difference(clang_tiles, tiles)
|
||||||
|
row = {
|
||||||
|
"stratum": stratum, "compiler": compiler, "workers": workers,
|
||||||
|
"source_stamp": stamp, "tiles": len(tiles),
|
||||||
|
"l104_same_cell_tile_mismatches": old_mismatches,
|
||||||
|
"l104_same_cell_key_count": old_keys,
|
||||||
|
"l104_same_cell_whole_hash_match": whole == old_whole,
|
||||||
|
"clang_w1_tile_mismatches": cross_mismatches,
|
||||||
|
"clang_w1_key_count": cross_keys,
|
||||||
|
"clang_w1_whole_hash_match": whole == clang_whole,
|
||||||
|
"wall_s": round(time.monotonic() - started, 3),
|
||||||
|
"log": str(log.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
print("[L123HashRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
row["tile_hashes"] = {"|".join(map(str, key)): value for key, value in tiles.items()}
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
for path in (*BINARIES.values(), DATA):
|
||||||
|
if not path.is_file():
|
||||||
|
raise SystemExit(f"missing input: {path}")
|
||||||
|
rows = []
|
||||||
|
for stratum in CAMERAS:
|
||||||
|
for compiler in BINARIES:
|
||||||
|
for workers in (1, 8, 12):
|
||||||
|
rows.append(run(stratum, compiler, workers))
|
||||||
|
(OUT / "runs-partial.json").write_text(
|
||||||
|
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
|
||||||
|
failures = [row for row in rows if row["l104_same_cell_tile_mismatches"]
|
||||||
|
or not row["l104_same_cell_whole_hash_match"]
|
||||||
|
or row["clang_w1_tile_mismatches"]
|
||||||
|
or not row["clang_w1_whole_hash_match"]]
|
||||||
|
summary = {"runs": [{key: value for key, value in row.items() if key != "tile_hashes"}
|
||||||
|
for row in rows], "failed_runs": len(failures)}
|
||||||
|
(OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
return 1 if failures else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""One 30 m/s headless movement run for the UE-only region gate experiment."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BIN = ROOT / "Saved/Linux/l108/bin/VoxelForgeCoreHeadless-l108-after-clang"
|
||||||
|
OUT = ROOT / "Saved/Linux/l123/headless"
|
||||||
|
SAMPLE = re.compile(r"^\[L79MovingSample\] (.*)$", re.M)
|
||||||
|
SUMMARY = re.compile(r"^\[L79MovingSummary\] (.*)$", re.M)
|
||||||
|
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||||
|
|
||||||
|
|
||||||
|
def p99(values: list[float]) -> float | None:
|
||||||
|
if not values:
|
||||||
|
return None
|
||||||
|
values.sort()
|
||||||
|
return values[max(0, math.ceil(0.99 * len(values)) - 1)]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
log = OUT / "movement-30mps-r1.log"
|
||||||
|
camera = "19.166015625000,-14.200195312500,-753.374023437500,0,0"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||||
|
"timeout", "1740s", str(BIN), str(DATA), "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3",
|
||||||
|
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||||
|
"--profile-lod-work", "--stream-scheduler-policy=3",
|
||||||
|
"--moving-camera-speed-mps=30.0", "--moving-camera-duration-s=12",
|
||||||
|
"--moving-camera-dwell-s=5",
|
||||||
|
]
|
||||||
|
started = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as stream:
|
||||||
|
process = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
content = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
rows = []
|
||||||
|
for match in SAMPLE.finditer(content):
|
||||||
|
row = dict(FIELDS.findall(match.group(1)))
|
||||||
|
for name in ("near_ready_pct", "far_hole_pct", "queue_update_ms"):
|
||||||
|
if name in row:
|
||||||
|
row[name] = float(row[name])
|
||||||
|
row["phase"] = row.get("phase", "move")
|
||||||
|
rows.append(row)
|
||||||
|
summary = dict(FIELDS.findall(SUMMARY.search(content).group(1))) if SUMMARY.search(content) else {}
|
||||||
|
moving = [row for row in rows if row["phase"] == "move"]
|
||||||
|
updates = [row["queue_update_ms"] for row in moving if "queue_update_ms" in row]
|
||||||
|
result = {
|
||||||
|
"exit_code": process.returncode,
|
||||||
|
"wall_s": round(time.monotonic() - started, 3),
|
||||||
|
"moving_samples": len(moving),
|
||||||
|
"near_ready_moving_mean_pct": sum(r["near_ready_pct"] for r in moving) / max(1, len(moving)),
|
||||||
|
"far_holes_moving_mean_pct": sum(r["far_hole_pct"] for r in moving) / max(1, len(moving)),
|
||||||
|
"update_work_p99_ms": p99(updates),
|
||||||
|
"update_work_max_ms": max(updates, default=None),
|
||||||
|
"generation_failures": int(summary.get("generation_failures", -1)),
|
||||||
|
"log": str(log.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
(OUT / "summary.json").write_text(json.dumps(result, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
print("[L123Headless] " + json.dumps(result, sort_keys=True), flush=True)
|
||||||
|
if process.returncode or not moving or result["generation_failures"] != 0:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Five interleaved 30 m/s UE pairs for the Core region lookahead experiment."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import statistics
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
HOST = Path("/home/jahni/VFHost")
|
||||||
|
EDITOR = Path("/home/jahni/UnrealEngine/Engine/Binaries/Linux/UnrealEditor-Cmd")
|
||||||
|
SPAWN_TN_M = (19.166015625, -14.2001953125, -753.3740234375)
|
||||||
|
SPAWN_FI_M = (25.8369140625, 3.19921875, -737.0)
|
||||||
|
|
||||||
|
|
||||||
|
def percentile(values: list[float], fraction: float) -> float | None:
|
||||||
|
if not values:
|
||||||
|
return None
|
||||||
|
ordered = sorted(values)
|
||||||
|
return ordered[max(0, min(len(ordered) - 1, math.ceil(len(ordered) * fraction) - 1))]
|
||||||
|
|
||||||
|
|
||||||
|
def read_events(path: Path) -> list[dict]:
|
||||||
|
rows = []
|
||||||
|
if path.is_file():
|
||||||
|
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||||
|
if not line.startswith("{"):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
rows.append(json.loads(line))
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def run(scenario: str, condition: str, repetition: int, turn: bool = False) -> dict:
|
||||||
|
lookahead = int(condition in ("lookahead", "turn_lookahead"))
|
||||||
|
if scenario == "movement":
|
||||||
|
speed, start, duration, exit_s, slot, spawn = (
|
||||||
|
3000.0, 6.0, 12.0, 23.0, 0, SPAWN_TN_M)
|
||||||
|
else:
|
||||||
|
speed, start, duration, exit_s, slot, spawn = (
|
||||||
|
140.0, 20.0, 12.0, 37.0, 1, SPAWN_FI_M)
|
||||||
|
|
||||||
|
label = f"{scenario}-{condition}-r{repetition}"
|
||||||
|
output_group = "turn" if turn else scenario
|
||||||
|
out = ROOT / "Saved/Linux/l123" / output_group / condition / "unreal"
|
||||||
|
host_out = HOST / "Plugins/VoxelForge/Saved/Linux/l123" / output_group / condition
|
||||||
|
host_saved = HOST / "Saved/Linux/l123"
|
||||||
|
trace = out / f"{label}.trace.jsonl"
|
||||||
|
abslog = out / f"{label}.log"
|
||||||
|
stdout = out / f"{label}.stdout.log"
|
||||||
|
export = host_out / "output" / label
|
||||||
|
userdir = host_saved / "user" / label
|
||||||
|
zendata = host_saved / "zen" / label
|
||||||
|
ddc = host_saved / "DDC"
|
||||||
|
for path in (out, export, userdir, zendata, ddc):
|
||||||
|
path.mkdir(parents=True, exist_ok=True)
|
||||||
|
trace.unlink(missing_ok=True)
|
||||||
|
spawn_cm = ",".join(f"{value * 100.0:.3f}" for value in spawn)
|
||||||
|
command = [
|
||||||
|
str(EDITOR), str(HOST / "VFHost.uproject"), "-nullrhi", "-unattended",
|
||||||
|
"-nop4", "-nosplash", "-NoSound", "-corelimit=12", "-NoZenAutoLaunch",
|
||||||
|
"-notraceserver", "-nocrashreports", "-DisablePlugins=AndroidFileServer",
|
||||||
|
"-ddc=NoZenLocalFallback", f"-userdir={userdir}", f"-ZenDataPath={zendata}",
|
||||||
|
f"-LocalDataCachePath={ddc}", "-run=VoxelForgeExplore", "-seed=0",
|
||||||
|
"-archetype=TunnelNetwork", f"-slot={slot}", "-modes=export", "-gameconfig=1",
|
||||||
|
"-coreworldtest=1", "-ownerfourstrateplan=1", "-corevoxelsizecm=25",
|
||||||
|
"-coreislandtest=0", "-corefallbacktest=0", "-coreofftest=0", "-coreplantest=0",
|
||||||
|
"-settingsref=/Game/VoxelForge/DA_Settings.DA_Settings", "-corehybridmesher=0",
|
||||||
|
f"-out={export}", "-voxel.StreamingCoarseFirst=1",
|
||||||
|
"-voxel.StreamingDynamicNearReserve=1", "-voxel.StreamingLegacyPendingResultCap=0",
|
||||||
|
"-voxel.CoreRegionConcurrentAdmission=1", "-voxel.StreamingResultBacklogCap=256",
|
||||||
|
"-voxel.StreamingReadyQueueDepth=8", "-voxel.StreamingBackgroundWorkerCap=1",
|
||||||
|
"-voxel.CoreColumnCacheDirectMapped=0", "-voxel.StreamingFarApplySupplement=1",
|
||||||
|
"-voxel.CoreMCGradientMemoization=1", "-voxel.StreamingCollisionLookahead=1",
|
||||||
|
f"-voxel.CoreRegionLookahead={lookahead}", "-voxel.TestMove=1",
|
||||||
|
f"-voxel.TestMoveSpeedCmPerSecond={speed:.1f}",
|
||||||
|
f"-voxel.TestMoveStartSeconds={start:.1f}",
|
||||||
|
f"-voxel.TestMoveDurationSeconds={duration:.1f}",
|
||||||
|
f"-voxel.TestExitSeconds={exit_s:.1f}", "-voxel.TestMoveDirection=1,0,0",
|
||||||
|
"-voxel.StreamingSchedulerPolicy=3", "-voxel.TestExitOnSteady=0",
|
||||||
|
"-coreworldstreamtrace=1", f"-voxel.StartupTraceSpawnCm={spawn_cm}",
|
||||||
|
"-voxel.StartupTraceUntilEndPlay=1", "-voxel.StartupTraceThroughCrossing=1",
|
||||||
|
"-voxel.StartupTraceMaxTiles=16384", f"-voxel.StartupTraceFile={trace}",
|
||||||
|
f"-abslog={abslog}",
|
||||||
|
]
|
||||||
|
if turn:
|
||||||
|
command.extend(["-voxel.TestMoveTurnAfterSeconds=6.0",
|
||||||
|
"-voxel.TestMoveTurnDirection=0,1,0"])
|
||||||
|
started = time.monotonic()
|
||||||
|
wrapped = ["taskset", "-c", "0-11", "timeout", "1740s", *command]
|
||||||
|
with stdout.open("w", encoding="utf-8") as stream:
|
||||||
|
process = subprocess.run(wrapped, cwd=ROOT, stdout=stream,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
events = read_events(trace)
|
||||||
|
samples = [event for event in events if event.get("type") == "streaming_sample"]
|
||||||
|
moving = [event for event in samples if float(event.get("speed_cm_s", 0.0)) >= 50.0]
|
||||||
|
stop_event = next((event for event in events
|
||||||
|
if event.get("type") == "streaming_test_move_stopped"), {})
|
||||||
|
stop_s = float(stop_event.get("t_s", 0.0))
|
||||||
|
move_start = min((float(event.get("t_s", 0.0)) for event in moving), default=float("inf"))
|
||||||
|
dwell = [event for event in samples if float(event.get("t_s", 0.0)) >= stop_s
|
||||||
|
and float(event.get("speed_cm_s", 0.0)) < 50.0]
|
||||||
|
backend = next((event for event in events if event.get("type") == "worker_backend_config"), {})
|
||||||
|
cap = next((event for event in events if event.get("type") == "streaming_worker_cap_readback"), {})
|
||||||
|
if process.returncode or len(moving) < 40 or len(dwell) < 16:
|
||||||
|
raise RuntimeError(f"incomplete UE run {label}: exit={process.returncode}, "
|
||||||
|
f"move={len(moving)}, dwell={len(dwell)}, trace={trace}")
|
||||||
|
if (int(backend.get("core_region_lookahead", -1)) != lookahead
|
||||||
|
or int(backend.get("streaming_far_apply_supplement", -1)) != 1
|
||||||
|
or int(cap.get("streaming_background_worker_cap", -1)) != 1
|
||||||
|
or int(backend.get("effective_tile_slots", -1)) != 8):
|
||||||
|
raise RuntimeError(f"configuration readback mismatch: {backend}; {cap}")
|
||||||
|
|
||||||
|
moving_rows = []
|
||||||
|
for event in moving:
|
||||||
|
desired = event.get("desired_by_lod", [0] * 9)
|
||||||
|
resident = event.get("resident_by_lod", [0] * 9)
|
||||||
|
covered = event.get("covered_by_lod", resident)
|
||||||
|
moving_rows.append({
|
||||||
|
"near_ready_pct": 100.0 * sum(resident[:3]) / max(1, sum(desired[:3])),
|
||||||
|
"lod0_ready_pct": 100.0 * resident[0] / max(1, desired[0]),
|
||||||
|
"lod1_ready_pct": 100.0 * resident[1] / max(1, desired[1]),
|
||||||
|
"lod2_ready_pct": 100.0 * resident[2] / max(1, desired[2]),
|
||||||
|
"far_hole_pct": 100.0 * (sum(desired[3:]) - sum(covered[3:]))
|
||||||
|
/ max(1, sum(desired[3:])),
|
||||||
|
})
|
||||||
|
work = [event for event in events if event.get("type") == "game_thread_work_frame"
|
||||||
|
and move_start <= float(event.get("t_s", 0.0)) < stop_s]
|
||||||
|
ticks = [float(event.get("tick_ms", 0.0)) for event in work]
|
||||||
|
updates = [float(event.get("update_chunks_ms", 0.0)) for event in work]
|
||||||
|
arrival = [event for event in samples if float(event.get("t_s", 0.0)) < start
|
||||||
|
and float(event.get("speed_cm_s", 0.0)) < 50.0]
|
||||||
|
|
||||||
|
def settled(event: dict, end: int) -> bool:
|
||||||
|
desired = event.get("desired_by_lod", [0] * 9)
|
||||||
|
actual = event.get("settled_by_lod", [0] * 9)
|
||||||
|
return sum(desired[:end]) > 0 and sum(actual[:end]) >= sum(desired[:end])
|
||||||
|
|
||||||
|
def far_holes(event: dict) -> float | None:
|
||||||
|
desired = event.get("desired_by_lod", [0] * 9)[3:]
|
||||||
|
covered = event.get("covered_by_lod", [0] * 9)[3:]
|
||||||
|
return 100.0 * (sum(desired) - sum(covered)) / sum(desired) if sum(desired) else None
|
||||||
|
|
||||||
|
near_settled = next((event for event in arrival if settled(event, 3)), None)
|
||||||
|
whole_settled = next((event for event in arrival if settled(event, 9)), None)
|
||||||
|
far_clear = next((event for event in arrival if far_holes(event) == 0.0), None)
|
||||||
|
compile_done = [event for event in events if event.get("type") == "core_region_compile_complete"
|
||||||
|
and move_start <= float(event.get("t_s", 0.0)) < stop_s]
|
||||||
|
compile_requests = [event for event in events
|
||||||
|
if event.get("type") == "core_region_compile_request"
|
||||||
|
and move_start <= float(event.get("t_s", 0.0)) < stop_s]
|
||||||
|
lookahead_turns = [event for event in events
|
||||||
|
if event.get("type") == "core_region_lookahead_turn"]
|
||||||
|
row = {
|
||||||
|
"scenario": scenario, "condition": condition, "repetition": repetition,
|
||||||
|
"speed_mps": speed / 100.0, "core_region_lookahead": lookahead,
|
||||||
|
"exit_code": process.returncode, "wall_s": round(time.monotonic() - started, 3),
|
||||||
|
"moving_samples": len(moving), "dwell_samples": len(dwell),
|
||||||
|
"near_ready_moving_mean_pct": statistics.mean(r["near_ready_pct"] for r in moving_rows),
|
||||||
|
"lod0_ready_moving_mean_pct": statistics.mean(r["lod0_ready_pct"] for r in moving_rows),
|
||||||
|
"lod1_ready_moving_mean_pct": statistics.mean(r["lod1_ready_pct"] for r in moving_rows),
|
||||||
|
"lod2_ready_moving_mean_pct": statistics.mean(r["lod2_ready_pct"] for r in moving_rows),
|
||||||
|
"far_holes_moving_mean_pct": statistics.mean(r["far_hole_pct"] for r in moving_rows),
|
||||||
|
"game_thread_tick_p99_ms": percentile(ticks, 0.99),
|
||||||
|
"game_thread_tick_max_ms": max(ticks, default=None),
|
||||||
|
"stream_update_p99_ms": percentile(updates, 0.99),
|
||||||
|
"stream_update_max_ms": max(updates, default=None),
|
||||||
|
"lod02_settled_t_s": float(near_settled["t_s"]) if near_settled else None,
|
||||||
|
"whole_view_settled_t_s": float(whole_settled["t_s"]) if whole_settled else None,
|
||||||
|
"far_clear_t_s": float(far_clear["t_s"]) if far_clear else None,
|
||||||
|
"far_holes_at_pre_move_pct": far_holes(arrival[-1]) if arrival else None,
|
||||||
|
"pending_gate_p50_ms": percentile(
|
||||||
|
[float(e.get("pending_gate_elapsed_ms", 0.0)) for e in compile_done], 0.50),
|
||||||
|
"compile_queue_p95_ms": percentile(
|
||||||
|
[float(e.get("compile_queue_ms", 0.0)) for e in compile_done], 0.95),
|
||||||
|
"compile_task_p95_ms": percentile(
|
||||||
|
[float(e.get("compile_task_ms", 0.0)) for e in compile_done], 0.95),
|
||||||
|
"compiled_tiles_total": sum(int(e.get("compiled_tiles", 0)) for e in compile_done),
|
||||||
|
"lookahead_tiles_peak": max((int(e.get("lookahead_region_tiles", 0))
|
||||||
|
for e in compile_requests), default=0),
|
||||||
|
"wasted_prefetch_tiles": sum(int(e.get("wasted_prefetch_tiles", 0))
|
||||||
|
for e in compile_done),
|
||||||
|
"lookahead_turn_events": lookahead_turns,
|
||||||
|
"compile_completion_count": len(compile_done),
|
||||||
|
"trace": str(trace.relative_to(ROOT)), "log": str(abslog.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
with (ROOT / "Saved/Linux/l123" / "runs.jsonl").open("a", encoding="utf-8") as stream:
|
||||||
|
stream.write(json.dumps(row, sort_keys=True) + "\n")
|
||||||
|
print("[L123Pair] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def pairs_for(runs: list[dict], scenario: str) -> list[dict]:
|
||||||
|
pairs = []
|
||||||
|
for repetition in range(1, 6):
|
||||||
|
baseline = next(row for row in runs if row["scenario"] == scenario
|
||||||
|
and row["condition"] == "baseline" and row["repetition"] == repetition)
|
||||||
|
candidate = next(row for row in runs if row["scenario"] == scenario
|
||||||
|
and row["condition"] == "lookahead" and row["repetition"] == repetition)
|
||||||
|
pairs.append({"repetition": repetition,
|
||||||
|
"near_ready_delta_pct": candidate["near_ready_moving_mean_pct"]
|
||||||
|
- baseline["near_ready_moving_mean_pct"],
|
||||||
|
"far_holes_delta_pct": candidate["far_holes_moving_mean_pct"]
|
||||||
|
- baseline["far_holes_moving_mean_pct"],
|
||||||
|
"tick_p99_delta_ms": candidate["game_thread_tick_p99_ms"]
|
||||||
|
- baseline["game_thread_tick_p99_ms"],
|
||||||
|
"tick_max_delta_ms": candidate["game_thread_tick_max_ms"]
|
||||||
|
- baseline["game_thread_tick_max_ms"]})
|
||||||
|
return pairs
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--scenario", choices=("movement", "arrival"), required=True)
|
||||||
|
parser.add_argument("--turn-diagnostic", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
if not EDITOR.is_file():
|
||||||
|
raise SystemExit(f"missing Unreal editor: {EDITOR}")
|
||||||
|
root_out = ROOT / "Saved/Linux/l123" / args.scenario
|
||||||
|
root_out.mkdir(parents=True, exist_ok=True)
|
||||||
|
(ROOT / "Saved/Linux/l123").mkdir(parents=True, exist_ok=True)
|
||||||
|
if args.turn_diagnostic:
|
||||||
|
row = run("movement", "turn_lookahead", 1, turn=True)
|
||||||
|
turn_out = ROOT / "Saved/Linux/l123/turn"
|
||||||
|
turn_out.mkdir(parents=True, exist_ok=True)
|
||||||
|
(turn_out / "summary.json").write_text(
|
||||||
|
json.dumps({"run": row}, indent=2) + "\n", encoding="utf-8")
|
||||||
|
return 0
|
||||||
|
runs = []
|
||||||
|
for repetition in range(1, 6):
|
||||||
|
order = ("baseline", "lookahead") if repetition % 2 else ("lookahead", "baseline")
|
||||||
|
for condition in order:
|
||||||
|
runs.append(run(args.scenario, condition, repetition))
|
||||||
|
(root_out / "runs-partial.json").write_text(
|
||||||
|
json.dumps(runs, indent=2) + "\n", encoding="utf-8")
|
||||||
|
pairs = pairs_for(runs, args.scenario)
|
||||||
|
summary = {
|
||||||
|
"design": "Compile/retain the current near region set plus one step along velocity; "
|
||||||
|
"return completion through an Epoch-tagged MPSC queue.",
|
||||||
|
"scenario": args.scenario, "runs": runs, "pairs": pairs,
|
||||||
|
"paired_median_near_ready_delta_pct": statistics.median(
|
||||||
|
pair["near_ready_delta_pct"] for pair in pairs),
|
||||||
|
"paired_median_far_holes_delta_pct": statistics.median(
|
||||||
|
pair["far_holes_delta_pct"] for pair in pairs),
|
||||||
|
"paired_median_tick_p99_delta_ms": statistics.median(
|
||||||
|
pair["tick_p99_delta_ms"] for pair in pairs),
|
||||||
|
"paired_median_tick_max_delta_ms": statistics.median(
|
||||||
|
pair["tick_max_delta_ms"] for pair in pairs),
|
||||||
|
}
|
||||||
|
(root_out / "summary.json").write_text(json.dumps(summary, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,825 @@
|
|||||||
|
diff --git a/Source/VoxelForge/Private/VoxelWorld.cpp b/Source/VoxelForge/Private/VoxelWorld.cpp
|
||||||
|
index 42ac6f5..2c7046b 100644
|
||||||
|
--- a/Source/VoxelForge/Private/VoxelWorld.cpp
|
||||||
|
+++ b/Source/VoxelForge/Private/VoxelWorld.cpp
|
||||||
|
@@ -195,10 +195,13 @@ public:
|
||||||
|
std::atomic<bool> bShuttingDown{false};
|
||||||
|
std::atomic<uint64> NextEditSequence{1};
|
||||||
|
std::atomic<uint64> RegionCompileStartCycles{0};
|
||||||
|
+ std::atomic<uint64> RegionCompileWorkerStartCycles{0};
|
||||||
|
std::atomic<uint64> RegionCompileEndCycles{0};
|
||||||
|
std::atomic<int32> RegionCompileTileCount{0};
|
||||||
|
std::atomic<int32> RegionCompileSerial{0};
|
||||||
|
+ std::atomic<uint32> RegionCompileWorkerThreadId{0};
|
||||||
|
std::atomic<double> RegionCompileWorkerSeconds{0.0};
|
||||||
|
+ std::atomic<double> RegionCompileWallSeconds{0.0};
|
||||||
|
int32 FullResolutionLodCount = 0;
|
||||||
|
FString InitialFailureReason;
|
||||||
|
};
|
||||||
|
@@ -262,6 +265,12 @@ namespace
|
||||||
|
GVoxelForgeCoreRegionConcurrentAdmission,
|
||||||
|
TEXT("Allow tiles independent of a pending Core region compile to proceed; 0 restores the global streaming stop."));
|
||||||
|
|
||||||
|
+ int32 GVoxelForgeCoreRegionLookahead = 0;
|
||||||
|
+ FAutoConsoleVariableRef CVarVoxelForgeCoreRegionLookahead(
|
||||||
|
+ TEXT("voxel.CoreRegionLookahead"),
|
||||||
|
+ GVoxelForgeCoreRegionLookahead,
|
||||||
|
+ TEXT("Retain and compile the next near region set along velocity; 0 disables region lookahead."));
|
||||||
|
+
|
||||||
|
int32 GVoxelForgeStreamingResultBacklogCap = 256;
|
||||||
|
FAutoConsoleVariableRef CVarVoxelForgeStreamingResultBacklogCap(
|
||||||
|
TEXT("voxel.StreamingResultBacklogCap"),
|
||||||
|
@@ -1745,6 +1754,10 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
|
{
|
||||||
|
if (Discard.bObsolete) { ++ObsoleteTileAbortCount; }
|
||||||
|
}
|
||||||
|
+ FCoreRegionCompileResult DiscardedRegionCompile;
|
||||||
|
+ while (CoreRegionCompileResults.Dequeue(DiscardedRegionCompile))
|
||||||
|
+ {
|
||||||
|
+ }
|
||||||
|
UE_LOG(LogTemp, Display, TEXT("[VoxelWorld] EndPlay: workers drained in %.6fs; obsolete_tile_aborts=%d"),
|
||||||
|
FPlatformTime::Seconds() - EndPlayStartSeconds, ObsoleteTileAbortCount);
|
||||||
|
VoxelForgeCoreState.Reset();
|
||||||
|
@@ -2093,6 +2106,11 @@ void AVoxelWorld::BeginPlay()
|
||||||
|
PendingCoreRegionTiles.Reset();
|
||||||
|
PendingCoreRegionTileSet.Reset();
|
||||||
|
PendingCoreRetainedTiles.Reset();
|
||||||
|
+ CoreRegionRetainedDesiredTiles.Reset();
|
||||||
|
+ CoreRegionLookaheadTiles.Reset();
|
||||||
|
+ LastCoreRegionLookaheadCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
|
||||||
|
+ LastCoreRegionLookaheadStep = FIntVector::ZeroValue;
|
||||||
|
+ LastCoreRegionLookaheadGenerationEpoch = GenerationEpoch;
|
||||||
|
CoreRegionCompileTilesInFlight.Reset();
|
||||||
|
LastCoreRegionRequestCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
|
||||||
|
PendingCoreRegionRequestCenter = FIntVector::ZeroValue;
|
||||||
|
@@ -2149,6 +2167,7 @@ void AVoxelWorld::BeginPlay()
|
||||||
|
int32 CommandLineSampleStacks = GVoxelForgeSampleStacks;
|
||||||
|
int32 CommandLineCollisionGate = GVoxelForgeCollisionGate;
|
||||||
|
int32 CommandLineCoreRegionConcurrentAdmission = GVoxelForgeCoreRegionConcurrentAdmission;
|
||||||
|
+ int32 CommandLineCoreRegionLookahead = GVoxelForgeCoreRegionLookahead;
|
||||||
|
int32 CommandLineStreamingCollisionLookahead = GVoxelForgeStreamingCollisionLookahead;
|
||||||
|
int32 CommandLineStreamingResultBacklogCap = GVoxelForgeStreamingResultBacklogCap;
|
||||||
|
int32 CommandLineStreamingReadyQueueDepth = GVoxelForgeStreamingReadyQueueDepth;
|
||||||
|
@@ -2257,6 +2276,11 @@ void AVoxelWorld::BeginPlay()
|
||||||
|
GVoxelForgeCoreRegionConcurrentAdmission =
|
||||||
|
CommandLineCoreRegionConcurrentAdmission != 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
+ if (FParse::Value(FCommandLine::Get(), TEXT("voxel.CoreRegionLookahead="),
|
||||||
|
+ CommandLineCoreRegionLookahead))
|
||||||
|
+ {
|
||||||
|
+ GVoxelForgeCoreRegionLookahead = CommandLineCoreRegionLookahead != 0 ? 1 : 0;
|
||||||
|
+ }
|
||||||
|
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingResultBacklogCap="),
|
||||||
|
CommandLineStreamingResultBacklogCap))
|
||||||
|
{
|
||||||
|
@@ -2747,6 +2771,7 @@ void AVoxelWorld::BeginPlay()
|
||||||
|
"\"streaming_coarse_first\":%d,\"streaming_dynamic_near_reserve\":%d,"
|
||||||
|
"\"streaming_legacy_pending_result_cap\":%d,"
|
||||||
|
"\"streaming_collision_lookahead\":%d,\"core_region_concurrent_admission\":%d,"
|
||||||
|
+ "\"core_region_lookahead\":%d,"
|
||||||
|
"\"collision_gate\":%d,"
|
||||||
|
"\"streaming_result_backlog_cap\":%d,\"streaming_startup_apply_boost\":%d,"
|
||||||
|
"\"streaming_far_apply_supplement\":%d,"
|
||||||
|
@@ -2769,6 +2794,7 @@ void AVoxelWorld::BeginPlay()
|
||||||
|
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
|
||||||
|
GVoxelForgeStreamingLegacyPendingResultCap,
|
||||||
|
GVoxelForgeStreamingCollisionLookahead, GVoxelForgeCoreRegionConcurrentAdmission,
|
||||||
|
+ GVoxelForgeCoreRegionLookahead,
|
||||||
|
GVoxelForgeCollisionGate,
|
||||||
|
GVoxelForgeStreamingResultBacklogCap, GVoxelForgeStreamingStartupApplyBoost,
|
||||||
|
GVoxelForgeStreamingFarApplySupplement,
|
||||||
|
@@ -3246,11 +3272,13 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||||
|
float Speed = 800.0f;
|
||||||
|
float StartSeconds = 20.0f;
|
||||||
|
float MoveDurationSeconds = 0.0f;
|
||||||
|
+ float MoveTurnAfterSeconds = 0.0f;
|
||||||
|
float ExitSeconds = 0.0f;
|
||||||
|
int32 ExitOnSteadyValue = 0;
|
||||||
|
FParse::Value(CommandLine, TEXT("voxel.TestMoveSpeedCmPerSecond="), Speed);
|
||||||
|
FParse::Value(CommandLine, TEXT("voxel.TestMoveStartSeconds="), StartSeconds);
|
||||||
|
FParse::Value(CommandLine, TEXT("voxel.TestMoveDurationSeconds="), MoveDurationSeconds);
|
||||||
|
+ FParse::Value(CommandLine, TEXT("voxel.TestMoveTurnAfterSeconds="), MoveTurnAfterSeconds);
|
||||||
|
FParse::Value(CommandLine, TEXT("voxel.TestExitSeconds="), ExitSeconds);
|
||||||
|
FParse::Value(CommandLine, TEXT("voxel.TestTeleportAfterSeconds="),
|
||||||
|
HeadlessStreamingTestTeleportAtSeconds);
|
||||||
|
@@ -3418,9 +3446,44 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||||
|
{
|
||||||
|
HeadlessStreamingTestDirection = FVector::XAxisVector;
|
||||||
|
}
|
||||||
|
+ FString TurnDirectionText;
|
||||||
|
+ const FString TurnDirectionKey(TEXT("voxel.TestMoveTurnDirection="));
|
||||||
|
+ const int32 TurnDirectionKeyIndex = CommandLineText.Find(
|
||||||
|
+ TurnDirectionKey, ESearchCase::IgnoreCase, ESearchDir::FromStart);
|
||||||
|
+ if (TurnDirectionKeyIndex != INDEX_NONE)
|
||||||
|
+ {
|
||||||
|
+ TurnDirectionText = CommandLineText.Mid(
|
||||||
|
+ TurnDirectionKeyIndex + TurnDirectionKey.Len());
|
||||||
|
+ const int32 DirectionEnd = TurnDirectionText.Find(
|
||||||
|
+ TEXT(" "), ESearchCase::CaseSensitive, ESearchDir::FromStart);
|
||||||
|
+ if (DirectionEnd != INDEX_NONE)
|
||||||
|
+ {
|
||||||
|
+ TurnDirectionText.LeftInline(DirectionEnd, EAllowShrinking::No);
|
||||||
|
+ }
|
||||||
|
+ TurnDirectionText.TrimStartAndEndInline();
|
||||||
|
+ TurnDirectionText.RemoveFromStart(TEXT("\""));
|
||||||
|
+ TurnDirectionText.RemoveFromEnd(TEXT("\""));
|
||||||
|
+ TArray<FString> Components;
|
||||||
|
+ TurnDirectionText.ParseIntoArray(Components, TEXT(","), true);
|
||||||
|
+ if (Components.Num() == 3)
|
||||||
|
+ {
|
||||||
|
+ HeadlessStreamingTestTurnDirection = FVector(
|
||||||
|
+ FCString::Atof(*Components[0]),
|
||||||
|
+ FCString::Atof(*Components[1]),
|
||||||
|
+ FCString::Atof(*Components[2]));
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ HeadlessStreamingTestTurnDirection =
|
||||||
|
+ HeadlessStreamingTestTurnDirection.GetSafeNormal();
|
||||||
|
+ if (HeadlessStreamingTestTurnDirection.IsNearlyZero())
|
||||||
|
+ {
|
||||||
|
+ HeadlessStreamingTestTurnDirection = FVector::YAxisVector;
|
||||||
|
+ }
|
||||||
|
HeadlessStreamingTestSpeedCmPerSecond = FMath::Max(Speed, 0.0f);
|
||||||
|
HeadlessStreamingTestStartSeconds = FMath::Max(StartSeconds, 0.0f);
|
||||||
|
HeadlessStreamingTestMoveDurationSeconds = FMath::Max(MoveDurationSeconds, 0.0f);
|
||||||
|
+ HeadlessStreamingTestTurnAfterSeconds = FMath::Max(MoveTurnAfterSeconds, 0.0f);
|
||||||
|
+ bHeadlessStreamingTestTurnDone = false;
|
||||||
|
bHeadlessStreamingTestMovement = HeadlessStreamingTestSpeedCmPerSecond > 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -3431,15 +3494,20 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||||
|
HeadlessStreamingTestBeginSeconds = FPlatformTime::Seconds();
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeStreamingTest] clean=1 movement=%d speed_cm_s=%.3f start_s=%.3f "
|
||||||
|
- "move_duration_s=%.3f exit_s=%.3f direction=(%.3f,%.3f,%.3f)"),
|
||||||
|
+ "move_duration_s=%.3f turn_after_s=%.3f exit_s=%.3f "
|
||||||
|
+ "direction=(%.3f,%.3f,%.3f) turn_direction=(%.3f,%.3f,%.3f)"),
|
||||||
|
bHeadlessStreamingTestMovement ? 1 : 0,
|
||||||
|
HeadlessStreamingTestSpeedCmPerSecond,
|
||||||
|
HeadlessStreamingTestStartSeconds,
|
||||||
|
HeadlessStreamingTestMoveDurationSeconds,
|
||||||
|
+ HeadlessStreamingTestTurnAfterSeconds,
|
||||||
|
HeadlessStreamingTestExitSeconds,
|
||||||
|
HeadlessStreamingTestDirection.X,
|
||||||
|
HeadlessStreamingTestDirection.Y,
|
||||||
|
- HeadlessStreamingTestDirection.Z);
|
||||||
|
+ HeadlessStreamingTestDirection.Z,
|
||||||
|
+ HeadlessStreamingTestTurnDirection.X,
|
||||||
|
+ HeadlessStreamingTestTurnDirection.Y,
|
||||||
|
+ HeadlessStreamingTestTurnDirection.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 CollisionGateStressValue = 0;
|
||||||
|
@@ -4503,6 +4571,32 @@ void AVoxelWorld::AdvanceHeadlessStreamingTest(
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ if (!bHeadlessStreamingTestTurnDone && HeadlessStreamingTestTurnAfterSeconds > 0.0f
|
||||||
|
+ && ElapsedSeconds - static_cast<double>(HeadlessStreamingTestStartSeconds)
|
||||||
|
+ >= static_cast<double>(HeadlessStreamingTestTurnAfterSeconds))
|
||||||
|
+ {
|
||||||
|
+ bHeadlessStreamingTestTurnDone = true;
|
||||||
|
+ const FVector PreviousDirection = HeadlessStreamingTestDirection;
|
||||||
|
+ HeadlessStreamingTestDirection = HeadlessStreamingTestTurnDirection;
|
||||||
|
+ if (VoxelForgeStartupTrace::IsActive())
|
||||||
|
+ {
|
||||||
|
+ VoxelForgeStartupTrace::RecordEvent(TEXT("streaming_test_move_turned"),
|
||||||
|
+ FString::Printf(
|
||||||
|
+ TEXT("\"elapsed_s\":%s,\"from\":[%.0f,%.0f,%.0f],"
|
||||||
|
+ "\"to\":[%.0f,%.0f,%.0f]"),
|
||||||
|
+ *FString::SanitizeFloat(ElapsedSeconds),
|
||||||
|
+ PreviousDirection.X, PreviousDirection.Y, PreviousDirection.Z,
|
||||||
|
+ HeadlessStreamingTestDirection.X, HeadlessStreamingTestDirection.Y,
|
||||||
|
+ HeadlessStreamingTestDirection.Z));
|
||||||
|
+ }
|
||||||
|
+ UE_LOG(LogTemp, Display,
|
||||||
|
+ TEXT("[VoxelForgeStreamingTest] turn=1 elapsed_s=%.3f from=(%.0f,%.0f,%.0f) "
|
||||||
|
+ "to=(%.0f,%.0f,%.0f)"),
|
||||||
|
+ ElapsedSeconds, PreviousDirection.X, PreviousDirection.Y, PreviousDirection.Z,
|
||||||
|
+ HeadlessStreamingTestDirection.X, HeadlessStreamingTestDirection.Y,
|
||||||
|
+ HeadlessStreamingTestDirection.Z);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
if (HeadlessStreamingTestMoveDurationSeconds > 0.0f
|
||||||
|
&& ElapsedSeconds - static_cast<double>(HeadlessStreamingTestStartSeconds)
|
||||||
|
>= static_cast<double>(HeadlessStreamingTestMoveDurationSeconds))
|
||||||
|
@@ -11258,16 +11352,113 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||||
|
}
|
||||||
|
BuildDesiredTransitionIndex();
|
||||||
|
|
||||||
|
- bool bRetainedSetChanged = false;
|
||||||
|
- int32 RetainedTileCount = 0;
|
||||||
|
+ FIntVector RegionLookaheadStep = FIntVector::ZeroValue;
|
||||||
|
+ if (GVoxelForgeCoreRegionLookahead != 0 && PlayerHeading.Size() >= 50.0f)
|
||||||
|
+ {
|
||||||
|
+ RegionLookaheadStep = GetStreamingHeadingStep(PlayerHeading, PlayerPawn);
|
||||||
|
+ }
|
||||||
|
+ const bool bRegionLookaheadTurn = RegionLookaheadStep != FIntVector::ZeroValue
|
||||||
|
+ && LastCoreRegionLookaheadStep != FIntVector::ZeroValue
|
||||||
|
+ && LastCoreRegionLookaheadStep != RegionLookaheadStep
|
||||||
|
+ && LastCoreRegionLookaheadGenerationEpoch == GenerationEpoch;
|
||||||
|
+ const FIntVector PreviousRegionLookaheadStep = LastCoreRegionLookaheadStep;
|
||||||
|
+ const TArray<FVoxelTileKey> PreviousRegionLookaheadTiles = bRegionLookaheadTurn
|
||||||
|
+ ? CoreRegionLookaheadTiles : TArray<FVoxelTileKey>();
|
||||||
|
+ if (RegionLookaheadStep != FIntVector::ZeroValue
|
||||||
|
+ && (LastCoreRegionLookaheadCenter != Center
|
||||||
|
+ || LastCoreRegionLookaheadStep != RegionLookaheadStep
|
||||||
|
+ || LastCoreRegionLookaheadGenerationEpoch != GenerationEpoch))
|
||||||
|
+ {
|
||||||
|
+ const FVector AheadCameraVoxels = CameraVoxels + FVector(
|
||||||
|
+ static_cast<float>(RegionLookaheadStep.X),
|
||||||
|
+ static_cast<float>(RegionLookaheadStep.Y),
|
||||||
|
+ static_cast<float>(RegionLookaheadStep.Z)) * static_cast<float>(CHUNK_SIZE);
|
||||||
|
+ std::vector<VoxelForgeCore::TileKey> AheadDesired;
|
||||||
|
+ {
|
||||||
|
+ FReadScopeLock Lock(State->WorldLock);
|
||||||
|
+ if (State->World)
|
||||||
|
+ {
|
||||||
|
+ AheadDesired = State->World->desiredTiles(
|
||||||
|
+ {AheadCameraVoxels.X, AheadCameraVoxels.Y, AheadCameraVoxels.Z});
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ CoreRegionLookaheadTiles.Reset();
|
||||||
|
+ TSet<FVoxelTileKey> AheadSet;
|
||||||
|
+ for (const VoxelForgeCore::TileKey& Key : AheadDesired)
|
||||||
|
+ {
|
||||||
|
+ if (Key.lod >= State->FullResolutionLodCount) continue;
|
||||||
|
+ const FVoxelTileKey RuntimeKey(
|
||||||
|
+ FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
|
||||||
|
+ if (!AheadSet.Contains(RuntimeKey))
|
||||||
|
+ {
|
||||||
|
+ AheadSet.Add(RuntimeKey);
|
||||||
|
+ CoreRegionLookaheadTiles.Add(RuntimeKey);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if (bRegionLookaheadTurn)
|
||||||
|
+ {
|
||||||
|
+ TSet<FVoxelTileKey> ActualSet;
|
||||||
|
+ for (const FVoxelTileKey& Key : DesiredSorted) ActualSet.Add(Key);
|
||||||
|
+ int32 StaleRetainedLookaheadTiles = 0;
|
||||||
|
+ {
|
||||||
|
+ FScopeLock RetainedLock(&State->RetainedTilesLock);
|
||||||
|
+ for (const FVoxelTileKey& Key : PreviousRegionLookaheadTiles)
|
||||||
|
+ {
|
||||||
|
+ if (ActualSet.Contains(Key) || AheadSet.Contains(Key)) continue;
|
||||||
|
+ const VoxelForgeCore::TileKey CoreKey{
|
||||||
|
+ {Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level};
|
||||||
|
+ if (State->RetainedTiles.contains(CoreKey))
|
||||||
|
+ {
|
||||||
|
+ ++StaleRetainedLookaheadTiles;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if (VoxelForgeStartupTrace::IsActive())
|
||||||
|
+ {
|
||||||
|
+ VoxelForgeStartupTrace::RecordEvent(TEXT("core_region_lookahead_turn"),
|
||||||
|
+ FString::Printf(
|
||||||
|
+ TEXT("\"previous_step\":[%d,%d,%d],\"new_step\":[%d,%d,%d],"
|
||||||
|
+ "\"previous_prefetch_tiles\":%d,"
|
||||||
|
+ "\"stale_retained_lookahead_tiles\":%d"),
|
||||||
|
+ PreviousRegionLookaheadStep.X, PreviousRegionLookaheadStep.Y,
|
||||||
|
+ PreviousRegionLookaheadStep.Z, RegionLookaheadStep.X,
|
||||||
|
+ RegionLookaheadStep.Y, RegionLookaheadStep.Z,
|
||||||
|
+ PreviousRegionLookaheadTiles.Num(), StaleRetainedLookaheadTiles));
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ LastCoreRegionLookaheadCenter = Center;
|
||||||
|
+ LastCoreRegionLookaheadStep = RegionLookaheadStep;
|
||||||
|
+ LastCoreRegionLookaheadGenerationEpoch = GenerationEpoch;
|
||||||
|
+ }
|
||||||
|
+ else if (GVoxelForgeCoreRegionLookahead == 0)
|
||||||
|
+ {
|
||||||
|
+ CoreRegionLookaheadTiles.Reset();
|
||||||
|
+ LastCoreRegionLookaheadCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
|
||||||
|
+ LastCoreRegionLookaheadStep = FIntVector::ZeroValue;
|
||||||
|
+ LastCoreRegionLookaheadGenerationEpoch = GenerationEpoch;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ CoreRegionRetainedDesiredTiles = DesiredSorted;
|
||||||
|
TSet<FVoxelTileKey> DesiredSet;
|
||||||
|
+ DesiredSet.Reserve(DesiredSorted.Num() + CoreRegionLookaheadTiles.Num());
|
||||||
|
for (const FVoxelTileKey& Key : DesiredSorted) DesiredSet.Add(Key);
|
||||||
|
+ for (const FVoxelTileKey& Key : CoreRegionLookaheadTiles)
|
||||||
|
+ {
|
||||||
|
+ if (!DesiredSet.Contains(Key))
|
||||||
|
+ {
|
||||||
|
+ DesiredSet.Add(Key);
|
||||||
|
+ CoreRegionRetainedDesiredTiles.Add(Key);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ bool bRetainedSetChanged = false;
|
||||||
|
+ int32 RetainedTileCount = 0;
|
||||||
|
TArray<FVoxelTileKey> MissingRetainedFineTiles;
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&State->RetainedTilesLock);
|
||||||
|
RetainedTileCount = static_cast<int32>(State->RetainedTiles.size());
|
||||||
|
bRetainedSetChanged = DesiredSet.Num() != RetainedTileCount;
|
||||||
|
- for (const FVoxelTileKey& Key : DesiredSorted)
|
||||||
|
+ for (const FVoxelTileKey& Key : CoreRegionRetainedDesiredTiles)
|
||||||
|
{
|
||||||
|
const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level};
|
||||||
|
const bool bAlreadyRetained = State->RetainedTiles.contains(CoreKey);
|
||||||
|
@@ -11313,7 +11504,7 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||||
|
{
|
||||||
|
PendingCoreRegionTileSet.Add(Key);
|
||||||
|
}
|
||||||
|
- PendingCoreRetainedTiles = DesiredSorted;
|
||||||
|
+ PendingCoreRetainedTiles = CoreRegionRetainedDesiredTiles;
|
||||||
|
bCoreRegionCompilePending = true;
|
||||||
|
++CoreRegionCompileRequestSerial;
|
||||||
|
if (VoxelForgeStartupTrace::IsActive())
|
||||||
|
@@ -11322,6 +11513,7 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||||
|
TEXT("\"request_serial\":%d,\"reason\":\"%s\",\"center\":[%d,%d,%d],"
|
||||||
|
"\"previous_center\":[%d,%d,%d],\"center_delta\":[%d,%d,%d],"
|
||||||
|
"\"desired_tiles\":%d,\"retained_tiles\":%d,\"missing_region_tiles\":%d,"
|
||||||
|
+ "\"lookahead_region_tiles\":%d,\"lookahead_step\":[%d,%d,%d],"
|
||||||
|
"\"desired_epoch\":%u,\"active_tile_tasks\":%d"),
|
||||||
|
CoreRegionCompileRequestSerial,
|
||||||
|
!bHadPreviousCenter ? TEXT("initial_desired_region_set")
|
||||||
|
@@ -11332,7 +11524,9 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||||
|
bCenterMoved ? Center.X - PreviousCenter.X : 0,
|
||||||
|
bCenterMoved ? Center.Y - PreviousCenter.Y : 0,
|
||||||
|
bCenterMoved ? Center.Z - PreviousCenter.Z : 0,
|
||||||
|
- DesiredSorted.Num(), RetainedTileCount, PendingCoreRegionTiles.Num(), DesiredEpoch,
|
||||||
|
+ DesiredSorted.Num(), RetainedTileCount, PendingCoreRegionTiles.Num(),
|
||||||
|
+ CoreRegionLookaheadTiles.Num(), RegionLookaheadStep.X, RegionLookaheadStep.Y,
|
||||||
|
+ RegionLookaheadStep.Z, DesiredEpoch,
|
||||||
|
ActiveTileTaskCount.load(std::memory_order_relaxed)));
|
||||||
|
}
|
||||||
|
StartPendingCoreRegionCompile();
|
||||||
|
@@ -11363,13 +11557,27 @@ void AVoxelWorld::StartPendingCoreRegionCompile()
|
||||||
|
State->bRegionCompileSucceeded.store(false, std::memory_order_release);
|
||||||
|
const TArray<FVoxelTileKey> MissingTiles = PendingCoreRegionTiles;
|
||||||
|
const TArray<FVoxelTileKey> RetainedTiles = PendingCoreRetainedTiles;
|
||||||
|
+ TSet<FVoxelTileKey> ActualDesiredSet;
|
||||||
|
+ TSet<FVoxelTileKey> PrefetchSet;
|
||||||
|
+ for (const FVoxelTileKey& Key : DesiredSorted) ActualDesiredSet.Add(Key);
|
||||||
|
+ for (const FVoxelTileKey& Key : CoreRegionLookaheadTiles) PrefetchSet.Add(Key);
|
||||||
|
+ TArray<FVoxelTileKey> PrefetchTiles;
|
||||||
|
+ for (const FVoxelTileKey& Key : MissingTiles)
|
||||||
|
+ {
|
||||||
|
+ if (PrefetchSet.Contains(Key) && !ActualDesiredSet.Contains(Key))
|
||||||
|
+ {
|
||||||
|
+ PrefetchTiles.Add(Key);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
const FIntVector CompileCenter = PendingCoreRegionRequestCenter;
|
||||||
|
const bool bAllowConcurrentGeneration = GVoxelForgeCoreRegionConcurrentAdmission != 0;
|
||||||
|
const uint32 TaskEpoch = GenerationEpoch;
|
||||||
|
const uint32 TaskDesiredEpoch = DesiredEpoch;
|
||||||
|
+ const int32 RequestSerial = CoreRegionCompileRequestSerial;
|
||||||
|
const uint32 WorkerLimit = static_cast<uint32>(FMath::Clamp(GetMaxConcurrentTasks(), 1, 16));
|
||||||
|
ActiveCoreRegionCompileCenter = CompileCenter;
|
||||||
|
ActiveCoreRegionCompileRequestSerial = CoreRegionCompileRequestSerial;
|
||||||
|
+ ActiveCoreRegionWastedPrefetchTiles = 0;
|
||||||
|
ActiveCoreRegionCompileStartSeconds = FPlatformTime::Seconds();
|
||||||
|
ActiveCoreRegionCompileStartCycles = FPlatformTime::Cycles64();
|
||||||
|
CoreRegionCompileTilesInFlight.Reset();
|
||||||
|
@@ -11381,25 +11589,32 @@ void AVoxelWorld::StartPendingCoreRegionCompile()
|
||||||
|
1, std::memory_order_relaxed) + 1;
|
||||||
|
State->RegionCompileStartCycles.store(ActiveCoreRegionCompileStartCycles,
|
||||||
|
std::memory_order_release);
|
||||||
|
+ State->RegionCompileWorkerStartCycles.store(0, std::memory_order_release);
|
||||||
|
State->RegionCompileEndCycles.store(0, std::memory_order_release);
|
||||||
|
State->RegionCompileTileCount.store(0, std::memory_order_release);
|
||||||
|
+ State->RegionCompileWorkerThreadId.store(0, std::memory_order_release);
|
||||||
|
State->RegionCompileWorkerSeconds.store(0.0, std::memory_order_release);
|
||||||
|
+ State->RegionCompileWallSeconds.store(0.0, std::memory_order_release);
|
||||||
|
if (VoxelForgeStartupTrace::IsActive())
|
||||||
|
{
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("core_region_compile_start"), FString::Printf(
|
||||||
|
TEXT("\"compile_serial\":%d,\"request_serial\":%d,\"center\":[%d,%d,%d],"
|
||||||
|
"\"missing_region_tiles\":%d,\"retained_tiles\":%d,\"concurrent_admission\":%d,"
|
||||||
|
- "\"active_tile_tasks\":%d,\"active_task_guard\":%d"),
|
||||||
|
+ "\"prefetch_compile_tiles\":%d,"
|
||||||
|
+ "\"active_tile_tasks\":%d,\"active_generation_workers\":%d,"
|
||||||
|
+ "\"active_task_guard\":%d,\"game_thread_id\":%u"),
|
||||||
|
CompileSerial, CoreRegionCompileRequestSerial,
|
||||||
|
CompileCenter.X, CompileCenter.Y, CompileCenter.Z,
|
||||||
|
MissingTiles.Num(), RetainedTiles.Num(), bAllowConcurrentGeneration ? 1 : 0,
|
||||||
|
+ PrefetchTiles.Num(),
|
||||||
|
ActiveTileTaskCount.load(std::memory_order_relaxed),
|
||||||
|
- ActiveTaskCount.load(std::memory_order_relaxed)));
|
||||||
|
+ ActiveGenerationWorkerCount.load(std::memory_order_relaxed),
|
||||||
|
+ ActiveTaskCount.load(std::memory_order_relaxed), FPlatformTLS::GetCurrentThreadId()));
|
||||||
|
}
|
||||||
|
ActiveTaskCount.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
UE::Tasks::Launch(TEXT("VoxelForgeCoreTileRegion"),
|
||||||
|
- [this, State, MissingTiles, RetainedTiles, TaskEpoch, TaskDesiredEpoch, WorkerLimit,
|
||||||
|
- bAllowConcurrentGeneration, CompileSerial]()
|
||||||
|
+ [this, State, MissingTiles, RetainedTiles, PrefetchTiles, CompileCenter, TaskEpoch,
|
||||||
|
+ TaskDesiredEpoch, RequestSerial, WorkerLimit, bAllowConcurrentGeneration, CompileSerial]()
|
||||||
|
{
|
||||||
|
struct FTaskGuard
|
||||||
|
{
|
||||||
|
@@ -11407,12 +11622,23 @@ void AVoxelWorld::StartPendingCoreRegionCompile()
|
||||||
|
~FTaskGuard() { Counter.fetch_sub(1, std::memory_order_release); }
|
||||||
|
} Guard{ActiveTaskCount};
|
||||||
|
|
||||||
|
+ FCoreRegionCompileResult Result;
|
||||||
|
+ Result.RuntimeState = State;
|
||||||
|
+ Result.GenerationEpoch = TaskEpoch;
|
||||||
|
+ Result.DesiredEpoch = TaskDesiredEpoch;
|
||||||
|
+ Result.Center = CompileCenter;
|
||||||
|
+ Result.RequestSerial = RequestSerial;
|
||||||
|
+ Result.CompileSerial = CompileSerial;
|
||||||
|
+ Result.PrefetchTiles = PrefetchTiles;
|
||||||
|
+ Result.WorkerStartCycles = FPlatformTime::Cycles64();
|
||||||
|
+ Result.WorkerThreadId = FPlatformTLS::GetCurrentThreadId();
|
||||||
|
+
|
||||||
|
if (bShuttingDown.load(std::memory_order_acquire)
|
||||||
|
|| State->bShuttingDown.load(std::memory_order_acquire))
|
||||||
|
{
|
||||||
|
- State->RegionCompileEndCycles.store(FPlatformTime::Cycles64(), std::memory_order_release);
|
||||||
|
- State->bRegionCompileInFlight.store(false, std::memory_order_release);
|
||||||
|
- State->bRegionCompileFinished.store(true, std::memory_order_release);
|
||||||
|
+ Result.bAborted = true;
|
||||||
|
+ Result.WorkerEndCycles = FPlatformTime::Cycles64();
|
||||||
|
+ CoreRegionCompileResults.Enqueue(MoveTemp(Result));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<VoxelForgeCore::TileKey> Missing;
|
||||||
|
@@ -11456,38 +11682,14 @@ void AVoxelWorld::StartPendingCoreRegionCompile()
|
||||||
|
FWriteScopeLock Lock(State->WorldLock);
|
||||||
|
CompileAndRetain();
|
||||||
|
}
|
||||||
|
- State->RegionCompileWorkerSeconds.store(CompileStats.workerSeconds,
|
||||||
|
- std::memory_order_release);
|
||||||
|
- State->bRegionCompileSucceeded.store(bCompiled, std::memory_order_release);
|
||||||
|
- if (!bCompiled)
|
||||||
|
- {
|
||||||
|
- State->bRegionCompileFailed.store(true, std::memory_order_release);
|
||||||
|
- UE_LOG(LogTemp, Error,
|
||||||
|
- TEXT("[VoxelForgeCoreRuntime] tile-region compile failed epoch=%u desired_epoch=%u: %s"),
|
||||||
|
- TaskEpoch, TaskDesiredEpoch, UTF8_TO_TCHAR(Error.c_str()));
|
||||||
|
- }
|
||||||
|
- else
|
||||||
|
- {
|
||||||
|
- {
|
||||||
|
- FScopeLock RetainedLock(&State->RetainedTilesLock);
|
||||||
|
- State->RetainedTiles.clear();
|
||||||
|
- for (const VoxelForgeCore::TileKey& Key : Retained)
|
||||||
|
- {
|
||||||
|
- State->RetainedTiles.insert(Key);
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
- State->RegionCompileTileCount.store(
|
||||||
|
- static_cast<int32>(CompiledTileCount), std::memory_order_release);
|
||||||
|
- State->bRebuildDesired.store(true, std::memory_order_release);
|
||||||
|
- UE_LOG(LogTemp, Display,
|
||||||
|
- TEXT("[VoxelForgeCoreRuntime] tile_regions_compiled=%d retained=%d epoch=%u desired_epoch=%u "
|
||||||
|
- "wall_ms=%.3f worker_s=%.3f serial=%d"),
|
||||||
|
- static_cast<int32>(CompiledTileCount), RetainedTiles.Num(), TaskEpoch, TaskDesiredEpoch,
|
||||||
|
- CompileStats.wallSeconds * 1000.0, CompileStats.workerSeconds, CompileSerial);
|
||||||
|
- }
|
||||||
|
- State->RegionCompileEndCycles.store(FPlatformTime::Cycles64(), std::memory_order_release);
|
||||||
|
- State->bRegionCompileInFlight.store(false, std::memory_order_release);
|
||||||
|
- State->bRegionCompileFinished.store(true, std::memory_order_release);
|
||||||
|
+ Result.bSucceeded = bCompiled;
|
||||||
|
+ Result.RetainedTiles = RetainedTiles;
|
||||||
|
+ Result.Error = UTF8_TO_TCHAR(Error.c_str());
|
||||||
|
+ Result.CompiledTileCount = static_cast<int32>(CompiledTileCount);
|
||||||
|
+ Result.WorkerSeconds = CompileStats.workerSeconds;
|
||||||
|
+ Result.CoreWallSeconds = CompileStats.wallSeconds;
|
||||||
|
+ Result.WorkerEndCycles = FPlatformTime::Cycles64();
|
||||||
|
+ CoreRegionCompileResults.Enqueue(MoveTemp(Result));
|
||||||
|
}, UE::Tasks::ETaskPriority::BackgroundNormal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -12331,6 +12533,88 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||||
|
RecordSchedulerSkip(TEXT("core_not_ready"));
|
||||||
|
return; // Core mode never falls through to the legacy generator.
|
||||||
|
}
|
||||||
|
+ FCoreRegionCompileResult RegionResult;
|
||||||
|
+ while (CoreRegionCompileResults.Dequeue(RegionResult))
|
||||||
|
+ {
|
||||||
|
+ if (RegionResult.RuntimeState.IsValid())
|
||||||
|
+ {
|
||||||
|
+ RegionResult.RuntimeState->bRegionCompileInFlight.store(
|
||||||
|
+ false, std::memory_order_release);
|
||||||
|
+ }
|
||||||
|
+ if (RegionResult.bAborted
|
||||||
|
+ || RegionResult.RuntimeState != State
|
||||||
|
+ || RegionResult.GenerationEpoch != GenerationEpoch)
|
||||||
|
+ {
|
||||||
|
+ if (VoxelForgeStartupTrace::IsActive())
|
||||||
|
+ {
|
||||||
|
+ VoxelForgeStartupTrace::RecordEvent(TEXT("core_region_compile_discard"),
|
||||||
|
+ FString::Printf(
|
||||||
|
+ TEXT("\"request_serial\":%d,\"compile_serial\":%d,"
|
||||||
|
+ "\"result_epoch\":%u,\"current_epoch\":%u,\"aborted\":%d"),
|
||||||
|
+ RegionResult.RequestSerial, RegionResult.CompileSerial,
|
||||||
|
+ RegionResult.GenerationEpoch, GenerationEpoch,
|
||||||
|
+ RegionResult.bAborted ? 1 : 0));
|
||||||
|
+ }
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ State->RegionCompileWorkerStartCycles.store(
|
||||||
|
+ RegionResult.WorkerStartCycles, std::memory_order_release);
|
||||||
|
+ State->RegionCompileEndCycles.store(
|
||||||
|
+ RegionResult.WorkerEndCycles, std::memory_order_release);
|
||||||
|
+ State->RegionCompileTileCount.store(
|
||||||
|
+ RegionResult.CompiledTileCount, std::memory_order_release);
|
||||||
|
+ State->RegionCompileWorkerThreadId.store(
|
||||||
|
+ RegionResult.WorkerThreadId, std::memory_order_release);
|
||||||
|
+ State->RegionCompileWorkerSeconds.store(
|
||||||
|
+ RegionResult.WorkerSeconds, std::memory_order_release);
|
||||||
|
+ State->RegionCompileWallSeconds.store(
|
||||||
|
+ RegionResult.CoreWallSeconds, std::memory_order_release);
|
||||||
|
+ State->bRegionCompileSucceeded.store(
|
||||||
|
+ RegionResult.bSucceeded, std::memory_order_release);
|
||||||
|
+ ActiveCoreRegionCompileCenter = RegionResult.Center;
|
||||||
|
+ ActiveCoreRegionCompileRequestSerial = RegionResult.RequestSerial;
|
||||||
|
+ TSet<FVoxelTileKey> CurrentDesiredSet;
|
||||||
|
+ for (const FVoxelTileKey& Key : DesiredSorted) CurrentDesiredSet.Add(Key);
|
||||||
|
+ ActiveCoreRegionWastedPrefetchTiles = 0;
|
||||||
|
+ for (const FVoxelTileKey& Key : RegionResult.PrefetchTiles)
|
||||||
|
+ {
|
||||||
|
+ if (!CurrentDesiredSet.Contains(Key) && !CoreRegionLookaheadTiles.Contains(Key))
|
||||||
|
+ {
|
||||||
|
+ ++ActiveCoreRegionWastedPrefetchTiles;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if (RegionResult.bSucceeded)
|
||||||
|
+ {
|
||||||
|
+ {
|
||||||
|
+ FScopeLock RetainedLock(&State->RetainedTilesLock);
|
||||||
|
+ State->RetainedTiles.clear();
|
||||||
|
+ for (const FVoxelTileKey& Key : RegionResult.RetainedTiles)
|
||||||
|
+ {
|
||||||
|
+ State->RetainedTiles.insert({
|
||||||
|
+ {Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level});
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ State->bRebuildDesired.store(true, std::memory_order_release);
|
||||||
|
+ UE_LOG(LogTemp, Display,
|
||||||
|
+ TEXT("[VoxelForgeCoreRuntime] tile_regions_compiled=%d retained=%d "
|
||||||
|
+ "epoch=%u desired_epoch=%u wall_ms=%.3f worker_s=%.3f serial=%d"),
|
||||||
|
+ RegionResult.CompiledTileCount, RegionResult.RetainedTiles.Num(),
|
||||||
|
+ RegionResult.GenerationEpoch, RegionResult.DesiredEpoch,
|
||||||
|
+ RegionResult.CoreWallSeconds * 1000.0, RegionResult.WorkerSeconds,
|
||||||
|
+ RegionResult.CompileSerial);
|
||||||
|
+ }
|
||||||
|
+ else
|
||||||
|
+ {
|
||||||
|
+ State->bRegionCompileFailed.store(true, std::memory_order_release);
|
||||||
|
+ UE_LOG(LogTemp, Error,
|
||||||
|
+ TEXT("[VoxelForgeCoreRuntime] tile-region compile failed epoch=%u "
|
||||||
|
+ "desired_epoch=%u: %s"),
|
||||||
|
+ RegionResult.GenerationEpoch, RegionResult.DesiredEpoch,
|
||||||
|
+ *RegionResult.Error);
|
||||||
|
+ }
|
||||||
|
+ State->bRegionCompileFinished.store(true, std::memory_order_release);
|
||||||
|
+ }
|
||||||
|
if (State->bRebuildDesired.exchange(false, std::memory_order_acq_rel))
|
||||||
|
{
|
||||||
|
bForceDesiredRebuild = true;
|
||||||
|
@@ -12345,9 +12629,17 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||||
|
State->RegionCompileStartCycles.load(std::memory_order_acquire);
|
||||||
|
const uint64 CompileEndCycles =
|
||||||
|
State->RegionCompileEndCycles.load(std::memory_order_acquire);
|
||||||
|
+ const uint64 CompileWorkerStartCycles =
|
||||||
|
+ State->RegionCompileWorkerStartCycles.load(std::memory_order_acquire);
|
||||||
|
const double CompileElapsedMilliseconds = CompileEndCycles >= CompileStartCycles
|
||||||
|
? FPlatformTime::ToSeconds64(CompileEndCycles - CompileStartCycles) * 1000.0
|
||||||
|
: 0.0;
|
||||||
|
+ const double CompileQueueMilliseconds = CompileWorkerStartCycles >= CompileStartCycles
|
||||||
|
+ ? FPlatformTime::ToSeconds64(CompileWorkerStartCycles - CompileStartCycles) * 1000.0
|
||||||
|
+ : 0.0;
|
||||||
|
+ const double CompileTaskMilliseconds = CompileEndCycles >= CompileWorkerStartCycles
|
||||||
|
+ ? FPlatformTime::ToSeconds64(CompileEndCycles - CompileWorkerStartCycles) * 1000.0
|
||||||
|
+ : 0.0;
|
||||||
|
const double GateElapsedMilliseconds = CoreRegionPendingSinceSeconds > 0.0
|
||||||
|
? FMath::Max(0.0, FPlatformTime::Seconds() - CoreRegionPendingSinceSeconds) * 1000.0
|
||||||
|
: 0.0;
|
||||||
|
@@ -12355,6 +12647,10 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||||
|
State->RegionCompileTileCount.load(std::memory_order_acquire);
|
||||||
|
const double CompileWorkerSeconds =
|
||||||
|
State->RegionCompileWorkerSeconds.load(std::memory_order_acquire);
|
||||||
|
+ const double CompileCoreWallSeconds =
|
||||||
|
+ State->RegionCompileWallSeconds.load(std::memory_order_acquire);
|
||||||
|
+ const uint32 CompileWorkerThreadId =
|
||||||
|
+ State->RegionCompileWorkerThreadId.load(std::memory_order_acquire);
|
||||||
|
const int32 CompileSerial =
|
||||||
|
State->RegionCompileSerial.load(std::memory_order_acquire);
|
||||||
|
const uint64 IndependentSubmissions = CoreRegionIndependentSubmissions;
|
||||||
|
@@ -12364,16 +12660,23 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("core_region_compile_complete"), FString::Printf(
|
||||||
|
TEXT("\"compile_serial\":%d,\"request_serial\":%d,\"center\":[%d,%d,%d],"
|
||||||
|
"\"success\":%d,\"compiled_tiles\":%d,\"compile_elapsed_ms\":%s,"
|
||||||
|
- "\"compile_worker_s\":%s,\"pending_gate_elapsed_ms\":%s,"
|
||||||
|
- "\"independent_submissions\":%llu,"
|
||||||
|
+ "\"compile_queue_ms\":%s,\"compile_task_ms\":%s,"
|
||||||
|
+ "\"compile_core_wall_ms\":%s,\"compile_worker_s\":%s,"
|
||||||
|
+ "\"compile_worker_thread_id\":%u,\"pending_gate_elapsed_ms\":%s,"
|
||||||
|
+ "\"independent_submissions\":%llu,\"wasted_prefetch_tiles\":%d,"
|
||||||
|
"\"dependent_declines\":%llu,\"concurrent_admission\":%d"),
|
||||||
|
CompileSerial, ActiveCoreRegionCompileRequestSerial,
|
||||||
|
ActiveCoreRegionCompileCenter.X, ActiveCoreRegionCompileCenter.Y,
|
||||||
|
ActiveCoreRegionCompileCenter.Z, bRegionCompileSucceeded ? 1 : 0,
|
||||||
|
CompiledTileCount, *FString::SanitizeFloat(CompileElapsedMilliseconds),
|
||||||
|
+ *FString::SanitizeFloat(CompileQueueMilliseconds),
|
||||||
|
+ *FString::SanitizeFloat(CompileTaskMilliseconds),
|
||||||
|
+ *FString::SanitizeFloat(CompileCoreWallSeconds * 1000.0),
|
||||||
|
*FString::SanitizeFloat(CompileWorkerSeconds),
|
||||||
|
+ CompileWorkerThreadId,
|
||||||
|
*FString::SanitizeFloat(GateElapsedMilliseconds),
|
||||||
|
static_cast<unsigned long long>(IndependentSubmissions),
|
||||||
|
+ ActiveCoreRegionWastedPrefetchTiles,
|
||||||
|
static_cast<unsigned long long>(DependentDeclines),
|
||||||
|
GVoxelForgeCoreRegionConcurrentAdmission != 0 ? 1 : 0));
|
||||||
|
}
|
||||||
|
@@ -12388,8 +12691,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||||
|
{
|
||||||
|
FScopeLock RetainedLock(&State->RetainedTilesLock);
|
||||||
|
bRetainedSetMatchesDesired =
|
||||||
|
- DesiredSorted.Num() == static_cast<int32>(State->RetainedTiles.size());
|
||||||
|
- for (const FVoxelTileKey& Key : DesiredSorted)
|
||||||
|
+ CoreRegionRetainedDesiredTiles.Num()
|
||||||
|
+ == static_cast<int32>(State->RetainedTiles.size());
|
||||||
|
+ for (const FVoxelTileKey& Key : CoreRegionRetainedDesiredTiles)
|
||||||
|
{
|
||||||
|
const VoxelForgeCore::TileKey CoreKey{
|
||||||
|
{Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level};
|
||||||
|
@@ -12431,7 +12735,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||||
|
{
|
||||||
|
PendingCoreRegionTileSet.Add(Key);
|
||||||
|
}
|
||||||
|
- PendingCoreRetainedTiles = DesiredSorted;
|
||||||
|
+ PendingCoreRetainedTiles = CoreRegionRetainedDesiredTiles;
|
||||||
|
PendingCoreRegionRequestCenter = DesiredEpochCenterChunk;
|
||||||
|
bCoreRegionCompilePending = true;
|
||||||
|
StartPendingCoreRegionCompile();
|
||||||
|
@@ -12584,6 +12888,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||||
|
&& bWalkableRingReadyForReserve;
|
||||||
|
int32 SchedulerSubmittedByLevel[9] = {};
|
||||||
|
int32 SchedulerLoadAttemptsByLevel[9] = {};
|
||||||
|
+ int32 SchedulerRegionCompileDeclinesByLevel[9] = {};
|
||||||
|
+ int32 SchedulerFarParentDeclinesByLevel[9] = {};
|
||||||
|
int32 SchedulerLevelCapReturns[9] = {};
|
||||||
|
int32 SchedulerLevelCapBreaks[9] = {};
|
||||||
|
int32 SchedulerCriticalFullCapBreaks = 0;
|
||||||
|
@@ -12951,7 +13257,21 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||||
|
if(CoreState.IsValid()
|
||||||
|
&&CoreState->bFarWorldRequired.load(std::memory_order_acquire)
|
||||||
|
&&!CoreState->bFarWorldReady.load(std::memory_order_acquire))
|
||||||
|
+ {
|
||||||
|
+ if (bTraceStreamingScheduler && Tile.Level >= 0 && Tile.Level < 9)
|
||||||
|
+ {
|
||||||
|
+ ++SchedulerFarParentDeclinesByLevel[Tile.Level];
|
||||||
|
+ }
|
||||||
|
return;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if (bTraceStreamingScheduler && bUseVoxelForgeCore && bCoreRegionCompilePending
|
||||||
|
+ && GVoxelForgeCoreRegionConcurrentAdmission != 0
|
||||||
|
+ && (PendingCoreRegionTileSet.Contains(Tile)
|
||||||
|
+ || CoreRegionCompileTilesInFlight.Contains(Tile))
|
||||||
|
+ && Tile.Level >= 0 && Tile.Level < 9)
|
||||||
|
+ {
|
||||||
|
+ ++SchedulerRegionCompileDeclinesByLevel[Tile.Level];
|
||||||
|
}
|
||||||
|
if (Tile.Level >= 0 && Tile.Level < 9)
|
||||||
|
{
|
||||||
|
@@ -13661,10 +13981,37 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||||
|
const double BusySeconds = FPlatformTime::ToSeconds64(WorkerCyclesDelta);
|
||||||
|
const double SchedulerUpdateMilliseconds = (SchedulerTraceNow
|
||||||
|
- StreamingUpdateStartSeconds) * 1000.0;
|
||||||
|
+ int32 CriticalMissingTiles = 0;
|
||||||
|
+ for (const FVoxelTileKey& Tile : CriticalDesiredTiles)
|
||||||
|
+ {
|
||||||
|
+ if (!LoadedTiles.Contains(Tile) && !CollisionNotRequiredTiles.Contains(Tile))
|
||||||
|
+ {
|
||||||
|
+ ++CriticalMissingTiles;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ int32 CollisionGateMissingTiles = 0;
|
||||||
|
+ for (const FVoxelTileKey& Tile : CollisionGateDesiredTiles)
|
||||||
|
+ {
|
||||||
|
+ if (!LoadedTiles.Contains(Tile) && !CollisionNotRequiredTiles.Contains(Tile))
|
||||||
|
+ {
|
||||||
|
+ ++CollisionGateMissingTiles;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ const FIntVector DesiredCenterLag = DesiredEpochCenterChunk - CenterChunk;
|
||||||
|
+ const TSharedPtr<FVoxelForgeCoreRuntimeState, ESPMode::ThreadSafe> TraceCoreState =
|
||||||
|
+ VoxelForgeCoreState;
|
||||||
|
+ const bool bFarWorldRequiredForTrace = TraceCoreState.IsValid()
|
||||||
|
+ && TraceCoreState->bFarWorldRequired.load(std::memory_order_relaxed);
|
||||||
|
+ const bool bFarWorldReadyForTrace = TraceCoreState.IsValid()
|
||||||
|
+ && TraceCoreState->bFarWorldReady.load(std::memory_order_relaxed);
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("scheduler_admission"), FString::Printf(
|
||||||
|
TEXT("\"frame\":%llu,\"center\":[%d,%d,%d],\"desired_rebuilt\":%d,"
|
||||||
|
"\"submission_loop_ran\":%d,\"all_chunks_loaded\":%d,"
|
||||||
|
"\"walkable_ready\":%d,\"moving\":%d,"
|
||||||
|
+ "\"pawn_collision_gate_engaged\":%d,\"critical_tiles_ready\":%d,"
|
||||||
|
+ "\"critical_missing\":%d,\"collision_gate_missing\":%d,"
|
||||||
|
+ "\"desired_center_lag\":[%d,%d,%d],\"desired_center_current\":%d,"
|
||||||
|
+ "\"far_world_required\":%d,\"far_world_ready\":%d,"
|
||||||
|
"\"core_region_compile_pending\":%d,\"core_ready\":%d,\"coarse_first\":%d,"
|
||||||
|
"\"dynamic_near_reserve\":%d,\"legacy_pending_result_cap\":%d,"
|
||||||
|
"\"core_region_concurrent_admission\":%d,"
|
||||||
|
@@ -13685,6 +14032,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||||
|
"\"level_candidate_scan_ms\":%s,\"passage_score_ms\":%s,"
|
||||||
|
"\"level_sort_ms\":%s,\"level_admit_ms\":%s,"
|
||||||
|
"\"desired_missing_by_lod\":%s,\"loaded_by_lod\":%s,\"pending_by_lod\":%s,"
|
||||||
|
+ "\"region_compile_declines_by_lod\":%s,"
|
||||||
|
+ "\"far_parent_declines_by_lod\":%s,"
|
||||||
|
"\"submitted_by_lod\":%s,\"load_attempts_by_lod\":%s,"
|
||||||
|
"\"admission_cap_by_lod\":%s,\"declined_rule_by_lod\":%s,"
|
||||||
|
"\"level_cap_returns_by_lod\":%s,\"level_cap_breaks_by_lod\":%s,"
|
||||||
|
@@ -13695,6 +14044,11 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||||
|
static_cast<unsigned long long>(GFrameCounter), CenterChunk.X, CenterChunk.Y, CenterChunk.Z,
|
||||||
|
bDesiredRebuiltThisUpdate ? 1 : 0, bSubmissionLoopRan ? 1 : 0, bAllChunksLoaded ? 1 : 0,
|
||||||
|
bWalkableRingReadyForReserve ? 1 : 0, bPawnMoving ? 1 : 0,
|
||||||
|
+ bPawnCollisionGateEngaged ? 1 : 0, CriticalMissingTiles == 0 ? 1 : 0,
|
||||||
|
+ CriticalMissingTiles, CollisionGateMissingTiles,
|
||||||
|
+ DesiredCenterLag.X, DesiredCenterLag.Y, DesiredCenterLag.Z,
|
||||||
|
+ DesiredCenterLag == FIntVector::ZeroValue ? 1 : 0,
|
||||||
|
+ bFarWorldRequiredForTrace ? 1 : 0, bFarWorldReadyForTrace ? 1 : 0,
|
||||||
|
bCoreRegionCompilePending ? 1 : 0, IsVoxelForgeCoreReady() ? 1 : 0,
|
||||||
|
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
|
||||||
|
GVoxelForgeStreamingLegacyPendingResultCap,
|
||||||
|
@@ -13721,7 +14075,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||||
|
*FString::SanitizeFloat(LevelSortMilliseconds),
|
||||||
|
*FString::SanitizeFloat(LevelAdmissionMilliseconds),
|
||||||
|
*IntArrayJson(DesiredMissingByLevel), *IntArrayJson(LoadedByLevel),
|
||||||
|
- *IntArrayJson(PendingByLevel), *IntArrayJson(SchedulerSubmittedByLevel),
|
||||||
|
+ *IntArrayJson(PendingByLevel), *IntArrayJson(SchedulerRegionCompileDeclinesByLevel),
|
||||||
|
+ *IntArrayJson(SchedulerFarParentDeclinesByLevel), *IntArrayJson(SchedulerSubmittedByLevel),
|
||||||
|
*IntArrayJson(SchedulerLoadAttemptsByLevel), *IntArrayJson(AdmissionCapByLevel),
|
||||||
|
*IntArrayJson(DeclinedRuleByLevel), *IntArrayJson(SchedulerLevelCapReturns),
|
||||||
|
*IntArrayJson(SchedulerLevelCapBreaks), FullCapDeclinedTiles, ReserveDeclinedTiles,
|
||||||
|
diff --git a/Source/VoxelForge/Public/VoxelWorld.h b/Source/VoxelForge/Public/VoxelWorld.h
|
||||||
|
index b9fa34c..5979f29 100644
|
||||||
|
--- a/Source/VoxelForge/Public/VoxelWorld.h
|
||||||
|
+++ b/Source/VoxelForge/Public/VoxelWorld.h
|
||||||
|
@@ -207,6 +207,28 @@ struct FChunkResult
|
||||||
|
TArray<FVector> SurfaceWitnesses;
|
||||||
|
};
|
||||||
|
|
||||||
|
+/** Worker-to-game-thread completion for immutable Core tile-region publication. */
|
||||||
|
+struct FCoreRegionCompileResult
|
||||||
|
+{
|
||||||
|
+ TSharedPtr<FVoxelForgeCoreRuntimeState, ESPMode::ThreadSafe> RuntimeState;
|
||||||
|
+ TArray<FVoxelTileKey> RetainedTiles;
|
||||||
|
+ TArray<FVoxelTileKey> PrefetchTiles;
|
||||||
|
+ FString Error;
|
||||||
|
+ FIntVector Center = FIntVector::ZeroValue;
|
||||||
|
+ uint32 GenerationEpoch = 0;
|
||||||
|
+ uint32 DesiredEpoch = 0;
|
||||||
|
+ uint64 WorkerStartCycles = 0;
|
||||||
|
+ uint64 WorkerEndCycles = 0;
|
||||||
|
+ uint32 WorkerThreadId = 0;
|
||||||
|
+ int32 CompileSerial = 0;
|
||||||
|
+ int32 RequestSerial = 0;
|
||||||
|
+ int32 CompiledTileCount = 0;
|
||||||
|
+ double WorkerSeconds = 0.0;
|
||||||
|
+ double CoreWallSeconds = 0.0;
|
||||||
|
+ bool bSucceeded = false;
|
||||||
|
+ bool bAborted = false;
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
/** One worker-produced mesh identity emitted only by the explicit parity harness. */
|
||||||
|
struct FVoxelTileHashDumpRecord
|
||||||
|
{
|
||||||
|
@@ -798,6 +820,11 @@ private:
|
||||||
|
TArray<FVoxelTileKey> PendingCoreRegionTiles;
|
||||||
|
TSet<FVoxelTileKey> PendingCoreRegionTileSet;
|
||||||
|
TArray<FVoxelTileKey> PendingCoreRetainedTiles;
|
||||||
|
+ TArray<FVoxelTileKey> CoreRegionRetainedDesiredTiles;
|
||||||
|
+ TArray<FVoxelTileKey> CoreRegionLookaheadTiles;
|
||||||
|
+ FIntVector LastCoreRegionLookaheadCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
|
||||||
|
+ FIntVector LastCoreRegionLookaheadStep = FIntVector::ZeroValue;
|
||||||
|
+ uint32 LastCoreRegionLookaheadGenerationEpoch = 0;
|
||||||
|
TSet<FVoxelTileKey> CoreRegionCompileTilesInFlight;
|
||||||
|
FIntVector LastCoreRegionRequestCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
|
||||||
|
FIntVector PendingCoreRegionRequestCenter = FIntVector::ZeroValue;
|
||||||
|
@@ -810,6 +837,7 @@ private:
|
||||||
|
uint64 CoreRegionDependentDeclines = 0;
|
||||||
|
int32 CoreRegionCompileRequestSerial = 0;
|
||||||
|
int32 ActiveCoreRegionCompileRequestSerial = 0;
|
||||||
|
+ int32 ActiveCoreRegionWastedPrefetchTiles = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
@@ -1139,6 +1167,7 @@ public:
|
||||||
|
TQueue<FChunkResult, EQueueMode::Mpsc> EditedProcessQueue;
|
||||||
|
TQueue<FChunkResult, EQueueMode::Mpsc> NearProcessQueue;
|
||||||
|
TQueue<FChunkResult, EQueueMode::Mpsc> ProcessQueue;
|
||||||
|
+ TQueue<FCoreRegionCompileResult, EQueueMode::Mpsc> CoreRegionCompileResults;
|
||||||
|
// The cap counts completed MPSC results plus admitted tile tasks, so a slow game-thread apply
|
||||||
|
// cannot accumulate an unbounded payload queue while completed work never consumes worker slots.
|
||||||
|
// Le plafond compte les resultats MPSC et les taches admises; un apply lent ne peut donc pas
|
||||||
|
@@ -1362,6 +1391,9 @@ public:
|
||||||
|
float HeadlessStreamingTestSpeedCmPerSecond = 0.0f;
|
||||||
|
float HeadlessStreamingTestStartSeconds = 0.0f;
|
||||||
|
float HeadlessStreamingTestMoveDurationSeconds = 0.0f;
|
||||||
|
+ float HeadlessStreamingTestTurnAfterSeconds = 0.0f;
|
||||||
|
+ bool bHeadlessStreamingTestTurnDone = false;
|
||||||
|
+ FVector HeadlessStreamingTestTurnDirection = FVector::YAxisVector;
|
||||||
|
float HeadlessStreamingTestExitSeconds = 0.0f;
|
||||||
|
float HeadlessStreamingTestTeleportAtSeconds = 0.0f;
|
||||||
|
float HeadlessStreamingTestTeleportDistanceCm = 0.0f;
|
||||||
Reference in new issue
Block a user