Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dojo-core): add read schema support #2932

Merged
merged 14 commits into from
Jan 27, 2025
80 changes: 76 additions & 4 deletions crates/dojo/core-cairo-test/src/tests/model/model.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,39 @@ struct Foo3 {
v2: u32,
}

#[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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use more descriptive names please.

v0: u256,
v3: AStruct,
}

fn namespace_def() -> NamespaceDef {
NamespaceDef {
namespace: "dojo_cairo_test",
resources: [
namespace: "dojo_cairo_test", resources: [
TestResource::Model(m_Foo::TEST_CLASS_HASH.try_into().unwrap()),
TestResource::Model(m_Foo2::TEST_CLASS_HASH.try_into().unwrap()),
]
.span(),
TestResource::Model(m_Foo3::TEST_CLASS_HASH.try_into().unwrap()),
TestResource::Model(m_Foo4::TEST_CLASS_HASH.try_into().unwrap()),
].span()
}
}

Expand Down Expand Up @@ -194,3 +219,50 @@ fn test_model_ptr_from_entity_id() {
let v1 = world.read_member(ptr, selector!("v1"));
assert!(foo.v1 == v1);
}


#[test]
fn test_read_schema() {
let mut world = spawn_foo_world();
let foo = Foo4 { id: 1, v0: 2, v1: 3, v2: 4, v3: AStruct { a: 5, b: 6, c: 7, d: 8 } };
world.write_model(@foo);

let schema: Oo = world.read_schema(foo.ptr());
assert!(
schema.v0 == foo.v0
&& schema.v3.a == foo.v3.a
&& schema.v3.b == foo.v3.b
&& schema.v3.c == foo.v3.c
&& schema.v3.d == foo.v3.d
);
}


#[test]
fn test_read_schemas() {
let mut world = spawn_foo_world();
let foo = Foo4 { id: 1, v0: 2, v1: 3, v2: 4, v3: AStruct { a: 5, b: 6, c: 7, d: 8 } };
let mut foo_2 = foo;
foo_2.id = 2;
foo_2.v0 = 12;

world.write_models([@foo, @foo_2].span());

let mut values: Array<Oo> = world.read_schemas([foo.ptr(), foo_2.ptr()].span());
let schema_1 = values.pop_front().unwrap();
let schema_2 = values.pop_front().unwrap();
assert!(
schema_1.v0 == foo.v0
&& schema_1.v3.a == foo.v3.a
&& schema_1.v3.b == foo.v3.b
&& schema_1.v3.c == foo.v3.c
&& schema_1.v3.d == foo.v3.d
);
assert!(
schema_2.v0 == foo_2.v0
&& schema_2.v3.a == foo_2.v3.a
&& schema_2.v3.b == foo_2.v3.b
&& schema_2.v3.c == foo_2.v3.c
&& schema_2.v3.d == foo_2.v3.d
);
}
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
16 changes: 15 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,20 @@ pub struct ModelPtr<M> {
pub id: felt252,
}

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

pub impl ModelPtrsImpl<M> of ModelPtrsTrait<M> {
fn to_indexes(self: Span<ModelPtr<M>>) -> Span<ModelIndex> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you document this function and why it has been necessary adding it?

let mut ids = ArrayTrait::<ModelIndex>::new();
for ptr in self {
ids.append(ModelIndex::Id(*ptr.id));
};
ids.span()
}
}

