Skip to content

Commit

Permalink
feat(dojo-core): add support to read/write the same member from multi…
Browse files Browse the repository at this point in the history
…ple models (#2939)

* feat(model-storage): enhance model storage with new read/write methods for multiple members

* chamged read-members

* Removed unused code

* feat(model): add read/write methods for  multiple members and tests

* refactor(model-storage): rename read/write methods for clarity and consistency

* tests: regenerate test db

---------

Co-authored-by: glihm <[email protected]>
  • Loading branch information
bengineer42 and glihm authored Jan 24, 2025
1 parent 3405af9 commit 9b722fd
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 5 deletions.
49 changes: 49 additions & 0 deletions crates/dojo/core-cairo-test/src/tests/model/model.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,54 @@ fn test_model_ptr_from_entity_id() {
assert!(foo.v1 == v1);
}

#[test]
fn test_read_member() {
let mut world = spawn_foo_world();
let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 };
world.write_model(@foo);
let v1: u128 = world.read_member(foo.ptr(), selector!("v1"));
let v2: u32 = world.read_member(foo.ptr(), selector!("v2"));
assert!(foo.v1 == v1);
assert!(foo.v2 == v2);
}

#[test]
fn test_read_members() {
let mut world = spawn_foo_world();
let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 };
let foo2 = Foo { k1: 5, k2: 6, v1: 7, v2: 8 };
world.write_models([@foo, @foo2].span());
let ptrs = [foo.ptr(), foo2.ptr()].span();
let v1s: Array<u128> = world.read_member_of_models(ptrs, selector!("v1"));
let v2s: Array<u32> = world.read_member_of_models(ptrs, selector!("v2"));
assert!(v1s == array![foo.v1, foo2.v1]);
assert!(v2s == array![foo.v2, foo2.v2]);
}

#[test]
fn test_write_member() {
let mut world = spawn_foo_world();
let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 };
world.write_model(@foo);
world.write_member(foo.ptr(), selector!("v1"), 42);
let foo_read: Foo = world.read_model((foo.k1, foo.k2));
assert!(foo_read.v1 == 42 && foo_read.v2 == foo.v2);
}
#[test]
fn test_write_members() {
let mut world = spawn_foo_world();
let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 };
let foo2 = Foo { k1: 5, k2: 6, v1: 7, v2: 8 };
world.write_models([@foo, @foo2].span());
let ptrs = [foo.ptr(), foo2.ptr()].span();
let v1s = array![42, 43];
world.write_member_of_models(ptrs, selector!("v1"), v1s.span());
let v1s_read: Array<u128> = world.read_member_of_models(ptrs, selector!("v1"));
let v2s_read: Array<u32> = world.read_member_of_models(ptrs, selector!("v2"));
assert!(v1s_read == v1s);
assert!(v2s_read == array![foo.v2, foo2.v2]);
}

#[test]
fn test_ptr_from() {
let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 };
Expand All @@ -205,6 +253,7 @@ fn test_ptr_from() {
assert!(ptr_a == ptr_b && ptr_a == ptr_c && ptr_a == ptr_d);
}

