feat(O-T7): drop WB project references; complete extraction
End of Phase O extraction. Final cleanup: - Dropped <ProjectReference> entries to WorldBuilder.Shared and Chorizite.OpenGLSDLBackend from both AcDream.App.csproj and AcDream.Core.csproj. - Added Chorizite.Core NuGet PackageReference to AcDream.Core.csproj (needed by Core.Rendering.Wb.TextureHelpers for TextureFormat enum; previously transitive through the WB project ref). - Added BCnEncoder.Net.ImageSharp (1.1.2) + SixLabors.ImageSharp (3.1.12) as direct PackageReferences to AcDream.App.csproj — previously transitive via Chorizite.OpenGLSDLBackend project; used directly by ObjectMeshManager. Item A (BaseObjectRenderManager static fields): - Inlined CurrentAtlas/CurrentVAO/CurrentIBO into a new RenderStateCache.cs static class (AcDream.App.Rendering.Wb namespace) — the 4 consumers (ManagedGLIndexBuffer, ManagedGLTexture, ManagedGLTextureArray, ParticleBatcher) all reference RenderStateCache.* instead of BaseObjectRenderManager.*. - Dropped using Chorizite.OpenGLSDLBackend.Lib from all 4 consumers and from WbDrawDispatcher (which had it only as a dead import). Item B (ActiveParticleEmitter.ObjectLandblock): - ObjectLandblock? erased to object?; WorldBuilder.Shared.Models.ObjectId? erased to ulong? — both fields are stored but never read by any consumer in our codebase. - Dropped both WB using directives from ActiveParticleEmitter.cs. Item C (IDatReaderWriter / IDatDatabase): - Verbatim copy of both interfaces into IDatReaderWriter.cs in AcDream.App.Rendering.Wb namespace — DatCollectionAdapter and ObjectMeshManager already live in that namespace, so no using changes needed. - Dropped using WorldBuilder.Shared.Services from DatCollectionAdapter.cs and ObjectMeshManager.cs. Additional extractions required by the reference drop: - GeometryUtils.cs: verbatim copy of WorldBuilder.Shared.Lib.GeometryUtils (float-precision overloads only; Vector3d double-precision overloads omitted — ObjectMeshManager uses only the float versions). - Dropped using WorldBuilder.Shared.Lib from ObjectMeshManager.cs. WbMeshAdapter.cs cleanup (spec O-D12): - Deleted _wbDats (DefaultDatReaderWriter) field + ctor init + Dispose call. - Deleted the [indoor-upload] NULL_RESULT diagnostic block (lines ~205-262) — its Phase 2 cell-resolution investigation is complete; its _wbDats.ResolveId dependency goes with this commit. - Deleted _pendingEnvCellRequests field + isPendingEnvCell tracking in Tick(). - Simplified Tick() to a clean drain loop. Deleted SplitFormulaDivergenceTest.cs — one-time N.5b data-collection sweep; job done. Verified acceptance criteria: - Zero <ProjectReference> to WorldBuilder.* / Chorizite.OpenGLSDLBackend.* in any csproj. - Zero 'using WorldBuilder.*' / 'using Chorizite.OpenGLSDLBackend.*' in src/. - DefaultDatReaderWriter referenced in zero places in src/ (comments only). Build green (0 warnings, 0 errors). Tests: 1154 total (-1 from deleted SplitFormulaDivergenceTest), 1146 pass, 8 pre-existing failures (unchanged from baseline — physics/input tests unrelated to this change). Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a9ccc5acf5
commit
dc722e70bd
15 changed files with 312 additions and 328 deletions
|
|
@ -1,16 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AcDream.Core.Meshing;
|
||||
using AcDream.Core.Rendering;
|
||||
using Chorizite.OpenGLSDLBackend.Lib;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Silk.NET.OpenGL;
|
||||
using WorldBuilder.Shared.Services;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
|
|
@ -20,30 +16,19 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// so the rest of the renderer doesn't need to know about WB's types directly.
|
||||
///
|
||||
/// <para>
|
||||
/// The adapter constructs its own <c>DefaultDatReaderWriter</c> internally; it
|
||||
/// does NOT share file handles with our <c>DatCollection</c>. This duplicates
|
||||
/// index-cache memory (~50–100 MB) but keeps the two subsystems fully decoupled.
|
||||
/// Acceptable for Phase N.4 foundation work (plan Adjustment 1).
|
||||
/// As of Phase O-T7, all DAT I/O routes through <see cref="DatCollectionAdapter"/>
|
||||
/// (backed by our shared <see cref="DatCollection"/>) — the separate
|
||||
/// <c>DefaultDatReaderWriter</c> file-handle set has been removed.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
||||
{
|
||||
private readonly OpenGLGraphicsDevice? _graphicsDevice;
|
||||
private readonly DefaultDatReaderWriter? _wbDats;
|
||||
private readonly ObjectMeshManager? _meshManager;
|
||||
private readonly DatCollection? _dats;
|
||||
private readonly AcSurfaceMetadataTable _metadataTable = new();
|
||||
private readonly HashSet<ulong> _metadataPopulated = new();
|
||||
|
||||
/// <summary>
|
||||
/// EnvCell ids we've requested via PrepareMeshDataAsync but not yet
|
||||
/// seen completion for in Tick(). Used by the [indoor-upload] probe
|
||||
/// to log requested + completed pairs. Cleared per completion;
|
||||
/// missing completions after a few seconds indicate WB silently
|
||||
/// returned null (hypothesis H1 in the design spec).
|
||||
/// </summary>
|
||||
private readonly HashSet<ulong> _pendingEnvCellRequests = new();
|
||||
|
||||
/// <summary>
|
||||
/// True when this instance was created via <see cref="CreateUninitialized"/>;
|
||||
/// all public methods no-op when uninitialized.
|
||||
|
|
@ -53,13 +38,13 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the full WB pipeline: OpenGLGraphicsDevice → DefaultDatReaderWriter
|
||||
/// Constructs the full WB pipeline: OpenGLGraphicsDevice → DatCollectionAdapter
|
||||
/// → ObjectMeshManager.
|
||||
/// </summary>
|
||||
/// <param name="gl">Active Silk.NET GL context. Must be bound to the current
|
||||
/// thread (construction runs GL queries; call from OnLoad).</param>
|
||||
/// <param name="datDir">Path to the dat directory (same as the one supplied
|
||||
/// to our DatCollection). DefaultDatReaderWriter opens its own file handles.</param>
|
||||
/// <param name="datDir">Path to the dat directory. Retained for API compatibility;
|
||||
/// DatCollectionAdapter routes all DAT I/O through our shared DatCollection.</param>
|
||||
/// <param name="dats">acdream's DatCollection, used to populate the surface
|
||||
/// metadata side-table via <c>GfxObjMesh.Build</c>. Shares file handles with
|
||||
/// the rest of the client; read-only access from the render thread.</param>
|
||||
|
|
@ -75,12 +60,8 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
_dats = dats;
|
||||
_graphicsDevice = new OpenGLGraphicsDevice(gl, logger, new DebugRenderSettings());
|
||||
_graphicsDevice.ParticleBatcher = new ParticleBatcher(_graphicsDevice);
|
||||
_wbDats = new DefaultDatReaderWriter(datDir);
|
||||
// Phase 2 diagnostic — replace NullLogger with a Console-backed
|
||||
// logger so WB's internal catch block at ObjectMeshManager.cs:589
|
||||
// (and similar) surfaces its swallowed exceptions instead of
|
||||
// dropping them. ConsoleErrorLogger filters to LogLevel.Error+
|
||||
// so successful operations stay quiet.
|
||||
// ConsoleErrorLogger surfaces WB's silently-caught exceptions
|
||||
// (ObjectMeshManager.PrepareMeshData try/catch at line ~589).
|
||||
_meshManager = new ObjectMeshManager(
|
||||
_graphicsDevice,
|
||||
new DatCollectionAdapter(dats),
|
||||
|
|
@ -186,80 +167,7 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
// isSetup: false — acdream's MeshRefs already carry expanded
|
||||
// per-part GfxObj ids (0x01XXXXXX). WB's Setup-expansion path is
|
||||
// unused.
|
||||
var prepTask = _meshManager.PrepareMeshDataAsync(id, isSetup: false);
|
||||
|
||||
// [indoor-upload] requested probe — only for EnvCell ids.
|
||||
if (RenderingDiagnostics.IsEnvCellId(id) && RenderingDiagnostics.ProbeIndoorUploadEnabled)
|
||||
{
|
||||
bool hadRenderDataAtRequest = _meshManager.HasRenderData(id);
|
||||
_pendingEnvCellRequests.Add(id);
|
||||
Console.WriteLine($"[indoor-upload] requested cellId=0x{id:X8} hadRenderData={hadRenderDataAtRequest}");
|
||||
|
||||
// Phase 2 — surface what WB's catch block silently swallows.
|
||||
// ObjectMeshManager.PrepareMeshData has a try/catch at line 589
|
||||
// that calls _logger.LogError(ex, ...) — but we construct
|
||||
// ObjectMeshManager with NullLogger.Instance so the log is
|
||||
// dropped. This continuation captures the same data scoped to
|
||||
// EnvCell ids only. Runs on ThreadPool; non-blocking. Zero cost
|
||||
// when the probe is off.
|
||||
ulong cellId = id;
|
||||
_ = prepTask.ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted && t.Exception is not null)
|
||||
{
|
||||
var ex = t.Exception.InnerException ?? t.Exception;
|
||||
var stack = (ex.StackTrace ?? "").Split('\n')
|
||||
.Take(3).Select(s => s.Trim()).Where(s => s.Length > 0);
|
||||
Console.WriteLine(
|
||||
$"[indoor-upload] FAILED cellId=0x{cellId:X8} " +
|
||||
$"exception={ex.GetType().Name}: {ex.Message} " +
|
||||
$"stack=[{string.Join(" | ", stack)}]");
|
||||
}
|
||||
else if (t.IsCompletedSuccessfully && t.Result is null)
|
||||
{
|
||||
// Phase 2 cause-narrowing: WB's PrepareMeshData can return
|
||||
// null for several reasons (ResolveId empty / TryGet<EnvCell>
|
||||
// failed / type Unknown). Cross-check against acdream's own
|
||||
// DatCollection — if WE find the cell but WB doesn't, the
|
||||
// divergence is between dat readers, not a missing record.
|
||||
bool ourCellFound = false;
|
||||
try
|
||||
{
|
||||
ourCellFound = _dats?.Cell.TryGet<DatReaderWriter.DBObjs.EnvCell>(
|
||||
(uint)cellId, out _) ?? false;
|
||||
}
|
||||
catch { /* swallow — this is best-effort diagnostic */ }
|
||||
|
||||
int wbResolveCount = -1;
|
||||
string wbSelectedType = "none";
|
||||
bool wbDbTryGetEnvCell = false;
|
||||
bool wbDbIsPortal = false;
|
||||
try
|
||||
{
|
||||
var wbResolutions = _wbDats?.ResolveId((uint)cellId).ToList();
|
||||
wbResolveCount = wbResolutions?.Count ?? -1;
|
||||
if (wbResolutions is not null && wbResolutions.Count > 0)
|
||||
{
|
||||
var selected = wbResolutions
|
||||
.OrderByDescending(r => r.Database == _wbDats!.Portal)
|
||||
.First();
|
||||
wbSelectedType = selected.Type.ToString();
|
||||
wbDbIsPortal = selected.Database == _wbDats!.Portal;
|
||||
try { wbDbTryGetEnvCell = selected.Database.TryGet<DatReaderWriter.DBObjs.EnvCell>((uint)cellId, out _); } catch {}
|
||||
}
|
||||
}
|
||||
catch { /* swallow — best-effort */ }
|
||||
|
||||
Console.WriteLine(
|
||||
$"[indoor-upload] NULL_RESULT cellId=0x{cellId:X8} " +
|
||||
$"ourCellDb.TryGet={ourCellFound} " +
|
||||
$"wbResolveId.Count={wbResolveCount} " +
|
||||
$"wbSelectedType={wbSelectedType} " +
|
||||
$"wbDbIsPortal={wbDbIsPortal} " +
|
||||
$"wbDbTryGet<EnvCell>={wbDbTryGetEnvCell}");
|
||||
}
|
||||
}, TaskScheduler.Default);
|
||||
}
|
||||
_meshManager.PrepareMeshDataAsync(id, isSetup: false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -298,26 +206,7 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
_graphicsDevice!.ProcessGLQueue();
|
||||
while (_meshManager!.StagedMeshData.TryDequeue(out var meshData))
|
||||
{
|
||||
// [indoor-upload] completed probe — check BEFORE upload so we
|
||||
// see what WB actually produced (vertex counts, parts) before
|
||||
// any post-upload mutation.
|
||||
bool isPendingEnvCell = RenderingDiagnostics.ProbeIndoorUploadEnabled
|
||||
&& _pendingEnvCellRequests.Remove(meshData.ObjectId);
|
||||
|
||||
var renderData = _meshManager.UploadMeshData(meshData);
|
||||
|
||||
if (isPendingEnvCell)
|
||||
{
|
||||
int parts = meshData.SetupParts?.Count ?? 0;
|
||||
bool hasGeom = meshData.EnvCellGeometry is not null;
|
||||
int cellGeomVerts = meshData.EnvCellGeometry?.Vertices?.Length ?? 0;
|
||||
bool uploadOk = renderData is not null;
|
||||
Console.WriteLine(
|
||||
$"[indoor-upload] completed cellId=0x{meshData.ObjectId:X8} " +
|
||||
$"isSetup={meshData.IsSetup} parts={parts} " +
|
||||
$"hasEnvCellGeom={hasGeom} cellGeomVerts={cellGeomVerts} " +
|
||||
$"uploadOk={uploadOk}");
|
||||
}
|
||||
_meshManager.UploadMeshData(meshData);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -342,7 +231,6 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
_meshManager?.Dispose();
|
||||
_wbDats?.Dispose();
|
||||
_graphicsDevice?.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue