mirror of
https://github.com/Heretek-AI/game-engine.git
synced 2026-11-13 04:31:47 -10:00
164 lines
5.8 KiB
C#
164 lines
5.8 KiB
C#
// Program.cs — M5-04 ExportSample: minimal NativeAOT game entry.
|
|
//
|
|
// Cold-boot proof: preload native libs by absolute sibling path, load the
|
|
// ABI table, create a world, bulk-create a scene, run one frame through
|
|
// the real MovementSystem kernel, print timings, exit. The exporter
|
|
// measures wall time of THIS binary as the <1.0s cold-boot gate.
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using Engine.Bindings;
|
|
using Engine.Systems;
|
|
|
|
internal static class Program
|
|
{
|
|
internal static unsafe int Main(string[] args)
|
|
{
|
|
var total = Stopwatch.StartNew();
|
|
|
|
// Sibling-preload: the exporter stages libengine_core + libflecs
|
|
// next to this binary. NativeLibrary.Load by absolute path pins
|
|
// the handle, but bare-SONAME NativeLibrary.TryLoad("engine_core")
|
|
// still dlopens by SONAME — so register a DllImportResolver on the
|
|
// Bindings assembly that maps "engine_core" to the staged file.
|
|
// (NativeAOT-compatible: no DllImport stub needed; the resolver
|
|
// runs for NativeLibrary loads from the target assembly.)
|
|
var dir = AppContext.BaseDirectory;
|
|
string? corePath = null;
|
|
foreach (var f in new[] { "libengine_core.so.0.1.0", "libengine_core.so", "libengine_core.dylib", "engine_core.dll", })
|
|
{
|
|
var p = Path.Combine(dir, f);
|
|
if (File.Exists(p))
|
|
{
|
|
corePath = p;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (corePath == null)
|
|
{
|
|
Console.Error.WriteLine("export-sample: no staged engine_core next to binary.");
|
|
return 1;
|
|
}
|
|
|
|
string? flecsPath = null;
|
|
foreach (var f in new[] { "libflecs.so", "libflecs.dylib", "flecs.dll" })
|
|
{
|
|
var p = Path.Combine(dir, f);
|
|
if (File.Exists(p))
|
|
{
|
|
flecsPath = p;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (flecsPath != null)
|
|
{
|
|
try
|
|
{
|
|
NativeLibrary.Load(flecsPath);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"export-sample: flecs preload failed: {ex.Message}");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
NativeApi.ApiTable* table;
|
|
{
|
|
// Direct-from-handle load (Benchmark1M LiveApiLoader precedent):
|
|
// bare-SONAME TryLoad does not probe the app dir, so resolve the
|
|
// export off the staged handle we just pinned by absolute path.
|
|
nint coreHandle;
|
|
try
|
|
{
|
|
coreHandle = NativeLibrary.Load(corePath);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"export-sample: core handle failed: {ex.GetType().Name}: {ex.Message}");
|
|
return 1;
|
|
}
|
|
|
|
if (!NativeLibrary.TryGetExport(coreHandle, "engine_get_api_table", out var addr))
|
|
{
|
|
Console.Error.WriteLine("export-sample: engine_get_api_table not exported.");
|
|
return 1;
|
|
}
|
|
|
|
var getTable = (delegate* unmanaged[Cdecl]<NativeApi.ApiTable*>)addr;
|
|
table = getTable();
|
|
if (table == null)
|
|
{
|
|
Console.Error.WriteLine("export-sample: engine_get_api_table returned null.");
|
|
return 1;
|
|
}
|
|
|
|
if (table->AbiVersionMajor != NativeApi.AbiVersionMajor
|
|
|| table->AbiVersionMinor != NativeApi.AbiVersionMinor
|
|
|| table->AbiVersionPatch != NativeApi.AbiVersionPatch)
|
|
{
|
|
Console.Error.WriteLine(
|
|
$"export-sample: ABI mismatch native {table->AbiVersionMajor}.{table->AbiVersionMinor}.{table->AbiVersionPatch} " +
|
|
$"!= managed {NativeApi.AbiVersionMajor}.{NativeApi.AbiVersionMinor}.{NativeApi.AbiVersionPatch}.");
|
|
return 1;
|
|
}
|
|
|
|
Console.WriteLine($"export-sample: ABI {table->AbiVersionMajor}.{table->AbiVersionMinor}.{table->AbiVersionPatch} table={table->Size}B");
|
|
}
|
|
|
|
{
|
|
var world = table->EngineWorldCreate();
|
|
if (world == null)
|
|
{
|
|
Console.Error.WriteLine("export-sample: world_create returned null.");
|
|
return 1;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Scene: 10k entities, one registered script system.
|
|
var n = table->EngineWorldBulkCreate(world, 10_000);
|
|
Console.WriteLine($"export-sample: bulk_created={n}");
|
|
|
|
var rc = table->EngineRegisterSystem(
|
|
world,
|
|
null,
|
|
&MovementSystem.MoveUnrolled,
|
|
null);
|
|
if (rc != 0)
|
|
{
|
|
Console.Error.WriteLine($"export-sample: register_system failed: {rc}.");
|
|
return 1;
|
|
}
|
|
|
|
var frame = new NativeApi.FrameDispatch
|
|
{
|
|
Dt = 1.0f / 60.0f,
|
|
};
|
|
var t0 = Stopwatch.GetTimestamp();
|
|
var runRc = table->EngineRunSystems(world, &frame);
|
|
var frameUs = (Stopwatch.GetTimestamp() - t0) * 1_000_000.0 / Stopwatch.Frequency;
|
|
if (runRc != 0)
|
|
{
|
|
Console.Error.WriteLine($"export-sample: run_systems failed: {runRc}.");
|
|
return 1;
|
|
}
|
|
|
|
Console.WriteLine($"export-sample: frame_10k_1sys_us={frameUs:F1}");
|
|
}
|
|
finally
|
|
{
|
|
table->EngineWorldDestroy(world);
|
|
}
|
|
}
|
|
|
|
total.Stop();
|
|
Console.WriteLine($"export-sample: cold_boot_ms={total.Elapsed.TotalMilliseconds:F1}");
|
|
Console.WriteLine("EXPORT-SAMPLE-OK");
|
|
return 0;
|
|
}
|
|
}
|