Skip to content

Latest commit

 

History

History
189 lines (134 loc) · 5.55 KB

README.md

File metadata and controls

189 lines (134 loc) · 5.55 KB

Dōjō

Dojo is a full stack toolchain for developing onchain games in Cairo. Dojo leverages the affordances provided by Cairo language plugins to offer a best-in-class developer experience for easily integrating blockchain properties into games.

  • Simple composition through the Entity Component System pattern
  • Concise implementations leveraging language plugins and macros
  • Expressive query system with efficiently compiled strategies
  • Typed interface generation for client libraries

The toolchain includes the following:

  • dojo-ecs: A concise and efficient implementation of the Entity Component System pattern.
  • dojo-migrate: Deploy, migrate, and manage the entities, components, and systems in the world.
  • dojo-bind: Generate bindings for various languages / frameworks (typescript, phaser / rust, bevy).

Development

Setup Submodules

git submodule update --init --recursive

Development container

It is recommended to use the dev container when building on DoJo as it contains everything needed to begin developing.

Make sure you update your Docker to the latest stable version, sometimes the Dev containers do not play nicely with old Docker versions.

Restart VSCode for this to take effect

Open and build container

Command pallete: ctrl + shift + p

Then: Remote-Containers: Rebuild Container Without Cache

Setup the language server

cd cairo/vscode-cairo

npm install --global @vscode/vsce
npm install
vsce package
code --install-extension cairo1*.vsix

cd /workspaces/dojo

cargo build --bin dojo-language-server --release

Development without container

  • Install Rust
  • Setup Rust:
rustup override set stable && rustup update && cargo test

Then install the language like described above.


Overview

Entity Component System

Dojo implements the ECS pattern which is subsequently compiled to Starknet contracts for deployment. The syntax and semantics are heavily inspired by Bevy.

Worlds

A world is the top-level concept in an onchain game, serving as a centralized registry, namespace, and event bus for all entities, components, systems, and resources.

The worlds interface is as follows:

trait World {
    #[event]
    fn ValueSet(component: felt252, key: StorageKey, offset: u8, value: Span<felt252>) {}

    #[event]
    fn ComponentRegistered(name: felt252, class_hash: ClassHash) {}

    #[event]
    fn SystemRegistered(name: felt252, class_hash: ClassHash) {}

    // Returns a globally unique identifier.
    #[view]
    fn uuid() -> felt252;

    // Returns a globally unique identifier.
    #[view]
    fn get(component: felt252, key: StorageKey, offset: u8, length: usize) -> Span<felt252>;

    // Returns all entities that contain the component.
    #[view]
    fn all(component: felt252, partition: felt252) -> Array<StorageKey>;

    // Sets a components value.
    #[external]
    fn set(component: felt252, key: StorageKey, offset: u8, value: Span<felt252>);

    // Returns all entities that contain the component.
    #[external]
    fn register_component(name: felt252, class_hash: ClassHash);

    // Returns all entities that contain the component.
    #[external]
    fn register_system(name: felt252, class_hash: ClassHash);
}

Components

Components in dojo-ecs are modules with a single struct describing its state, for example, the following implements a Position component which exposes a is_zero and is_equal method.

#[derive(Component)]
struct Position {
    x: u32,
    y: u32
}

trait PositionTrait {
    fn is_zero(self: Position) -> bool;
    fn is_equal(self: Position, b: Position) -> bool;
}

impl PositionImpl of PositionTrait {
    fn is_zero(self: Position) -> bool {
        match self.x - self.y {
            0 => bool::True(()),
            _ => bool::False(()),
        }
    }

    fn is_equal(self: Position, b: Position) -> bool {
        self.x == b.x & self.y == b.y
    }
}

Systems

A system is a pure function that takes as input a set of entities to operate on. Systems define a Query which describes a set of criteria to query entities with.

#[system]
mod SpawnSystem {
    #[execute]
    fn spawn(name: String) {
        let player_id = commands::create((
            Health::new(100_u8),
            Name::new(name)
        ));
        return ();
    }
}

#[system]
mod MoveSystem {
    #[execute]
    fn move(player_id: usize) {
        let player = commands<(Health, Name)>::get(player_id);
        let positions = commands<(Position, Health)>::all();

        // @NOTE: Loops are not available in Cairo 1.0 yet.
        for (position, health) in positions {
            let is_zero = position.is_zero();
        }
        return ();
    }
}

Entities

An entity is addressed by a felt. An entity represents a collection of component state. A component can set state for an arbitrary entity, registering itself with the world as a side effect.

Events

Events are emitted anytime a components state is updated a ValueSet event is emitted from the world, enabling clients to easily track changes to world state.

Migrate

Given addresses of every component / system in the world is deterministically addressable, the dojo-migrate cli takes a world address as entrypoint and diffs the onchain state with the compiled state, generating a deploying + migration plan for declaring and registering new components and / or updating existing components.

Bind

Bind is a cli for generating typed interfaces for integration with various client libraries / languages.