C ABI surface + AOT-friendly double-precision core for ForceDirectedLayout#177
Merged
Conversation
Restructures ktsu.ForceDirectedLayout for dual-artifact shipping:
- Managed NuGet (existing generic facade, idiomatic .NET)
- NativeAOT shared library (.dll/.dylib/.so) via a thin
ForceDirectedLayout.Native publish project
Core changes:
- New double-precision Vec2D struct replaces System.Numerics.Vector2
in the layout core. Blittable POD layout so the same memory backs
both managed and native callers.
- LayoutSettings (POD, plain doubles) is the canonical settings shape;
PhysicsSettings (managed record with bool/init properties) sits on
top and converts at the boundary. The semantic-types dependency
(Force<float>/Length<float>) is dropped - it generated AOT/trim
concerns and the typed affordance is not worth the cost in the core.
- LayoutCore: bare algorithm operating on flat BodyState[] and
EdgeRef[] working buffers. No allocations per step. No reflection,
no generics, no managed-only types. Exposes Bodies and Edges as
Span<T>.
- ForceLayout: non-generic managed surface over LayoutCore. Bulk
NodeInit / EdgeInit submission, ReadOnlySpan<NodePosition> reads,
SetPinned / SetFrozen / SetPosition. Same code path the C exports
drive.
- ForceDirectedLayout<TBody, TEdge>: refactored to delegate to
LayoutCore via snapshot+commit. Accessor signatures now use Vec2D.
C ABI:
- NativeExports: 11 [UnmanagedCallersOnly] entry points - Create,
Destroy, SetSettings, SetNodes, SetEdges, Step, Solve, GetPositions,
SetPinned, GetIndexOf, GetNodeCount, GetLastErrorMessage. Every body
wraps a try/catch that converts managed exceptions to a small enum
of return codes - no exception escapes the boundary.
- Opaque handle = GCHandle wrapping a managed ForceLayout. Pair every
Create with a Destroy.
- Reserved Vec2D Anisotropy field on EdgeInit for future per-edge
biasing (execution vs data pin weighting) - adding it later would
be an ABI break.
- Hand-authored reference header at
ForceDirectedLayout.Native/ktsu_force_directed_layout.h alongside
the AOT-generated one (emitted at publish time).
Build:
- ForceDirectedLayout.csproj: IsAotCompatible, IsTrimmable,
EnableTrimAnalyzer, EnableAotAnalyzer, EnableSingleFileAnalyzer all
enabled so reflection drift fails the build.
- ForceDirectedLayout.Native.csproj: PublishAot=true, NativeLib=Shared,
InvariantGlobalization=true, TrimMode=full. Per-platform publish via
`dotnet publish -r <rid>`.
Consumer impact:
- ImGuiNodeEditor.NodeEditorEngine bridges System.Numerics.Vector2
(float, for ImNodes) to Vec2D (double) at the accessor boundary.
Public API of NodeEditorEngine remains float Vector2.
- CleanImNodesDemo PhysicsSettings sliders cast (float)<->(double) at
the ImGui boundary; the semantic-type formatting is gone.
- Adds tests/ForceDirectedLayout.Tests with MSTest coverage of the
new non-generic surface (repulsion, pinning, solve convergence,
settings round-trip).
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Summary
Note: this PR is stacked on top of #176 (the initial library extraction). The diff against
mainincludes the #176 commits; review will be cleanest after #176 lands, at which point this PR's diff reduces to just the new work.Follow-up to #176. Restructures
ktsu.ForceDirectedLayoutso a single source tree produces two artifacts:.dll/.dylib/.so) consumed via a C entry-point surface — Unreal plugins, C++ tooling, anything that candlopenand call C functionsThe C surface drove the design; the managed surface is the comfortable layer on top of the same core.
Layer cake
C API (V1 surface lock)
GCHandle.Alloc(ForceLayout). Pair every Create with a Destroy.[StructLayout(Sequential)], fixed-size primitives only.try/catchthat converts managed exceptions to error codes. No exception escapes into native code.ForceDirectedLayout.Native/ktsu_force_directed_layout.halongside the AOT-emitted one.What changed in the core
Vec2D: new double-precision blittable 2D vector. ReplacesSystem.Numerics.Vector2(which is float) throughout the layout core.PhysicsSettings: managed record, plain doubles. Thektsu.Semantics.Quantitiestyped wrappers (Force<float>etc.) are gone — they generated AOT/trim concerns and the typed affordance wasn't worth the friction at the boundary.LayoutSettings: POD mirror ofPhysicsSettings, crosses the C boundary unchanged.LayoutCore: the algorithm in its bare form. Operates on flatBodyState[]andEdgeRef[]buffers, no allocations per step, no reflection, no generics, no managed-only types.ForceLayout: non-generic managed surface. AcceptsNodeInit/EdgeInitPOD bulk submission, returnsReadOnlySpan<NodePosition>for zero-copy reads.ForceDirectedLayout<TBody, TEdge>: refactored to delegate toLayoutCorevia snapshot+commit. Accessor signatures now useVec2D.NodeEditorEngine: bridges floatVector2↔Vec2Dat the accessor boundary. Its public surface stays float-based.Build
ForceDirectedLayout.csproj:IsAotCompatible=true,IsTrimmable=true,EnableTrimAnalyzer=true,EnableAotAnalyzer=true,EnableSingleFileAnalyzer=true. Trim/AOT warnings fail the build so reflection drift is caught immediately.ForceDirectedLayout.Native.csproj: thin AOT publish project.<PublishAot>true</PublishAot>,<NativeLib>Shared</NativeLib>,<TrimMode>full</TrimMode>,<InvariantGlobalization>true</InvariantGlobalization>. Per-platform binaries viadotnet publish -r <rid>.Reserved
Vec2D AnisotropyonEdgeInitfor V2 (execution vs data-pin biasing in node-editor consumers) — adding it later would be an ABI break.Reserved / explicitly not in V1
EdgeInitandEdgeRef.Anisotropybut the algorithm ignores it.delegate* unmanaged<...>, notFunc<>/Action<>.Test plan
dotnet test --project tests/ForceDirectedLayout.Testspasses on the project's CI (covers repulsion, pinning, solve convergence, settings round-trip)dotnet build ForceDirectedLayout.Nativesucceedsdotnet publish ForceDirectedLayout.Native -c Release -r linux-x64produces a.so(and the equivalent for win-x64 / osx-arm64 in CI).hmatches the hand-authored reference header in field order and primitive sizesGenerated by Claude Code