fix(O-T4): thread-safety lock in DatDatabaseWrapper + drop unused using

Code-review findings on T4:

1. Added lock(_lock) around _db.TryGet and TryGetFileBytes in
   DatDatabaseWrapper, matching WB's DefaultDatDatabase pattern.
   ObjectMeshManager.PrepareMeshDataAsync runs on the thread pool, so
   concurrent dat access through the adapter must be serialized — our
   underlying DatCollection is not documented as thread-safe.

2. Removed unused `using WorldBuilder.Shared.Models;` from WbMeshAdapter.cs
   (its only purpose was TerrainEntry, which moved to AcDream.Core in T2).

Build green; tests green (1147 passing, 8 pre-existing failures baseline).

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:
Erik 2026-05-21 17:01:43 +02:00
parent c0326523ac
commit a9ccc5acf5
2 changed files with 21 additions and 8 deletions

View file

@ -118,6 +118,7 @@ internal sealed class DatDatabaseWrapper : IDatDatabase
{ {
private readonly DatDatabase _db; private readonly DatDatabase _db;
private readonly ConcurrentDictionary<(Type, uint), IDBObj> _cache = new(); private readonly ConcurrentDictionary<(Type, uint), IDBObj> _cache = new();
private readonly object _lock = new();
public DatDatabaseWrapper(DatDatabase db) public DatDatabaseWrapper(DatDatabase db)
{ {
@ -142,20 +143,33 @@ internal sealed class DatDatabaseWrapper : IDatDatabase
return true; return true;
} }
if (_db.TryGet<T>(fileId, out value)) lock (_lock)
{ {
_cache.TryAdd((typeof(T), fileId), value); if (_db.TryGet<T>(fileId, out value))
return true; {
_cache.TryAdd((typeof(T), fileId), value);
return true;
}
} }
return false; return false;
} }
public bool TryGetFileBytes(uint fileId, [MaybeNullWhen(false)] out byte[] value) => public bool TryGetFileBytes(uint fileId, [MaybeNullWhen(false)] out byte[] value)
_db.TryGetFileBytes(fileId, out value); {
lock (_lock)
{
return _db.TryGetFileBytes(fileId, out value);
}
}
public bool TryGetFileBytes(uint fileId, ref byte[] bytes, out int bytesRead) => public bool TryGetFileBytes(uint fileId, ref byte[] bytes, out int bytesRead)
_db.TryGetFileBytes(fileId, ref bytes, out bytesRead); {
lock (_lock)
{
return _db.TryGetFileBytes(fileId, ref bytes, out bytesRead);
}
}
public bool TrySave<T>(T obj, int iteration = 0) where T : IDBObj => public bool TrySave<T>(T obj, int iteration = 0) where T : IDBObj =>
throw new NotSupportedException("DatDatabaseWrapper is read-only."); throw new NotSupportedException("DatDatabaseWrapper is read-only.");

View file

@ -10,7 +10,6 @@ using DatReaderWriter.DBObjs;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Silk.NET.OpenGL; using Silk.NET.OpenGL;
using WorldBuilder.Shared.Models;
using WorldBuilder.Shared.Services; using WorldBuilder.Shared.Services;
namespace AcDream.App.Rendering.Wb; namespace AcDream.App.Rendering.Wb;