From b390eeab753a1c4d44c8fea8f04e1eb418eec869 Mon Sep 17 00:00:00 2001 From: Bengineer Date: Mon, 20 Jan 2025 12:10:34 +0000 Subject: [PATCH 1/4] feat: add documentation for read_schema command and its usage --- docs/pages/framework/world/api.md | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/pages/framework/world/api.md b/docs/pages/framework/world/api.md index 4fc230c9..e266cf26 100644 --- a/docs/pages/framework/world/api.md +++ b/docs/pages/framework/world/api.md @@ -105,6 +105,43 @@ let moves = world.read_model(player); world.erase_model(@moves); ``` +## `read_schema` + +The `read_schema` command allows for effient reading of parts of models. To define a schema the members name and type need to match the model, it needs to have `Serde` and `Introspect` derived and the model cannot be packed. + +```rust +#[derive(Copy, Drop, Serde, Debug, Introspect)] +struct AStruct { + a: u8, + b: u8, + c: u8, + d: u8, +} + +#[dojo::model] +#[derive(Copy, Drop, Serde, Debug)] +struct Foo4 { + #[key] + id: felt252, + v0: u256, + v1: felt252, + v2: u128, + v3: AStruct, +} + +#[derive(Copy, Drop, Serde, Debug, Introspect)] +struct Oo { + v0: u256, + v3: AStruct, +} + +fn something(){ + let id: felt252 = 12; + let values: Oo = world.read_schema(Model::::ptr_from_keys(id)); + ... +} +``` + ## World Interface The world exposes an interface which can be interacted with by any client. It is worth noting here that as a developer you don't deploy this world, it is deployed when you [migrate](/toolchain/sozo) your project as it is part of the `dojo-core` library. From 3462df83c4b1af38c4ee49ad2aba370eb86e876a Mon Sep 17 00:00:00 2001 From: Bengineer Date: Tue, 21 Jan 2025 19:56:35 +0000 Subject: [PATCH 2/4] feat: add documentation for read_member and read_members commands --- docs/pages/framework/world/api.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/pages/framework/world/api.md b/docs/pages/framework/world/api.md index e266cf26..1a4b35ff 100644 --- a/docs/pages/framework/world/api.md +++ b/docs/pages/framework/world/api.md @@ -81,6 +81,16 @@ world.write_model(@position); Here we are updating the `Position` model in the world state using the `player` as the entity id. +## `read_member` and `read_members` + +The `read_member` command is used to read a member in a model. + +```rust +let vec: Vec2 = world.read_member(Model::::ptr_from_keys(player), selector!("vec")); + +let vecs: Array = world.read_members(Model::::ptr_from_keys([player1, player2].span()), selector!("vec")); +``` + ## `emit_event` The `emit_event` command is used to emit [custom events](/framework/world/events.md#custom-events). These events are indexed by [Torii](/toolchain/torii). @@ -105,7 +115,7 @@ let moves = world.read_model(player); world.erase_model(@moves); ``` -## `read_schema` +## `read_schema` and `read_schemas` The `read_schema` command allows for effient reading of parts of models. To define a schema the members name and type need to match the model, it needs to have `Serde` and `Introspect` derived and the model cannot be packed. @@ -137,7 +147,11 @@ struct Oo { fn something(){ let id: felt252 = 12; - let values: Oo = world.read_schema(Model::::ptr_from_keys(id)); + let schema: Oo = world.read_schema(Model::::ptr_from_keys(id)); + + let ids: Span = [12, 13].span(); + let schemas: Array = world.read_schemas(Model::::ptrs_from_keys(ids)); + ... } ``` From 34e6538fccddf984f2fe8811928795e81cd4b4a0 Mon Sep 17 00:00:00 2001 From: Bengineer Date: Tue, 21 Jan 2025 20:02:12 +0000 Subject: [PATCH 3/4] added write_members --- docs/pages/framework/world/api.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/pages/framework/world/api.md b/docs/pages/framework/world/api.md index 1a4b35ff..00858fa7 100644 --- a/docs/pages/framework/world/api.md +++ b/docs/pages/framework/world/api.md @@ -83,7 +83,7 @@ Here we are updating the `Position` model in the world state using the `player` ## `read_member` and `read_members` -The `read_member` command is used to read a member in a model. +The `read_member` and `read_members` commands are used to read members from models. ```rust let vec: Vec2 = world.read_member(Model::::ptr_from_keys(player), selector!("vec")); @@ -91,6 +91,21 @@ let vec: Vec2 = world.read_member(Model::::ptr_from_keys(player), sele let vecs: Array = world.read_members(Model::::ptr_from_keys([player1, player2].span()), selector!("vec")); ``` +## `write_member` and `write_members` + +The `write_member` and `write_members` commands are used to write members to models. + +```rust + +let vec = Vec2{x: 1, y: 2}; +let vec_b = Vec2{x: 3, y: 4} +world.write_member(Model::::ptr_from_keys(player), selector!("vec"), vec); +let vecs = [vec, vec_b].span(); +world.write_members(Model::::ptr_from_keys([player1, player2].span()), selector!("vec"), vecs); +``` + + + ## `emit_event` The `emit_event` command is used to emit [custom events](/framework/world/events.md#custom-events). These events are indexed by [Torii](/toolchain/torii). From db240db9d5c726ca3eb19c7e102635b676cc3405 Mon Sep 17 00:00:00 2001 From: glihm Date: Mon, 27 Jan 2025 23:20:30 -0600 Subject: [PATCH 4/4] fix: reword + detail examples --- docs/pages/framework/world/api.md | 333 ++++++++++++++++++++++++++---- 1 file changed, 290 insertions(+), 43 deletions(-) diff --git a/docs/pages/framework/world/api.md b/docs/pages/framework/world/api.md index 00858fa7..b5704d82 100644 --- a/docs/pages/framework/world/api.md +++ b/docs/pages/framework/world/api.md @@ -33,7 +33,7 @@ struct Moves { ## `read_model` -The `read_model` command is used to retrieve models from the world state: +The `read_model` command is used to retrieve a model from the world state: ```rust let player = get_caller_address(); @@ -44,7 +44,7 @@ let mut position: Position = world.read_model(player); Here we are retrieving the `Position` model from the world state. We are also using the `caller` to retrieve the models for the current entity. :::warning -As a recall, a model must have at least one [key](/framework/models#the-key-attribute) to be retrievable. In this example, we assume that `Position` model has exactly one key, `caller`. +As a recall, a model must have at least one [key](/framework/models#the-key-attribute) to be retrievable. In this example, the model `Position` has exactly one key, `caller`. ::: You can then use `position` as you would as any other Cairo struct. @@ -62,7 +62,7 @@ If you use the `read_model` command on a model that has never been set before, a ## `write_model` -The `write_model` command is used to update models state. +The `write_model` command is used to update a model's state. ```rust let player = get_caller_address(); @@ -81,31 +81,36 @@ world.write_model(@position); Here we are updating the `Position` model in the world state using the `player` as the entity id. -## `read_member` and `read_members` +## `read_member` and `read_member_of_models` -The `read_member` and `read_members` commands are used to read members from models. +The `read_member` and `read_member_of_models` commands are used to read a member from model(s). ```rust +// The `ptr_from_keys` function is used to get the pointer to the model in the storage, for the given keys. +// Then the `selector!` macro is used to get the member to target. let vec: Vec2 = world.read_member(Model::::ptr_from_keys(player), selector!("vec")); -let vecs: Array = world.read_members(Model::::ptr_from_keys([player1, player2].span()), selector!("vec")); +// In this case, several entities are trageted, which will return an array of the member values. +// This function call could be read in english by: "Read the member `vec` from the models `Position` for the entities `player1` and `player2`." +let vecs: Array = world.read_member_of_models(Model::::ptr_from_keys([player1, player2].span()), selector!("vec")); ``` -## `write_member` and `write_members` +## `write_member` and `write_member_of_models` -The `write_member` and `write_members` commands are used to write members to models. +The `write_member` and `write_member_of_models` commands are used to write a member to a model. ```rust - let vec = Vec2{x: 1, y: 2}; let vec_b = Vec2{x: 3, y: 4} +// Same logic as the `read_member` command. world.write_member(Model::::ptr_from_keys(player), selector!("vec"), vec); + +// In this case, several entities are trageted, which will write the member to the models for the given entities. +// This function call could be read in english by: "Write the member `vec` to the model `Position` for the entities `player1` and `player2`." let vecs = [vec, vec_b].span(); -world.write_members(Model::::ptr_from_keys([player1, player2].span()), selector!("vec"), vecs); +world.write_member_of_models(Model::::ptr_from_keys([player1, player2].span()), selector!("vec"), vecs); ``` - - ## `emit_event` The `emit_event` command is used to emit [custom events](/framework/world/events.md#custom-events). These events are indexed by [Torii](/toolchain/torii). @@ -132,7 +137,7 @@ world.erase_model(@moves); ## `read_schema` and `read_schemas` -The `read_schema` command allows for effient reading of parts of models. To define a schema the members name and type need to match the model, it needs to have `Serde` and `Introspect` derived and the model cannot be packed. +The `read_schema` command allows for effient reading of parts of models. To define a schema the members name and type need to match the model, it needs to have `Serde` and `Introspect` derived and the model cannot be packed. If you plan to only read/write one member, `read_member` and `write_member` are more efficient. When two or more members are read, `read_schema` is more efficient. ```rust #[derive(Copy, Drop, Serde, Debug, Introspect)] @@ -155,19 +160,24 @@ struct Foo4 { } #[derive(Copy, Drop, Serde, Debug, Introspect)] -struct Oo { +struct MySchema { + // Name and type must match the model. v0: u256, + // Name and type must match the model. v3: AStruct, } fn something(){ - let id: felt252 = 12; - let schema: Oo = world.read_schema(Model::::ptr_from_keys(id)); + let id: felt252 = 12; + let schema: MySchema = world.read_schema(Model::::ptr_from_keys(id)); - let ids: Span = [12, 13].span(); - let schemas: Array = world.read_schemas(Model::::ptrs_from_keys(ids)); + // If an other struct has `v0` and `v3` as members (with the same types), you can read it like this: + // (The id could be something different, it is just an example) + let schema: MySchema = world.read_schema(Model::::ptr_from_keys(id)); - ... + // If you want to read multiple schemas, you can use the `read_schemas` command. + let ids: Span = [12, 13].span(); + let schemas: Array = world.read_schemas(Model::::ptrs_from_keys(ids)); } ``` @@ -181,41 +191,278 @@ However, you can interact with the world contract directly if you need to for in let game_id = world.uuid(); ``` -Here's the full [world's API](https://github.com/dojoengine/dojo/blob/main/crates/dojo/core/src/world/iworld.cairo): +Here's the full [world's API](https://github.com/dojoengine/dojo/blob/main/crates/dojo/core/src/world/iworld.cairo) for low level access: ```rust #[starknet::interface] -trait IWorld { - fn metadata(self: @T, resource_id: felt252) -> ResourceMetadata; +pub trait IWorld { + /// Returns the resource from its selector. + /// + /// # Arguments + /// * `selector` - the resource selector + /// + /// # Returns + /// * `Resource` - the resource data associated with the selector. + fn resource(self: @T, selector: felt252) -> Resource; + + /// Issues an autoincremented id to the caller. + /// This functionalities is useful to generate unique, but sequential ids. + /// + /// Note: This functionalities may impact performances since transaction paralellisation can't + /// be achieved since the same storage slot is being written. + fn uuid(ref self: T) -> usize; + + /// Returns the metadata of the resource. + /// + /// # Arguments + /// + /// `resource_selector` - The resource selector. + fn metadata(self: @T, resource_selector: felt252) -> ResourceMetadata; + + /// Sets the metadata of the resource. + /// + /// # Arguments + /// + /// `metadata` - The metadata content for the resource. fn set_metadata(ref self: T, metadata: ResourceMetadata); - fn model(self: @T, selector: felt252) -> (ClassHash, ContractAddress); - fn register_model(ref self: T, class_hash: ClassHash); - fn deploy_contract( - ref self: T, salt: felt252, class_hash: ClassHash, init_calldata: Span + + /// Registers a namespace in the world. + /// + /// # Arguments + /// + /// * `namespace` - The name of the namespace to be registered. + fn register_namespace(ref self: T, namespace: ByteArray); + + /// Registers an event in the world. + /// + /// # Arguments + /// + /// * `namespace` - The namespace of the event to be registered. + /// * `class_hash` - The class hash of the event to be registered. + fn register_event(ref self: T, namespace: ByteArray, class_hash: ClassHash); + + /// Registers a model in the world. + /// + /// # Arguments + /// + /// * `namespace` - The namespace of the model to be registered. + /// * `class_hash` - The class hash of the model to be registered. + fn register_model(ref self: T, namespace: ByteArray, class_hash: ClassHash); + + /// Registers and deploys a contract associated with the world and returns the address of newly + /// deployed contract. + /// + /// # Arguments + /// + /// * `salt` - The salt use for contract deployment. + /// * `namespace` - The namespace of the contract to be registered. + /// * `class_hash` - The class hash of the contract. + fn register_contract( + ref self: T, salt: felt252, namespace: ByteArray, class_hash: ClassHash, ) -> ContractAddress; - fn upgrade_contract(ref self: T, address: ContractAddress, class_hash: ClassHash) -> ClassHash; - fn uuid(ref self: T) -> usize; - fn emit(self: @T, keys: Array, values: Span); + + /// Initializes a contract associated registered in the world. + /// + /// As a constructor call, the initialization function can be called only once, and only + /// callable by the world itself. + /// + /// Also, the caller of this function must have the writer owner permission for the contract + /// resource. + fn init_contract(ref self: T, selector: felt252, init_calldata: Span); + + /// Upgrades an event in the world. + /// + /// # Arguments + /// + /// * `namespace` - The namespace of the event to be upgraded. + /// * `class_hash` - The class hash of the event to be upgraded. + fn upgrade_event(ref self: T, namespace: ByteArray, class_hash: ClassHash); + + /// Upgrades a model in the world. + /// + /// # Arguments + /// + /// * `namespace` - The namespace of the model to be upgraded. + /// * `class_hash` - The class hash of the model to be upgraded. + fn upgrade_model(ref self: T, namespace: ByteArray, class_hash: ClassHash); + + /// Upgrades an already deployed contract associated with the world and returns the new class + /// hash. + /// + /// # Arguments + /// + /// * `namespace` - The namespace of the contract to be upgraded. + /// * `class_hash` - The class hash of the contract. + fn upgrade_contract(ref self: T, namespace: ByteArray, class_hash: ClassHash) -> ClassHash; + + /// Emits a custom event that was previously registered in the world. + /// The dojo event emission is permissioned, since data are collected by + /// Torii and served to clients. + /// + /// # Arguments + /// + /// * `event_selector` - The selector of the event. + /// * `keys` - The keys of the event. + /// * `values` - The data to be logged by the event. + fn emit_event(ref self: T, event_selector: felt252, keys: Span, values: Span); + + /// Emits multiple events. + /// Permissions are only checked once, then the events are batched. + /// + /// # Arguments + /// + /// * `event_selector` - The selector of the event. + /// * `keys` - The keys of the event. + /// * `values` - The data to be logged by the event. + fn emit_events( + ref self: T, + event_selector: felt252, + keys: Span>, + values: Span>, + ); + + /// Gets the values of a model entity/member. + /// Returns a zero initialized model value if the entity/member has not been set. + /// + /// # Arguments + /// + /// * `model_selector` - The selector of the model to be retrieved. + /// * `index` - The index of the entity/member to read. + /// * `layout` - The memory layout of the model. + /// + /// # Returns + /// + /// * `Span` - The serialized value of the model, zero initialized if not set. fn entity( - self: @T, model: felt252, keys: Span, layout: dojo::database::introspect::Layout + self: @T, model_selector: felt252, index: ModelIndex, layout: Layout, ) -> Span; + + /// Gets the model values for the given entities. + /// + /// # Arguments + /// + /// * `model_selector` - The selector of the model to be retrieved. + /// * `indices` - The indexes of the entities/members to read. + /// * `layout` - The memory layout of the model. + fn entities( + self: @T, model_selector: felt252, indexes: Span, layout: Layout, + ) -> Span>; + + /// Sets the model value for the given entity/member. + /// + /// # Arguments + /// + /// * `model_selector` - The selector of the model to be set. + /// * `index` - The index of the entity/member to write. + /// * `values` - The value to be set, serialized using the model layout format. + /// * `layout` - The memory layout of the model. fn set_entity( ref self: T, - model: felt252, - keys: Span, + model_selector: felt252, + index: ModelIndex, values: Span, - layout: dojo::database::introspect::Layout + layout: Layout, ); - fn delete_entity( - ref self: T, model: felt252, keys: Span, layout: dojo::database::introspect::Layout + + /// Sets the model values for the given entities. + /// The permissions are only checked once, then the writes are batched. + /// + /// # Arguments + /// + /// * `model_selector` - The selector of the model to be set. + /// * `indexes` - The indexes of the entities/members to write. + /// * `values` - The values to be set, serialized using the model layout format. + /// * `layout` - The memory layout of the model. + fn set_entities( + ref self: T, + model_selector: felt252, + indexes: Span, + values: Span>, + layout: Layout, + ); + + /// Deletes a model value for the given entity/member. + /// Deleting is setting all the values to 0 in the given layout. + /// + /// # Arguments + /// + /// * `model_selector` - The selector of the model to be deleted. + /// * `index` - The index of the entity/member to delete. + /// * `layout` - The memory layout of the model. + fn delete_entity(ref self: T, model_selector: felt252, index: ModelIndex, layout: Layout); + + /// Deletes the model values for the given entities. + /// The permissions are only checked once, then the deletes are batched. + /// + /// # Arguments + /// + /// * `model_selector` - The selector of the model to be deleted. + /// * `indexes` - The indexes of the entities/members to delete. + /// * `layout` - The memory layout of the model. + fn delete_entities( + ref self: T, model_selector: felt252, indexes: Span, layout: Layout, ); - fn base(self: @T) -> ClassHash; - fn is_owner(self: @T, address: ContractAddress, resource: felt252) -> bool; - fn grant_owner(ref self: T, address: ContractAddress, resource: felt252); - fn revoke_owner(ref self: T, address: ContractAddress, resource: felt252); - - fn is_writer(self: @T, model: felt252, contract: ContractAddress) -> bool; - fn grant_writer(ref self: T, model: felt252, contract: ContractAddress); - fn revoke_writer(ref self: T, model: felt252, contract: ContractAddress); + + /// Returns true if the provided account has owner permission for the resource, false otherwise. + /// + /// # Arguments + /// + /// * `resource` - The selector of the resource. + /// * `address` - The address of the contract. + fn is_owner(self: @T, resource: felt252, address: ContractAddress) -> bool; + + /// Grants owner permission to the address. + /// Can only be called by an existing owner or the world admin. + /// + /// Note that this resource must have been registered to the world first. + /// + /// # Arguments + /// + /// * `resource` - The selector of the resource. + /// * `address` - The address of the contract to grant owner permission to. + fn grant_owner(ref self: T, resource: felt252, address: ContractAddress); + + /// Revokes owner permission to the contract for the resource. + /// Can only be called by an existing owner or the world admin. + /// + /// Note that this resource must have been registered to the world first. + /// + /// # Arguments + /// + /// * `resource` - The selector of the resource. + /// * `address` - The address of the contract to revoke owner permission from. + fn revoke_owner(ref self: T, resource: felt252, address: ContractAddress); + + + /// Returns true if the provided contract has writer permission for the resource, false + /// otherwise. + /// + /// # Arguments + /// + /// * `resource` - The selector of the resource. + /// * `contract` - The address of the contract. + fn is_writer(self: @T, resource: felt252, contract: ContractAddress) -> bool; + + /// Grants writer permission to the contract for the resource. + /// Can only be called by an existing resource owner or the world admin. + /// + /// Note that this resource must have been registered to the world first. + /// + /// # Arguments + /// + /// * `resource` - The selector of the resource. + /// * `contract` - The address of the contract to grant writer permission to. + fn grant_writer(ref self: T, resource: felt252, contract: ContractAddress); + + /// Revokes writer permission to the contract for the resource. + /// Can only be called by an existing resource owner or the world admin. + /// + /// Note that this resource must have been registered to the world first. + /// + /// # Arguments + /// + /// * `resource` - The selector of the resource. + /// * `contract` - The address of the contract to revoke writer permission from. + fn revoke_writer(ref self: T, resource: felt252, contract: ContractAddress); } ```