Skip to content
The world

The world

A world holds entities. An entity is nothing but an identity, and what it is comes from the components attached to it: a position, a health value, a tag. Each kind of component is declared once, in a schema.

fn layout() -> schema {
  let position = with_field_decl(with_field_decl(component_decl_of("position"),
    "x", kind_int()), "y", kind_int());
  let health = with_field_decl(component_decl_of("health"), "points", kind_int());
  with_component_decl(schema_of(position), health)
}

fn position() -> int { 0 }

fn health() -> int { 1 }

fn main() -> int {
  let w = world_new(layout());
  let hero = world_spawn(w);
  world_set(w, hero, position(), word(value: 2, next: word(value: 3, next: words_end())));
  world_set(w, hero, health(), word(value: 10, next: words_end()));
  print(world_text(w))
}
bootstrap/qdx run std/*.qdx engine/*.qdx hero.qdx
tick 0
slots 1
entity @0#1
  position x=2 y=3
  health points=10

Schemas

A schema lists the component kinds a world can hold. Each kind has a name and fields, and each field has a name and one of four kinds:

KindHolds
kind_int()A whole number.
kind_fixed()A fixed-point number.
kind_bool()0 for false, anything else for true.
kind_entity()A handle to an entity.

A component with no fields is a tag: an entity either has it or does not.

Components are numbered in the order they are declared, starting at 0, and the world’s functions take that number. Give each number a named function, as position() and health() do above. A schema holds at most 63 component kinds.

Handles

world_spawn returns a handle, an int that names one entity. It is written @0#1: slot 0, generation 1.

When an entity is destroyed its slot is freed, and a later spawn may use the slot again with a higher generation. A handle to the old entity then no longer matches, so world_is_alive is false for it and every other function refuses it. A stale handle can never reach the wrong entity.

Freed slots are reused oldest first.

Reading

FunctionResultMeaning
world_is_alive(w, handle)boolWhether the handle names a living entity.
world_has(w, handle, component)boolWhether the entity has that component.
world_field(w, handle, component, field)intThe value of one field, by its position in the component. Stops the program if the entity lacks the component.
world_count(w)intThe number of living entities.
world_tick(w)intThe current tick.
world_text(w)bytesThe whole world as text.

Changing

These functions change the world in place. In a game they are called only by the tick step, never by game rules.

FunctionResultMeaning
world_spawn(w)intA new entity with no components. Returns its handle.
world_destroy(w, handle)boolRemoves the entity and all its components. False if it was not alive.
world_set(w, handle, component, data)boolAttaches or replaces a component. False if the entity is not alive, the component does not exist, or data has the wrong number of values.
world_remove(w, handle, component)boolDetaches a component. False if the entity did not have it.

Component data is a words value, a list of int, with one value per field in declared order: word(value: 2, next: word(value: 3, next: words_end())).

The text form

world_text writes the tick, the number of slots ever used, and every living entity in slot order with its components in declared order. Two worlds with the same content always produce the same text, which makes it the way to compare them.

Snapshots

FunctionResultMeaning
world_snapshot(w)intKeeps the world as it is now. Returns a snapshot.
world_restore(w, snapshot)intPuts the world back to the snapshot. The snapshot stays usable.
snapshot_release(w, snapshot)intLets go of a snapshot that is no longer needed.

Taking a snapshot copies nothing. The world’s memory is kept in blocks of one page, and a snapshot shares them with the live world. The first change to a shared block copies that one block. The cost of a snapshot is therefore the size of what changes afterwards, not the size of the world.

Memory

World data lives in memory taken directly from the operating system, outside the heap that ordinary qdx values use. That is what allows the tick loop to throw away everything on the heap after each tick without touching the world.