acdream/src/AcDream.Bake/ConsoleErrorLogger.cs
Erik 50f7dd06cf feat(pipeline): MP1b - acdream-bake CLI
New offline console tool driving AcDream.Content.MeshExtractor (the SAME
extraction code the live client runs, per MP1a) to produce a versioned
pak file. Arguments: --dat-dir (required), --out (default acdream.pak
next to the dats), --ids/--landblocks (dev filters), --threads (default
Environment.ProcessorCount).

Enumeration mirrors existing idioms rather than inventing new ones:
GfxObj/Setup ids via DatCollection.GetAllIdsOfType<T>() (src/AcDream.Cli/
Program.cs); EnvCell ids by walking dats.Cell.Tree for LandBlockInfo
entries (low 16 bits == 0xFFFE) and then the firstCellId+NumCells range
per landblock (the same idiom as GameWindow.BuildPhysicsDatBundle /
BuildInteriorEntitiesForStreaming). GetAllIdsOfType<T>() does not cover
cell-dat range-based types, hence the manual walk.

BakeDatCollectionAdapter is a from-scratch copy of AcDream.App.Rendering.
Wb.DatCollectionAdapter (which is `internal` to AcDream.App and, more
importantly, referencing AcDream.App would drag Silk.NET/GL into the
bake tool — a hard violation of "no Silk.NET anywhere"). It's plain dat-
access glue, not an AC-specific algorithm, so a small duplicate is the
right call over inventing a shared-but-App-rooted package.

Particle-preload GfxObjs MeshExtractor side-stages mid-extraction
(sideStagedSink, thread-safe ConcurrentQueue per MP1a's documented
contract) are deduped against the primary GfxObj set and against each
other before being written as their own GfxObjMesh entries.

ConsoleErrorLogger is a minimal hand-rolled ILogger (stderr, Warning+)
rather than NullLogger — MeshExtractor's LogError/LogWarning calls on
malformed dat entries must stay visible per the project's "logger
injection for silent catches" lesson.

Progress line every 5s (baked/total, failures, elapsed, ETA); a
malformed dat entry is caught per-id and counted as a failure, never
fatal — matches the runtime's own per-id try/catch behavior. Failures
report is printed at the end, capped at 200 lines.

Smoke-tested against the real dats on this machine: --ids
0x01000001,0x02000001 baked 1 GfxObj (40 vertices) + 1 Setup (34 parts)
in 1.4s with 0 failures; a follow-up run adding EnvCell 0xA9B40100
(Holtburg's first interior cell) baked all three asset types
successfully. PakReader opened both scratch paks and read back
deep-correct ObjectMeshData; scratch files deleted after verification.

*.pak added to .gitignore in this commit per the plan.
2026-07-05 21:30:25 +02:00

38 lines
1.6 KiB
C#

using System;
using Microsoft.Extensions.Logging;
namespace AcDream.Bake;
/// <summary>
/// Minimal <see cref="ILogger"/> writing to stderr with a level prefix.
/// MeshExtractor logs failures via ILogger.LogError/LogWarning (dat-read
/// anomalies, malformed entries) — per the project's "logger injection for
/// silent catches" lesson, the bake tool must NOT wire a NullLogger here or
/// every per-id extraction failure goes silent. A tiny hand-rolled logger
/// avoids pulling in the Microsoft.Extensions.Logging.Console package
/// (not already a transitive dependency) for what is a one-line need.
/// </summary>
public sealed class ConsoleErrorLogger : ILogger {
private readonly string _categoryName;
public ConsoleErrorLogger(string categoryName) {
_categoryName = categoryName;
}
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
public bool IsEnabled(LogLevel logLevel) => logLevel >= LogLevel.Warning;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) {
if (!IsEnabled(logLevel)) return;
var message = formatter(state, exception);
Console.Error.WriteLine($"[{logLevel}] {_categoryName}: {message}");
if (exception is not null) Console.Error.WriteLine(exception);
}
}
/// <summary>Factory producing <see cref="ConsoleErrorLogger"/> instances for any category/type.</summary>
public sealed class ConsoleErrorLoggerProvider : ILoggerProvider {
public ILogger CreateLogger(string categoryName) => new ConsoleErrorLogger(categoryName);
public void Dispose() { }
}