nbody_sim_core 0.1.1
nbody_sim_core: ^0.1.1 copied to clipboard
Engine-agnostic N-body simulation core models, contracts, adapters, and utilities.
nbody_sim_core #
Engine-agnostic N-body simulation core package for Dart. High-iteration N-body vibes, production-ready core APIs.
nbody_sim_core provides:
- Physics domain models and contracts.
- Multiple engine backends (
Dart,Isolate,Rust FFI). - Scenario and snapshot serialization utilities.
- Schema migration and validation helpers.
- Embedded Rust crate + native build scripts.
Install #
dependencies:
nbody_sim_core: ^0.1.0
Public API #
import 'package:nbody_sim_core/nbody_sim_core.dart';
Barrel exports:
models.dart: configs, vectors, bodies, state, telemetry, edit contracts.engine.dart:SimulationEngine+ concrete backends.scenario.dart: schema validator + migrator APIs.
Core Concepts #
SimulationConfig- Integrator:
semiImplicitEuler,velocityVerlet,rk4 - Collision:
elastic,inelasticMerge,ignore - dt policy:
fixed,adaptive - Solver:
pairwise,barnesHut,auto
- Integrator:
SimulationBody- Required:
id,mass,radius,position,velocity,colorValue
- Required:
SimulationEnginecontractinitialize,setConfig,applyEdit,steploadScenario,saveScenariosnapshot,restoreSnapshotgetState,dispose
Quick Start (Pure Dart backend) #
import 'package:nbody_sim_core/nbody_sim_core.dart';
Future<void> main() async {
final engine = DartSimulationEngine();
await engine.initialize(
config: SimulationConfig.scientificDefault,
bodies: const [
SimulationBody(
id: 'sun',
mass: 1000,
radius: 2.0,
position: Vec2.zero,
velocity: Vec2.zero,
colorValue: 0xFFFFD54F,
),
SimulationBody(
id: 'planet',
mass: 1,
radius: 0.5,
position: Vec2(12, 0),
velocity: Vec2(0, 9.2),
colorValue: 0xFF64B5F6,
),
],
);
final summary = await engine.step(240);
final state = engine.getState();
print('tick=${state.tick}, simTime=${state.simTime}, mode=${summary.lastSolverMode}');
await engine.dispose();
}
Backend Options #
Use whichever backend fits your runtime and performance goals.
DartSimulationEngine- Easiest setup.
- No native prerequisites.
IsolateSimulationEngine- Runs simulation off the UI/main isolate.
- Backend selection:
EngineBackend.auto,EngineBackend.rust,EngineBackend.dart.
RustFfiSimulationEngine- Direct native Rust backend.
- Highest performance path when native library is present.
Example:
import 'package:nbody_sim_core/engine.dart';
import 'package:nbody_sim_core/models.dart';
final engine = IsolateSimulationEngine(
backend: EngineBackend.auto,
// Optional:
// rustLibraryPath: '/absolute/path/to/libgravity_engine.dylib',
);
Direct Rust backend:
import 'package:nbody_sim_core/engine.dart';
final engine = RustFfiSimulationEngine(
// Optional if GRAVITY_ENGINE_LIB is set or library is in ./native:
libraryPath: '/absolute/path/to/libgravity_engine.dylib',
);
Rust Native Build #
The package includes:
- Rust crate:
rust/gravity_engine - Build scripts:
tool/build_rust_engine.shtool/build_rust_engine.ps1
From package root:
# Run Rust tests
cargo test --manifest-path rust/gravity_engine/Cargo.toml
# Build native library and copy to ./native
./tool/build_rust_engine.sh
On Windows PowerShell:
.\tool\build_rust_engine.ps1
Generated library names:
- macOS:
libgravity_engine.dylib - Linux:
libgravity_engine.so - Windows:
gravity_engine.dll
Rust Library Discovery #
When using Rust backends, RustFfiBindings resolves the native library in this order:
- Explicit
libraryPathpassed into engine/bindings. GRAVITY_ENGINE_LIBenvironment variable../native/<platform-library-name>./rust/gravity_engine/target/release/<platform-library-name>- Dynamic loader default lookup by filename.
Runtime Edits (Create/Update/Delete Bodies) #
import 'package:nbody_sim_core/models.dart';
await engine.applyEdit(
const BodyCreate(
SimulationBody(
id: 'probe',
mass: 0.1,
radius: 0.1,
position: Vec2(0, 20),
velocity: Vec2(6, 0),
colorValue: 0xFFFFFFFF,
),
),
);
await engine.applyEdit(
const BodyUpdate(
id: 'probe',
velocity: Vec2(6.5, 0.2),
label: 'Science Probe',
kind: 'probe',
),
);
await engine.applyEdit(const BodyDelete('probe'));
Scenario and Snapshot APIs #
import 'package:nbody_sim_core/models.dart';
// Save current state as scenario
final scenario = await engine.saveScenario();
// Restore scenario
await engine.loadScenario(scenario);
// Point-in-time snapshot
final snap = await engine.snapshot();
await engine.restoreSnapshot(snap);
JSON import/export:
import 'dart:convert';
import 'package:nbody_sim_core/models.dart';
// Export scenario JSON string
final scenario = await engine.saveScenario();
final scenarioJsonString = jsonEncode(scenario.toJson());
// Import scenario JSON string
final parsedScenarioMap = jsonDecode(scenarioJsonString) as Map<String, dynamic>;
final importedScenario = ScenarioModel.fromJson(parsedScenarioMap);
await engine.loadScenario(importedScenario);
// Export snapshot JSON string
final snapshot = await engine.snapshot();
final snapshotJsonString = jsonEncode(snapshot.toJson());
// Import snapshot JSON string
final parsedSnapshotMap = jsonDecode(snapshotJsonString) as Map<String, dynamic>;
final importedSnapshot = SnapshotModel.fromJson(parsedSnapshotMap);
await engine.restoreSnapshot(importedSnapshot);
Schema Migration + Validation #
import 'package:nbody_sim_core/scenario.dart';
final migrated = ScenarioSchemaMigrator.migrateToLatest(rawJson);
final issues = ScenarioSchemaValidator.validateScenarioJson(migrated);
if (issues.isNotEmpty) {
for (final issue in issues) {
print(issue); // "<path>: <message>"
}
}
Deterministic vs Adaptive Notes #
- Deterministic mode is designed for replayable runs.
SimulationConfig.validate()rejectsdeterministic == truewithDtPolicy.adaptive.- For exact replay workflows, keep:
deterministic: truedtPolicy: DtPolicy.fixed
Minimal Lifecycle Pattern #
final engine = IsolateSimulationEngine();
await engine.initialize(config: SimulationConfig.scientificDefault, bodies: initialBodies);
await engine.step(1);
final state = engine.getState();
await engine.dispose();
Troubleshooting #
- Error: unable to open gravity engine dynamic library
- Build native library with
./tool/build_rust_engine.sh. - Set
GRAVITY_ENGINE_LIBto an absolute library path. - Or pass
rustLibraryPathexplicitly.
- Build native library with
StateError: Rust engine has not been initialized- Call
initialize()beforestep,setConfig,applyEdit, etc.
- Call
- Schema validation failures
- Run migrator first:
ScenarioSchemaMigrator.migrateToLatest(...). - Re-run validator and inspect issue paths/messages.
- Run migrator first:
License #
MIT (see LICENSE).