I'd contend that using Rust for anything nontrivial results in MaybeUninit & co being common.
759 karma · joined February 5, 2020
I'd contend that using Rust for anything nontrivial results in MaybeUninit & co being common.
internal readonly struct CarComponent : IComponent {
public readonly float Speed = 100f;
[Slider(1,20f)] public readonly float Mass = 1;
public CarComponent() {}
}
and of a system: [Filter(typeof(CarComponent), typeof(AgentImpulseComponent))]
[Filter(ContextAccessorFilter.NoneOf, typeof(DisableAgentComponent), typeof(CarEngineStoppedComponent) )]
internal class CarMoverSystem : IFixedUpdateSystem {
public void FixedUpdate(Context context) {
foreach (var e in context.Entities) {
var car = e.GetComponent<CarComponent>();
Vector2 startVelocity = e.TryGetVelocity()?.Velocity ?? Vector2.Zero;
var result = DoSomeCalculations(car, startVelocity);
e.RemoveFriction();
e.SetVelocity(result);
}
}
}
Looks a bit like DOTS (but I haven't really tried that one either). A system is a class, and which interface it implements determines when it runs. Haven't seen yet where systems are get added to the game / how systems of the same kind get ordered. I'm also curious how a system that does multiple different querries (e.g. one for cars and one for obstacles) looks like.Bang seems to use source code generators – unlike the generic GetComponent<T> above, TryGetVelocity, RemoveFriction and SetVelocity seem to be generated based on the existence of the Velocity and Friction components. I'm not sure why that is, maybe to cache the component id?
This isn't an archetype-based framework, instead each entity stores a dictionary of component id => component and each system tracks what entities match (also, each entity stores hooks for e.g. when a component gets added, which is used update which systems know about this entity). I don't know how good that's for performance, but for plenty of games performance isn't as important as ease of development.
Also looks like changes are applied immediately. Does this mean that if a system add/remove a component to an entity that affects whether the entity matches the systems filter, it depends on the entity id whether the system processes the entity the same tick?
I like Roc's approach of detecting errors statically but still trying to let you run the code. If a snippet is work in progress and has an unused variable, Go or Zig will refuse to compile it. Yes, an unused variable indicates a problem, which is why it's not going to pass any sensible CI setup and make its way into production, but that doesn't mean I should be disallowed from checking whether what I've got so far works. Roc allows¹ running a program with type errors as long as you don't execute a code path affected by them, which I imagine is very useful for refactoring.
The platform approach is also interesting, but I don't know how it will play into code reuse. I guess the different io/platform interfaces might not be quite as big of a problem in a pure functional language? I'm not experienced enough to tell.
¹: I haven't checked how successful it is, given it's immaturity I expect there to be issues
You're absolutely right that it should be, but the problem is that it isn't – Python treats these as belonging to the local time zone.
The listed reasons also apply to in-memory storage – searching a btree node is faster than doing the equivalent amount of pointer traversals for a binary tree. Of course this comes at the cost of increased implementation complexity, but unless you're using C, you're probably not implementing your own tree-based map.
You can then use different variants such as packing more entries into interior nodes by only storing values in the leafs (assuming you're making a map and not just a set, that is). If you then link neighboring nodes, you basically have a skip list.
https://github.com/python/cpython/blob/5f7aba938cf5007b6f954...
Here's even a test for it: https://github.com/python/cpython/blob/5f7aba938cf5007b6f954...
In an ECS architecture, the world is a database of entities, which are just identifiers, which have components associated with them. Systems is simply code that query this database and operate on the returned data. For example, if you want burning entities to set burnable entities on fire, you might write a system like this, using Bevy as an example:
fn fire_spread(mut commands: Commands, burning: Query<&Collider, With<Burning>>, flammable: Query<(Entity, &Collider), With<Flammable>>) {
for col_1 in &burning {
for (flammable_entity, col_2) in &flammable {
if col_1.touches(col_2) {
commands.entity(flammable_entity).insert(Burning)
}
}
}
}
The Commands here exists to defer archetype moves – otherwise what should the query do if you added Burning to an entity while the query was running? And of course in a real game, you might want to use a spatial query so time complexity isn't m×n. You could then run this system every tick: app.add_systems(Update, fire_spread)
If you're familiar with C++, I can also recommend you check out https://www.flecs.dev/flecs/- There is user code that needs to run before main. With C/C++ allowing separate compilation, the compiler invocation that creates the entrypoint doesn't know about all of these static constructors.
- Therefore Collect2 pretends to be ld; when invoked, it tries to call the real ld and examines its output
- It creates a c file with a table containing any constructor symbols it finds, then compiles that
- Finally it links the program again, together with the new object file