Repository navigation
Releases: StarworksDev/Astra
Release list
Astra 3.1.0
Astra ECS v3.1 - Hardening: Scheduler Seam, TypeContext, Container Fixes & CI
Release Highlights
Astra 3.1 is a hardening release focused on correctness, portability, and safe integration patterns. It ships a clean threading seam (no surprise thread spawning), process-shared type identity for multi-module and DLL scenarios, two container correctness fixes found by new fuzz suites, and a GitHub Actions CI matrix covering MSVC, gcc, and clang (distro-default on ubuntu-latest; verified locally with gcc 15.1 / clang 18). The public API is backward-compatible; two deprecated footguns (TypeID_v and MakeComponentMask constexpr) are removed.
Parallel Execution & The Scheduler Seam
The old View::ParallelForEach, Relations::ParallelForEachDescendant, and ParallelExecutor implementations spawned std::async tasks ad-hoc per call. Benchmarking at 1 M entities with a trivial store-only kernel showed this was actively harmful: the ad-hoc async path took 2.43 ms against 1.53 ms for the sequential path -- a 1.59x loss due to thread-pool contention and synchronisation overhead on a memory-bound kernel.
IWorkScheduler seam
A thin scheduler interface lives at Core/WorkScheduler.hpp:
class IWorkScheduler {
public:
virtual ~IWorkScheduler() = default;
virtual void ParallelFor(size_t count, size_t minBatch,
const std::function<void(size_t, size_t)>& fn) = 0;
virtual size_t WorkerCount() const noexcept = 0;
};Inject via Registry::Config::workScheduler (std::shared_ptr<IWorkScheduler>). When nullptr (the new default), all parallel APIs run sequentially inline -- safe in single-threaded contexts and faster on memory-bound workloads.
// Sequential (default -- fastest for memory-bound kernels)
Astra::Registry registry;
// Parallel with your own pool
auto scheduler = std::make_shared<MyEnkiTSAdapter>();
Astra::Registry::Config config;
config.workScheduler = scheduler;
Astra::Registry registry(config);The reference fork-join pool is available in tests/Support/TestWorkerPool.hpp for test and benchmark use. With the injected test pool on a compute-heavy kernel at 1 M entities: 2.17 ms (injected pool) vs 2.43 ms (old ad-hoc async) -- an 11% improvement once thread overhead is no longer paid per-call. Sequential remains the right default; parallelism pays when the kernel is compute-bound rather than memory-bound. For the store-only (memory-bound) kernel, sequential at 1.53 ms still wins; the default is now sequential rather than the silently-losing parallel path.
Multi-Module & DLL Support
Static typeindex-based IDs break across DLL boundaries because each module gets its own static. TypeContext (Core/TypeContext.hpp) replaces the old scheme with process-shared type identity.
How it works
- Stable XXHash64 type-name hashes are the cross-module identity key.
- Dense sequential ComponentIDs are assigned from the shared context and are mask-compatible with the existing archetype bitmask system.
- A pending static-registration queue is drained when
SetTypeContextis called from each module, so components registered before the context is installed are not lost. MetaRegistrynow lives inside the context; the singleton API (MetaRegistry::Instance()) is preserved.
DLL hot-reload sequence
// 1. Serialize living entities (snapshot or raw binary)
// 2. Unload old DLL; load new DLL
// 3. Install shared context in the new module
SetTypeContext(sharedCtx);
// 4. Refresh descriptors for recompiled components
componentRegistry->ReRegisterComponent<MyComponent>();
// 5. Deserialize -- IDs are stable across the reloadTypeID_v (a static-init template variable that was a DLL footgun) is removed; use Astra::TypeID<T>::Value() instead (IDs resolve through the active TypeContext).
Container Fixes & Fuzzing
New differential fuzz suites (21 fuzz tests covering FlatMap/FlatSet churn with adversarial hashing, SmallVector spill-boundary, and Bitmap bulk ops) found two correctness bugs in the internal containers.
FlatMap / FlatSet duplicate-insertion bug
Emplace inserted a key at the first empty-or-deleted (tombstone) slot encountered during the probe chain before completing the duplicate scan. Because tombstones do not terminate the probe sequence, a key already present later in the chain could be inserted a second time, causing a silent size overcount and returning iterators to two distinct slots for the same logical key.
Fix: defer slot selection until an EMPTY slot terminates the scan. If a matching key is found during the scan, return the existing slot (no insert). Only after confirming no duplicate exists is the deferred slot used for insertion.
SwissTable H2 metadata-range hazard
SwissTable::H2 extracted the top 7 bits of the hash via & 0x7F, which already excluded the 0x80 sentinel. However, a degenerate hasher whose top 7 bits are all zero produces H2 == 0, which falls outside the range [1, 127] that Group::Match assumes for probe bytes, breaking the SIMD match logic.
Fix: H2 now folds 0 to 1 centrally (return h2 == 0 ? 1 : h2) so any hasher is guaranteed to produce a value in [1, 127].
Cross-Compiler & CI
Approximately 25 portability fixes were required to build cleanly under current gcc and clang (verified locally with gcc 15.1 / clang 18):
- Dependent-name disambiguators (
typename/template) in templated base-class member access. - Removed in-class partial specialisations (not permitted by the standard; MSVC accepted them as an extension).
- Fixed member-initialiser order to match declaration order (gcc
-Wreorder). - Corrected attribute placement (
[[nodiscard]]beforeinline, not after). - Warning hygiene: signed/unsigned comparisons, unused parameters, shadow declarations.
A GitHub Actions CI matrix (.github/workflows/ci.yml) runs on every push to every branch and on every pull request:
| Job | Compiler | Standard |
|---|---|---|
| windows-msvc | MSVC on windows-latest | C++20 |
| linux-gcc | current distro gcc on ubuntu-latest (unpinned) | C++20 |
| linux-clang | current distro clang on ubuntu-latest (unpinned) | C++20 |
Compilers are unpinned (distro-default on ubuntu-latest and MSVC on windows-latest). Verified locally with gcc 15.1 / clang 18. All three jobs build in Release, run the full 496-test suite, and must be green before merge.
Documentation
- Removed the nonexistent
threadSafeconfig key from the README (it was never implemented). - Corrected component requirements section (copyable/movable constraints clarified).
- Fixed
CreateEntityWithexamples to match the actual API signature. - Added Threading Model section documenting the scheduler seam and default-sequential behaviour.
- Added Multi-Module (DLL) section documenting
TypeContext,SetTypeContext, and the hot-reload sequence.
Removals
| Removed | Replacement | Reason |
|---|---|---|
TypeID_v<T> (static template variable) |
Astra::TypeID<T>::Value() |
Static-init order fiasco across DLL boundaries |
MakeComponentMask constexpr overload |
Runtime ComponentMask construction |
Bitmap is not a literal type under clang; constexpr was misleading |
Requirements
- C++20 compatible compiler (MSVC 2022+, GCC 11+, Clang 13+)
- Premake5 for project generation
- Optional: GoogleTest, Google Benchmark
License
MIT License - See LICENSE file for details
Full Changelog: v3.0.0...v3.1.0
Astra 3.0.0
Astra ECS v3.0 - Resources, Runtime Reflection & Editor Integration
Major Release Highlights
Astra 3.0 introduces a singleton resource system for global state and a comprehensive runtime reflection system providing EnTT meta-style introspection, field enumeration, attribute decoration, and full ECS integration. This release enables editor tooling, runtime component inspection, and JSON schema generation.
New Capabilities
- Resource system for singleton/global components with SBO optimization
- Runtime type introspection with zero overhead when unused
- Field enumeration with get/set accessors and attribute metadata
- Entity inspection API for editor and debugging tools
New Features
Resource System (NEW)
Singleton resources for global state that exists outside the entity-component model:
// Add global resources
registry.AddResource<Time>(Time{ 0.0f, 0.016f });
registry.AddResource<InputState>();
registry.AddResource<RenderSettings>(width, height, vsync);
// Access resources
Time* time = registry.GetResource<Time>();
time->deltaTime = 0.016f;
// Check existence
if (registry.HasResource<InputState>()) { ... }
// Remove resources
registry.RemoveResource<RenderSettings>();Storage Architecture
- Sparse-dense pattern for O(1) access by ComponentID
- Small Buffer Optimization - Resources ≤64 bytes stored inline (no heap allocation)
- Swap-and-pop removal maintains cache locality
- Automatic registration with ComponentRegistry
Type-Erased Operations
For command buffers and deferred execution:
// Set resource by ID (for CommandBuffer)
resourceStorage.SetByID(componentId, dataPtr, dataSize);
// Remove by ID
resourceStorage.RemoveByID(componentId);
// Get by ID
void* data = resourceStorage.GetByID(componentId);Memory Efficiency
struct ResourceSlot {
union Storage {
alignas(64) std::byte inlineData[64]; // SBO: no allocation
void* heapPtr; // Large resources
};
// ... metadata
};
// Memory usage tracking
size_t bytes = resourceStorage.GetMemoryUsage();Runtime Reflection System (NEW)
TypeMeta - Complete Type Metadata
const TypeMeta* meta = GetMeta<Transform>();
// Introspect fields
meta->ForEachField([](const FieldInfo& field) {
std::cout << field.name << " : " << field.typeName << "\n";
});
// Dynamic field access
meta->SetFieldValue(&transform, "x", 100.0f);
float x = meta->GetFieldValue<float>(&transform, "x").value();FieldInfo - Field Descriptors with Accessors
- Type-erased
Get<T>()andSet<T>()methods - Offset, size, alignment metadata
- Compile-time type trait detection (isConst, isPointer, isTrivial)
- Attribute query support
Attribute System
Decorator metadata for fields and types:
ASTRA_REFLECT_TYPE(Transform)
ASTRA_REFLECT_FIELD(Transform, x)
ASTRA_REFLECT_ATTR(Range, -1000.0, 1000.0)
ASTRA_REFLECT_ATTR(DisplayName, "X Position")
ASTRA_REFLECT_ATTR(Tooltip, "Horizontal position in world space")
ASTRA_REFLECT_FIELD(Transform, y)
ASTRA_REFLECT_FIELD(Transform, z);Built-in attributes:
Range- Min/max/step constraintsDisplayName- UI label overrideTooltip- Help textCategory- Property groupingHidden- Exclude from inspectionReadOnly- Prevent modificationSerializable- Control persistence
Enum Reflection
ASTRA_REFLECT_ENUM(DamageType)
ASTRA_REFLECT_ENUM_VALUE(DamageType, Physical)
ASTRA_REFLECT_ENUM_VALUE_NAMED(DamageType, Fire, "Fire Damage")
ASTRA_REFLECT_ENUM_FLAGS(); // Mark as bitmask
// Runtime conversion
auto name = enumInfo->ToString(DamageType::Fire); // "Fire Damage"
auto value = enumInfo->FromString("Physical"); // DamageType::PhysicalContainer Traits Detection
Automatic detection of container types:
std::vector,std::array,std::spanstd::map,std::unordered_mapstd::set,std::unordered_set- Custom containers via trait specialization
ECS-Reflection Integration (NEW)
ComponentDescriptor ↔ TypeMeta Linking
Components registered with the ECS are automatically linked to their reflection metadata:
// ComponentDescriptor now includes:
const TypeMeta* meta; // Auto-populated on registration
// Bidirectional lookup
const TypeMeta* meta = GetMeta(componentId);
ComponentID id = MetaRegistry::Instance().GetComponentId(typeHash);Entity Inspection API
Runtime inspection of entity components for editors:
std::vector<ComponentInfo> components = registry.InspectEntity(entity);
for (const auto& info : components) {
std::cout << "Component: " << info.descriptor->name << "\n";
if (info.meta) {
info.meta->ForEachField([&](const FieldInfo& field) {
// Render field in editor UI
RenderFieldWidget(field, info.data);
});
}
}Resource Inspection API
std::vector<ResourceInfo> resources = registry.InspectResources();
for (const auto& res : resources) {
std::cout << "Resource: " << res.descriptor->name << "\n";
// Access via res.meta and res.data
}Hash-Based Runtime Access
// Get components by type hash (runtime lookup)
void* component = registry.GetComponentByHash(entity, typeHash);
bool has = registry.HasComponentByHash(entity, typeHash);
// Get components by name
void* component = registry.GetComponentByName(entity, "Transform");
// Resources
void* resource = registry.GetResourceByHash(typeHash);
bool has = registry.HasResourceByHash(typeHash);JSON Schema Generation (NEW)
Automatic JSON Schema generation for editor integration:
JsonSchemaGenerator generator;
std::string schema = generator.Generate(*GetMeta<Transform>());Generates:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Transform",
"type": "object",
"properties": {
"x": { "type": "number", "minimum": -1000, "maximum": 1000 },
"y": { "type": "number" },
"z": { "type": "number" }
},
"required": ["x", "y", "z"]
}Architectural Improvements
MetaRegistry
Global singleton for reflection metadata:
- O(1) lookup by type hash via FlatMap
- Bidirectional component ID ↔ type hash mapping
- Thread-safe registration (single-threaded lookup)
- Lazy initialization via static builders
Reflection File Structure
include/Astra/Reflection/
Reflection.hpp # Main include
TypeMeta.hpp # Type metadata container
FieldInfo.hpp # Field descriptor with get/set
EnumInfo.hpp # Enum reflection
Attribute.hpp # Attribute base + common attributes
MetaRegistry.hpp # Global registry
ContainerTraits.hpp # Container type detection
JsonSchema.hpp # JSON schema generation
Macros.hpp # Registration macros
Requirements
- C++20 compatible compiler (GCC 11+, Clang 13+, MSVC 2022+)
- Premake5 for project generation
- Optional: GoogleTest, Google Benchmark
License
MIT License - See LICENSE file for details
Full Changelog: v2.0.0...v3.0.0
Astra 2.0.0
Astra ECS v2.0 - System Scheduling, Parallel Execution & Advanced Architecture
🎉 Major Release Highlights
Astra 2.0 is a massive update introducing system scheduling, parallel execution, command buffers, batch operations, and a complete architectural overhaul for production-ready performance.
New Parallel Performance
- 17x speedup on 20 cores for single component iteration
- ParallelForEach with automatic work distribution across cores
- Thread-safe chunk-based parallelization
🚀 Major New Features
System Scheduling Architecture (NEW)
- SystemScheduler class for organizing and executing ECS systems
- Automatic parallelization based on component access patterns
- Lambda system support with automatic Read/Write detection via const qualifiers
- System traits for declaring component dependencies (Reads/Writes)
- Custom executor interface for job system integration
- Dependency analysis for safe parallel execution
Command Buffer Architecture (NEW)
- Deferred operations for safe modifications during iteration
- Thread-safe command recording from multiple sources
- Batch command execution with minimal overhead
- Entity remapping for cloning and prefab instantiation
- Undo/redo support foundation
Parallel Execution in Views (NEW)
- ParallelForEach method for automatic parallelization
- Chunk-based work distribution for load balancing
- Thread-safe iteration with per-chunk processing
- Configurable executor support
Const Components in Views (NEW)
- Type-safe const components -
View<Position, const Velocity> - Compile-time enforcement preventing accidental modifications
Batch Component Operations (NEW)
- AddComponentBatch for efficient bulk additions
- RemoveComponentBatch for bulk removals
FlatSet Container (NEW)
- Open-addressing hash set with cache-friendly layout
- SIMD-accelerated lookups using metadata bytes
- Swiss table algorithm for efficient probing
📦 Architectural Restructuring
Entity Management Overhaul
- EntityManager with for entity orchestration/lifecycle
- EntityTable for version lookups/state management
- EntityIDStack managing free/recycled IDs
Archetype System Redesign
- ArchetypeManager as central archetype controller
- ArchetypeGraph for efficient archetype transitions
- ArchetypeChunkPool for optimized chunk allocation/reuse
🛠️ Requirements
- C++20 compatible compiler (GCC 11+, Clang 13+, MSVC 2022+)
- Premake5 for project generation
- Optional: GoogleTest, Google Benchmark
📝 License
MIT License - See LICENSE file for details
Full Changelog: v1.0.0...v2.0.0
Astra 1.0.0
Astra ECS v1.0 - High-Performance Entity Component System
🚀 Overview
Astra is a header-only Entity Component System (ECS) library for modern C++20, delivering high performance through archetype-based storage and aggressive optimization techniques.
⚡ Performance Highlights
- ~1.04B entities/second single-component iteration (hot cache)
- ~465M entities/second dual-component iteration
- Sub-nanosecond per-entity overhead in tight loops
- 2x faster batch entity creation vs individual creation
🎯 Key Features
Core Architecture
- Archetype-based storage with automatic entity grouping by component composition
- Structure-of-Arrays (SoA) layout for optimal cache utilization
- 16KB chunk-based allocation aligned to modern CPU cache hierarchies
- O(1) component access via direct ComponentID indexing
Advanced Capabilities
- Relationship graphs - Hierarchical parent-child and bidirectional entity links
- Query system with compile-time iteration optimizations
- Optional components (
Optional<T>) - Exclusion filters (
Not<T>) - Any-of queries (
Any<T...>) - One-of constraints (
OneOf<T...>)
- Optional components (
- Memory defragmentation - Configurable chunk coalescing and archetype cleanup
- Binary serialization with compression support (LZ4/ZSTD)
- Cross-platform SIMD abstractions (SSE2/SSE4.2/AVX2/NEON)
🛠️ Requirements
- C++20 compatible compiler (GCC 11+, Clang 13+, MSVC 2022+)
- Premake5
- Optional: GoogleTest for tests, Google Benchmark for benchmarks
📝 License
MIT License - See LICENSE file for details
Full Changelog: https://github.com/T3mps/Astra/commits/1.0.0