pub trait KeyParser<M, K> {
/// Parses the key from the given model.
fn parse_key(self: @M) -> K;
Expand Down
14 changes: 11 additions & 3 deletions crates/dojo/core/src/model/storage.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use dojo::{model::{ModelPtr, model_value::ModelValueKey}};
use dojo::{model::{ModelPtr, model_value::ModelValueKey}, meta::Introspect};

// TODO: define the right interface for member accesses.

Expand Down Expand Up @@ -35,10 +35,18 @@ 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 part of a model, matching a schema.
fn read_schema<T, +Serde<T>, +Introspect<T>>(self: @S, ptr: ModelPtr<M>) -> T;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a bit more comments here, to show what a schema is.


/// Retrieves part of multiple models, matching a schema.
fn read_schemas<T, +Drop<T>, +Serde<T>, +Introspect<T>>(
self: @S, ptrs: Span<ModelPtr<M>>
) -> Array<T>;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing trailing commas in function definitions

In the definitions of read_schema and read_schemas methods (lines 42-47), trailing commas are missing in the parameter lists, which is causing pipeline failures.

Apply this diff to fix the issue:

 fn read_schema<T, +Serde<T>, +Introspect<T>>(self: @S, ptr: ModelPtr<M>) -> T;

 /// Retrieves part of multiple models, matching a schema.
 fn read_schemas<T, +Drop<T>, +Serde<T>, +Introspect<T>>(
     self: @S, ptrs: Span<ModelPtr<M>>,
 ) -> Array<T>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn read_schema<T, +Serde<T>, +Introspect<T>>(self: @S, ptr: ModelPtr<M>) -> T;
/// Retrieves part of multiple models, matching a schema.
fn read_schemas<T, +Drop<T>, +Serde<T>, +Introspect<T>>(
self: @S, ptrs: Span<ModelPtr<M>>
) -> Array<T>;
fn read_schema<T, +Serde<T>, +Introspect<T>>(self: @S, ptr: ModelPtr<M>,) -> T;
/// Retrieves part of multiple models, matching a schema.
fn read_schemas<T, +Drop<T>, +Serde<T>, +Introspect<T>>(
self: @S, ptrs: Span<ModelPtr<M>>,
) -> Array<T>;
🧰 Tools
🪛 GitHub Actions: ci

[error] 43-46: Missing trailing comma in function parameter list

/// 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,
);
Expand Down
46 changes: 44 additions & 2 deletions crates/dojo/core/src/world/storage.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

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::meta::{Layout, FieldLayout, Introspect};
use dojo::utils::{
entity_id_from_keys, entity_id_from_serialized_keys, serialize_inline, find_model_field_layout,
deserialize_unwrap,
Expand All @@ -24,6 +26,17 @@ fn field_layout_unwrap<M, +Model<M>>(field_selector: felt252) -> Layout {
}
}

fn make_partial_struct_layout<M, +Model<M>>(field_selectors: Span<felt252>) -> Layout {
let mut layouts: Array<FieldLayout> = array![];
for selector in field_selectors {
layouts
.append(
FieldLayout { selector: *selector, layout: field_layout_unwrap::<M>(*selector) }
);
};
Layout::Struct(layouts.span())
}

#[generate_trait]
pub impl WorldStorageInternalImpl of WorldStorageTrait {
fn new(world: IWorldDispatcher, namespace: @ByteArray) -> WorldStorage {
Expand Down Expand Up @@ -188,6 +201,35 @@ pub impl ModelStorageWorldStorageImpl<M, +Model<M>, +Drop<M>> of ModelStorage<Wo
),
)
}

fn read_schema<T, +Serde<T>, +Introspect<T>>(self: @WorldStorage, ptr: ModelPtr<M>) -> T {
deserialize_unwrap(
IWorldDispatcherTrait::entity(
*self.dispatcher,
Model::<M>::selector(*self.namespace_hash),
ModelIndex::Id(ptr.id),
Introspect::<T>::layout()
)
)
}

fn read_schemas<T, +Drop<T>, +Serde<T>, +Introspect<T>>(
self: @WorldStorage, ptrs: Span<ModelPtr<M>>
) -> Array<T> {
let mut values = ArrayTrait::<T>::new();

for entity in IWorldDispatcherTrait::entities(
*self.dispatcher,
Model::<M>::selector(*self.namespace_hash),
ptrs.to_indexes(),
Introspect::<T>::layout()
) {
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 Down
Loading