#[test]
fn test_ptrs_from() {
let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 };
let foo2 = Foo { k1: 3, k2: 4, v1: 5, v2: 6 };
Expand Down
2 changes: 1 addition & 1 deletion crates/dojo/core/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub mod model {
pub use definition::{ModelIndex, ModelDefinition, ModelDef};

pub mod model;
pub use model::{Model, KeyParser, ModelPtr};
pub use model::{Model, KeyParser, ModelPtr, ModelPtrsTrait};

pub mod model_value;
pub use model_value::{ModelValue, ModelValueKey};
Expand Down
25 changes: 24 additions & 1 deletion crates/dojo/core/src/model/model.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use dojo::{
utils::{entity_id_from_serialized_keys, find_model_field_layout, entity_id_from_keys},
};

use super::{ModelDefinition, ModelDef};
use super::{ModelDefinition, ModelDef, ModelIndex};
/// Trait `KeyParser` defines a trait for parsing keys from a given model.
///
/// A pointer to a model, which can be expressed by an entity id.
Expand All @@ -12,6 +12,29 @@ pub struct ModelPtr<M> {
pub id: felt252,
}

pub trait ModelPtrsTrait<M> {
fn to_indexes(self: Span<ModelPtr<M>>) -> Span<ModelIndex>;
fn to_member_indexes(self: Span<ModelPtr<M>>, field_selector: felt252) -> Span<ModelIndex>;
}

pub impl ModelPtrsImpl<M> of ModelPtrsTrait<M> {
fn to_indexes(self: Span<ModelPtr<M>>) -> Span<ModelIndex> {
let mut ids = ArrayTrait::<ModelIndex>::new();
for ptr in self {
ids.append(ModelIndex::Id(*ptr.id));
};
ids.span()
}

fn to_member_indexes(self: Span<ModelPtr<M>>, field_selector: felt252) -> Span<ModelIndex> {
let mut ids = ArrayTrait::<ModelIndex>::new();
for ptr in self {
ids.append(ModelIndex::MemberId((*ptr.id, field_selector)));
};
ids.span()
}
}

pub trait KeyParser<M, K> {
/// Parses the key from the given model.
fn parse_key(self: @M) -> K;
Expand Down
14 changes: 12 additions & 2 deletions crates/dojo/core/src/model/storage.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,24 @@ pub trait ModelStorage<S, M> {
/// The ptr is mostly used for type inferrence.
fn erase_models_ptrs(ref self: S, ptrs: Span<ModelPtr<M>>);

/// Retrieves a model of type `M` using the provided entity idref .
/// Retrieves a model of type `M` using the provided entity id.
fn read_member<T, +Serde<T>>(self: @S, ptr: ModelPtr<M>, field_selector: felt252) -> T;

/// Retrieves a model of type `M` using the provided entity id.
/// Retrieves a single member from multiple models.
fn read_member_of_models<T, +Serde<T>, +Drop<T>>(
self: @S, ptrs: Span<ModelPtr<M>>, field_selector: felt252,
) -> Array<T>;

/// Updates a member of a model.
fn write_member<T, +Serde<T>, +Drop<T>>(
ref self: S, ptr: ModelPtr<M>, field_selector: felt252, value: T,
);

/// Updates a member of multiple models.
fn write_member_of_models<T, +Serde<T>, +Drop<T>>(
ref self: S, ptrs: Span<ModelPtr<M>>, field_selector: felt252, values: Span<T>,
);

/// Returns the current namespace hash.
fn namespace_hash(self: @S) -> felt252;
}
Expand Down
36 changes: 35 additions & 1 deletion crates/dojo/core/src/world/storage.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

use core::panic_with_felt252;
use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait, Resource};
use dojo::model::{Model, ModelIndex, ModelValueKey, ModelValue, ModelStorage, ModelPtr};
use dojo::model::{
Model, ModelIndex, ModelValueKey, ModelValue, ModelStorage, ModelPtr, ModelPtrsTrait,
};
use dojo::event::{Event, EventStorage};
use dojo::meta::Layout;
use dojo::utils::{
Expand Down Expand Up @@ -207,6 +209,22 @@ pub impl ModelStorageWorldStorageImpl<M, +Model<M>, +Drop<M>> of ModelStorage<Wo
),
)
}

fn read_member_of_models<T, +Serde<T>, +Drop<T>>(
self: @WorldStorage, ptrs: Span<ModelPtr<M>>, field_selector: felt252,
) -> Array<T> {
let mut values: Array<T> = array![];
for entity in IWorldDispatcherTrait::entities(
*self.dispatcher,
Model::<M>::selector(*self.namespace_hash),
ptrs.to_member_indexes(field_selector),
field_layout_unwrap::<M>(field_selector),
) {
values.append(deserialize_unwrap(*entity));
};
values
}

fn write_member<T, +Serde<T>, +Drop<T>>(
ref self: WorldStorage, ptr: ModelPtr<M>, field_selector: felt252, value: T,
) {
Expand All @@ -219,6 +237,22 @@ pub impl ModelStorageWorldStorageImpl<M, +Model<M>, +Drop<M>> of ModelStorage<Wo
);
}

fn write_member_of_models<T, +Serde<T>, +Drop<T>>(
ref self: WorldStorage, ptrs: Span<ModelPtr<M>>, field_selector: felt252, values: Span<T>,
) {
let mut serialized_values = ArrayTrait::<Span<felt252>>::new();
for value in values {
serialized_values.append(serialize_inline(value));
};
IWorldDispatcherTrait::set_entities(
self.dispatcher,
Model::<M>::selector(self.namespace_hash),
ptrs.to_member_indexes(field_selector),
serialized_values.span(),
field_layout_unwrap::<M>(field_selector),
);
}

fn erase_models_ptrs(ref self: WorldStorage, ptrs: Span<ModelPtr<M>>) {
let mut indexes: Array<ModelIndex> = array![];
for ptr in ptrs {
Expand Down
Binary file modified spawn-and-move-db.tar.gz
Binary file not shown.
Binary file modified types-test-db.tar.gz
Binary file not shown.

0 comments on commit 9b722fd

Please sign in to comment.