feat(O-T4): extract ObjectMeshManager + mesh pipeline closure into AcDream.App.Rendering.Wb
Phase O Task 4: extract the WB mesh pipeline (ObjectMeshManager + 7 support files) from references/WorldBuilder into src/AcDream.App/Rendering/Wb/ and bridge dat I/O through our DatCollection via a thin DatCollectionAdapter. O-D7 adapter path taken: ObjectMeshManager has 26 _dats.X call sites (threshold 20), so a DatCollectionAdapter : IDatReaderWriter is introduced rather than refactoring ObjectMeshManager's internal dat access directly. Files added (verbatim copies, namespace-only changes): - ObjectMeshManager.cs — mesh pipeline hub; IDatReaderWriter field satisfied by adapter - GlobalMeshBuffer.cs — single global VAO/VBO/IBO manager - EdgeLineBuilder.cs — wireframe edge geometry from CellStruct polygons - ModernRenderData.cs — ModernBatchData + LandblockMdiCommand structs - TextureAtlasManager.cs — texture array grouping by (Width, Height, Format) - ParticleBatcher.cs — GPU particle batching; T4 interim uses BaseObjectRenderManager static fields from Chorizite.OpenGLSDLBackend.Lib (stays until T7) - ParticleEmitterRenderer.cs — per-emitter particle lifecycle + rendering - ActiveParticleEmitter.cs — wrapper holding renderer + part index + local offset - DatCollectionAdapter.cs — NEW: bridges DatCollection → IDatReaderWriter; implements ResolveId() via DatDatabase.TypeFromId + Tree.TryGetFile in HighRes→Portal→Language→Cell order matching DefaultDatReaderWriter; DatDatabaseWrapper wraps DatDatabase as IDatDatabase WbMeshAdapter.cs changes (T4 Step 6): - _graphicsDevice switched from Chorizite.OpenGLSDLBackend.OpenGLGraphicsDevice to extracted AcDream.App.Rendering.Wb.OpenGLGraphicsDevice - ParticleBatcher = new ParticleBatcher(_graphicsDevice) restored (T3 had null! placeholder) - ObjectMeshManager now constructed with new DatCollectionAdapter(dats) instead of _wbDats - _wbDats field + its construction + disposal + [indoor-upload] NULL_RESULT diagnostic block left intact — T7 cleanup removes these once WorldBuilder project ref is dropped EmbeddedResourceReader.cs: replaced assembly manifest lookup (wrong prefix for our assembly) with disk-based lookup mapping "Shaders.Particle.vert" → Rendering/Shaders/wb_particle.vert; consistent with all other acdream shaders. wb_particle.vert / wb_particle.frag: WB particle shaders copied verbatim with wb_ prefix to distinguish from acdream's own particle.vert. OpenGLGraphicsDevice.cs: ParticleBatcher property type updated to extracted ParticleBatcher; setter changed from private to internal so WbMeshAdapter (same assembly) can assign post-ctor. Build: green (0 errors, 0 warnings in AcDream.App). Tests: 1147+8 baseline maintained (8 pre-existing failures unchanged). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4cc38805b5
commit
d16d8cd4e5
14 changed files with 3535 additions and 19 deletions
167
src/AcDream.App/Rendering/Wb/DatCollectionAdapter.cs
Normal file
167
src/AcDream.App/Rendering/Wb/DatCollectionAdapter.cs
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
using DatReaderWriter;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Lib.IO;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using WorldBuilder.Shared.Services;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// Adapts acdream's <see cref="DatCollection"/> to WB's <see cref="IDatReaderWriter"/> interface.
|
||||
///
|
||||
/// O-D7 fallback path: taken because ObjectMeshManager has 26 _dats.X call sites (threshold is 20),
|
||||
/// making a full refactor to DatCollection larger than spec permits in a single task.
|
||||
/// This adapter lets ObjectMeshManager stay byte-identical to the WB original while
|
||||
/// routing all DAT I/O through our single DatCollection. The adapter is dropped in T7
|
||||
/// when the WorldBuilder project reference is removed entirely.
|
||||
/// </summary>
|
||||
internal sealed class DatCollectionAdapter : IDatReaderWriter
|
||||
{
|
||||
private readonly DatCollection _dats;
|
||||
private readonly DatDatabaseWrapper _portal;
|
||||
private readonly DatDatabaseWrapper _cell;
|
||||
private readonly DatDatabaseWrapper _highRes;
|
||||
private readonly DatDatabaseWrapper _language;
|
||||
private readonly ReadOnlyDictionary<uint, IDatDatabase> _cellRegions;
|
||||
|
||||
public DatCollectionAdapter(DatCollection dats)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dats);
|
||||
_dats = dats;
|
||||
_portal = new DatDatabaseWrapper(dats.Portal);
|
||||
_cell = new DatDatabaseWrapper(dats.Cell);
|
||||
_highRes = new DatDatabaseWrapper(dats.HighRes);
|
||||
_language = new DatDatabaseWrapper(dats.Local);
|
||||
|
||||
// DatCollection has a single Cell, not multiple cell regions.
|
||||
// Expose it as region 0 to satisfy callers that iterate CellRegions.
|
||||
var regions = new Dictionary<uint, IDatDatabase> { [0u] = _cell };
|
||||
_cellRegions = new ReadOnlyDictionary<uint, IDatDatabase>(regions);
|
||||
}
|
||||
|
||||
/// <summary>Source directory of the underlying DatCollection.</summary>
|
||||
public string SourceDirectory => _dats.Options.DatDirectory ?? string.Empty;
|
||||
|
||||
public IDatDatabase Portal => _portal;
|
||||
public ReadOnlyDictionary<uint, IDatDatabase> CellRegions => _cellRegions;
|
||||
public IDatDatabase HighRes => _highRes;
|
||||
public IDatDatabase Language => _language;
|
||||
|
||||
// RegionFileMap is used by some WB internals but not by ObjectMeshManager.
|
||||
public ReadOnlyDictionary<uint, uint> RegionFileMap =>
|
||||
new ReadOnlyDictionary<uint, uint>(new Dictionary<uint, uint>());
|
||||
|
||||
// Iteration properties — not used by ObjectMeshManager, so delegate to 0.
|
||||
public int PortalIteration => 0;
|
||||
public int CellIteration => 0;
|
||||
public int HighResIteration => 0;
|
||||
public int LanguageIteration => 0;
|
||||
|
||||
public bool TryGetFileBytes(uint regionId, uint fileId, ref byte[] bytes, out int bytesRead)
|
||||
{
|
||||
// Route to cell db (the only region we expose)
|
||||
return _dats.Cell.TryGetFileBytes(fileId, ref bytes, out bytesRead);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a DAT id to all databases that contain it, along with the DBObjType.
|
||||
/// Mirrors DefaultDatReaderWriter.ResolveId — checks each underlying DatDatabase
|
||||
/// via DatDatabase.TypeFromId (which reads the type range tables).
|
||||
/// </summary>
|
||||
public IEnumerable<IDatReaderWriter.IdResolution> ResolveId(uint id)
|
||||
{
|
||||
var results = new List<IDatReaderWriter.IdResolution>();
|
||||
|
||||
void CheckDb(DatDatabaseWrapper wrapper)
|
||||
{
|
||||
var rawDb = wrapper.RawDatabase;
|
||||
if (rawDb.Tree.TryGetFile(id, out _))
|
||||
{
|
||||
var type = rawDb.TypeFromId(id);
|
||||
if (type != DBObjType.Unknown)
|
||||
results.Add(new IDatReaderWriter.IdResolution(wrapper, type));
|
||||
}
|
||||
}
|
||||
|
||||
// Match DefaultDatReaderWriter ordering: HighRes → Portal → Language → Cell
|
||||
CheckDb(_highRes);
|
||||
CheckDb(_portal);
|
||||
CheckDb(_language);
|
||||
CheckDb(_cell);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public bool TrySave<T>(T obj, int iteration = 0) where T : IDBObj =>
|
||||
throw new NotSupportedException("DatCollectionAdapter is read-only.");
|
||||
|
||||
public bool TrySave<T>(uint regionId, T obj, int iteration = 0) where T : IDBObj =>
|
||||
throw new NotSupportedException("DatCollectionAdapter is read-only.");
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// The underlying DatCollection is owned by the caller — do not dispose it here.
|
||||
// Individual wrapper objects hold no unmanaged resources.
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps a <see cref="DatDatabase"/> as <see cref="IDatDatabase"/>.
|
||||
/// Mirrors WorldBuilder.Shared.Services.DefaultDatDatabase but lives in our namespace
|
||||
/// so the WorldBuilder project reference can be dropped in T7.
|
||||
/// </summary>
|
||||
internal sealed class DatDatabaseWrapper : IDatDatabase
|
||||
{
|
||||
private readonly DatDatabase _db;
|
||||
private readonly ConcurrentDictionary<(Type, uint), IDBObj> _cache = new();
|
||||
|
||||
public DatDatabaseWrapper(DatDatabase db)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(db);
|
||||
_db = db;
|
||||
}
|
||||
|
||||
/// <summary>Exposes the raw DatDatabase for ResolveId's Tree.TryGetFile + TypeFromId calls.</summary>
|
||||
internal DatDatabase RawDatabase => _db;
|
||||
|
||||
public DatDatabase Db => _db;
|
||||
public int Iteration => _db.Iteration?.CurrentIteration ?? 0;
|
||||
|
||||
public IEnumerable<uint> GetAllIdsOfType<T>() where T : IDBObj =>
|
||||
_db.GetAllIdsOfType<T>();
|
||||
|
||||
public bool TryGet<T>(uint fileId, [MaybeNullWhen(false)] out T value) where T : IDBObj
|
||||
{
|
||||
if (_cache.TryGetValue((typeof(T), fileId), out var cached))
|
||||
{
|
||||
value = (T)cached;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_db.TryGet<T>(fileId, out value))
|
||||
{
|
||||
_cache.TryAdd((typeof(T), fileId), value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetFileBytes(uint fileId, [MaybeNullWhen(false)] out byte[] value) =>
|
||||
_db.TryGetFileBytes(fileId, out value);
|
||||
|
||||
public bool TryGetFileBytes(uint fileId, ref byte[] bytes, out int bytesRead) =>
|
||||
_db.TryGetFileBytes(fileId, ref bytes, out bytesRead);
|
||||
|
||||
public bool TrySave<T>(T obj, int iteration = 0) where T : IDBObj =>
|
||||
throw new NotSupportedException("DatDatabaseWrapper is read-only.");
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// The underlying DatDatabase is owned by DatCollection — do not dispose here.
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue