diff --git a/backend/dojo_examples/combat_game/src/helpers/pseudo_random.cairo b/backend/dojo_examples/combat_game/src/helpers/pseudo_random.cairo index f43679b..7169a93 100644 --- a/backend/dojo_examples/combat_game/src/helpers/pseudo_random.cairo +++ b/backend/dojo_examples/combat_game/src/helpers/pseudo_random.cairo @@ -1,10 +1,10 @@ /// A module for generating pseudo-random numbers using block data for entropy. pub mod PseudoRandom { - use super::*; - use core::pedersen::PedersenTrait; use core::hash::HashStateTrait; - use core::starknet::{get_block_timestamp, get_block_number}; use core::num::traits::{WrappingAdd, WrappingMul}; + use core::pedersen::PedersenTrait; + use core::starknet::{get_block_number, get_block_timestamp}; + use super::*; /// Generates a pseudo-random `u8` value within a specified range `[min, max]`. /// diff --git a/backend/dojo_examples/combat_game/src/helpers/timestamp.cairo b/backend/dojo_examples/combat_game/src/helpers/timestamp.cairo index de5cf1f..46322e1 100644 --- a/backend/dojo_examples/combat_game/src/helpers/timestamp.cairo +++ b/backend/dojo_examples/combat_game/src/helpers/timestamp.cairo @@ -1,8 +1,8 @@ // Core imports -use core::traits::TryInto; // Constants imports use combat_game::constants; +use core::traits::TryInto; #[generate_trait] pub impl Timestamp of TimestampTrait { diff --git a/backend/dojo_examples/combat_game/src/lib.cairo b/backend/dojo_examples/combat_game/src/lib.cairo index 1eb0142..b2b48ff 100644 --- a/backend/dojo_examples/combat_game/src/lib.cairo +++ b/backend/dojo_examples/combat_game/src/lib.cairo @@ -29,9 +29,9 @@ mod types { } mod helpers { + pub mod experience_utils; pub mod pseudo_random; pub mod timestamp; - pub mod experience_utils; } pub mod utils { diff --git a/backend/dojo_examples/combat_game/src/models/bag.cairo b/backend/dojo_examples/combat_game/src/models/bag.cairo index 7a4ebde..28a6b40 100644 --- a/backend/dojo_examples/combat_game/src/models/bag.cairo +++ b/backend/dojo_examples/combat_game/src/models/bag.cairo @@ -33,10 +33,10 @@ impl BagActions of BagActionsTrait { #[cfg(test)] mod tests { - use super::{Bag, BagActions}; - use crate::types::potion::Potion; - use starknet::contract_address_const; use core::result::ResultTrait; + use starknet::contract_address_const; + use crate::types::potion::Potion; + use super::{Bag, BagActions}; #[test] #[available_gas(300000)] diff --git a/backend/dojo_examples/combat_game/src/models/battle.cairo b/backend/dojo_examples/combat_game/src/models/battle.cairo index 62f4e14..f629567 100644 --- a/backend/dojo_examples/combat_game/src/models/battle.cairo +++ b/backend/dojo_examples/combat_game/src/models/battle.cairo @@ -1,9 +1,7 @@ -use starknet::{ContractAddress, get_block_timestamp}; +use combat_game::helpers::pseudo_random::PseudoRandom::generate_random_u8; +use combat_game::types::battle_status::BattleStatus; use core::num::traits::zero::Zero; -use combat_game::{ - helpers::{pseudo_random::PseudoRandom::generate_random_u8}, - types::{battle_status::BattleStatus}, -}; +use starknet::{ContractAddress, get_block_timestamp}; #[derive(Copy, Drop, Serde, Debug, Introspect, PartialEq)] #[dojo::model] @@ -77,10 +75,10 @@ pub impl BattleImpl of BattleTrait { #[cfg(test)] mod tests { + use combat_game::types::battle_status::BattleStatus; use core::num::traits::zero::Zero; - use starknet::{contract_address_const}; + use starknet::contract_address_const; use super::{Battle, BattleTrait}; - use combat_game::types::battle_status::BattleStatus; #[test] fn test_end() { diff --git a/backend/dojo_examples/combat_game/src/models/beast.cairo b/backend/dojo_examples/combat_game/src/models/beast.cairo index f6c0bc0..6c2c2ca 100644 --- a/backend/dojo_examples/combat_game/src/models/beast.cairo +++ b/backend/dojo_examples/combat_game/src/models/beast.cairo @@ -5,8 +5,8 @@ use crate::constants::{ NOT_VERY_EFFECTIVE, SUPER_EFFECTIVE, }; use crate::models::skill::SkillTrait; -use crate::types::skill::SkillType; use crate::types::beast_type::BeastType; +use crate::types::skill::SkillType; #[derive(Copy, Drop, Serde, Debug, PartialEq)] #[dojo::model] diff --git a/backend/dojo_examples/combat_game/src/models/beast_stats.cairo b/backend/dojo_examples/combat_game/src/models/beast_stats.cairo index 983d413..0a6966c 100644 --- a/backend/dojo_examples/combat_game/src/models/beast_stats.cairo +++ b/backend/dojo_examples/combat_game/src/models/beast_stats.cairo @@ -1,8 +1,8 @@ -use crate::{ - types::{status_condition::StatusCondition, beast_type::BeastType}, - helpers::pseudo_random::PseudoRandom, -}; -use core::{poseidon::poseidon_hash_span, num::traits::Bounded}; +use core::num::traits::Bounded; +use core::poseidon::poseidon_hash_span; +use crate::helpers::pseudo_random::PseudoRandom; +use crate::types::beast_type::BeastType; +use crate::types::status_condition::StatusCondition; #[derive(Introspect, Copy, Drop, Serde, Debug, PartialEq)] #[dojo::model] @@ -174,7 +174,7 @@ pub impl BeastStatsActions of BeastStatsActionTrait { #[cfg(test)] mod tests { - use super::{BeastStats, BeastStatsActionTrait, StatusCondition, BeastType}; + use super::{BeastStats, BeastStatsActionTrait, BeastType, StatusCondition}; // take_damage() tests #[test] diff --git a/backend/dojo_examples/combat_game/src/models/potion.cairo b/backend/dojo_examples/combat_game/src/models/potion.cairo index edcf518..3de0ce4 100644 --- a/backend/dojo_examples/combat_game/src/models/potion.cairo +++ b/backend/dojo_examples/combat_game/src/models/potion.cairo @@ -40,9 +40,9 @@ pub impl PotionImpl of PotionTrait { #[cfg(test)] mod tests { - use super::{Potion, PotionTrait}; - use crate::types::rarity::Rarity; use core::num::traits::Bounded; + use crate::types::rarity::Rarity; + use super::{Potion, PotionTrait}; #[test] #[available_gas(300000)] diff --git a/backend/dojo_examples/combat_game/src/models/skill.cairo b/backend/dojo_examples/combat_game/src/models/skill.cairo index 091e30b..ae07f97 100644 --- a/backend/dojo_examples/combat_game/src/models/skill.cairo +++ b/backend/dojo_examples/combat_game/src/models/skill.cairo @@ -67,12 +67,12 @@ pub impl SkillImpl of SkillTrait { #[cfg(test)] mod tests { use super::{ - SkillImpl, SkillType, SLASH_SKILL_ID, BEAM_SKILL_ID, WAVE_SKILL_ID, PUNCH_SKILL_ID, - KICK_SKILL_ID, BLAST_SKILL_ID, CRUSH_SKILL_ID, PIERCE_SKILL_ID, SMASH_SKILL_ID, - BURN_SKILL_ID, FREEZE_SKILL_ID, SHOCK_SKILL_ID, DEFAULT_SKILL_ID, SLASH_SKILL_DAMAGE, - BEAM_SKILL_DAMAGE, WAVE_SKILL_DAMAGE, PUNCH_SKILL_DAMAGE, KICK_SKILL_DAMAGE, - BLAST_SKILL_DAMAGE, CRUSH_SKILL_DAMAGE, PIERCE_SKILL_DAMAGE, SMASH_SKILL_DAMAGE, - BURN_SKILL_DAMAGE, FREEZE_SKILL_DAMAGE, SHOCK_SKILL_DAMAGE, DEFAULT_SKILL_DAMAGE, + BEAM_SKILL_DAMAGE, BEAM_SKILL_ID, BLAST_SKILL_DAMAGE, BLAST_SKILL_ID, BURN_SKILL_DAMAGE, + BURN_SKILL_ID, CRUSH_SKILL_DAMAGE, CRUSH_SKILL_ID, DEFAULT_SKILL_DAMAGE, DEFAULT_SKILL_ID, + FREEZE_SKILL_DAMAGE, FREEZE_SKILL_ID, KICK_SKILL_DAMAGE, KICK_SKILL_ID, PIERCE_SKILL_DAMAGE, + PIERCE_SKILL_ID, PUNCH_SKILL_DAMAGE, PUNCH_SKILL_ID, SHOCK_SKILL_DAMAGE, SHOCK_SKILL_ID, + SLASH_SKILL_DAMAGE, SLASH_SKILL_ID, SMASH_SKILL_DAMAGE, SMASH_SKILL_ID, SkillImpl, + SkillType, WAVE_SKILL_DAMAGE, WAVE_SKILL_ID, }; #[test] diff --git a/backend/dojo_examples/combat_game/src/store.cairo b/backend/dojo_examples/combat_game/src/store.cairo index dfdd3d4..86aeb1a 100644 --- a/backend/dojo_examples/combat_game/src/store.cairo +++ b/backend/dojo_examples/combat_game/src/store.cairo @@ -1,19 +1,19 @@ -use dojo::{model::ModelStorage, world::WorldStorage}; +use combat_game::constants::{BASE_BATTLE_EXPERIENCE, SECONDS_PER_DAY}; +use combat_game::helpers::experience_utils::ExperienceCalculatorImpl; +use combat_game::models::battle::{Battle, BattleTrait}; +use combat_game::models::beast::{Beast, BeastTrait}; +use combat_game::models::beast_skill::BeastSkill; +use combat_game::models::beast_stats::{BeastStats, BeastStatsActionTrait}; +use combat_game::models::player::Player; +use combat_game::models::skill; +use combat_game::models::skill::{Skill, SkillTrait}; +use combat_game::types::battle_status::BattleStatus; +use combat_game::types::beast_type::BeastType; +use combat_game::types::skill::SkillType; +use combat_game::types::status_condition::StatusCondition; use core::num::traits::zero::Zero; -use combat_game::{ - constants::{BASE_BATTLE_EXPERIENCE, SECONDS_PER_DAY}, - models::{ - player::Player, beast::{Beast, BeastTrait}, skill, skill::{Skill, SkillTrait}, - beast_skill::BeastSkill, beast_stats::{BeastStats, BeastStatsActionTrait}, - battle::{Battle, BattleTrait}, - }, - types::{ - beast_type::BeastType, skill::SkillType, status_condition::StatusCondition, - battle_status::BattleStatus, - }, - helpers::experience_utils::ExperienceCalculatorImpl, -}; - +use dojo::model::ModelStorage; +use dojo::world::WorldStorage; use starknet::ContractAddress; #[derive(Drop, Copy)] @@ -320,9 +320,11 @@ pub impl StoreImpl of StoreTrait { if level_up_occurred { // Calculate remaining exp - beast.experience = ExperienceCalculatorImpl::remaining_exp_after_level_up( - beast.level, beast.experience, - ); + beast + .experience = + ExperienceCalculatorImpl::remaining_exp_after_level_up( + beast.level, beast.experience, + ); beast.level += 1; // Update beast stats diff --git a/backend/dojo_examples/combat_game/src/systems/battle.cairo b/backend/dojo_examples/combat_game/src/systems/battle.cairo index 8a3ce2a..05e7ffc 100644 --- a/backend/dojo_examples/combat_game/src/systems/battle.cairo +++ b/backend/dojo_examples/combat_game/src/systems/battle.cairo @@ -8,21 +8,19 @@ pub trait IBattle { #[dojo::contract] pub mod battle_system { - use super::IBattle; - use combat_game::store::{StoreTrait}; - use combat_game::models::{battle::{BattleTrait}, player::{AssertTrait}}; - use combat_game::types::battle_status::BattleStatus; + use achievement::components::achievable::AchievableComponent; + use achievement::store::{Store as AchievementStore, StoreTrait as AchievementStoreTrait}; use combat_game::achievements::achievement::{Achievement, AchievementTrait}; - - use starknet::{get_caller_address, get_block_timestamp, ContractAddress}; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use combat_game::models::battle::BattleTrait; + use combat_game::models::player::AssertTrait; + use combat_game::store::StoreTrait; + use combat_game::types::battle_status::BattleStatus; use core::num::traits::zero::Zero; - - use achievement::components::achievable::AchievableComponent; - use achievement::store::{StoreTrait as AchievementStoreTrait, Store as AchievementStore}; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ContractAddress, get_block_timestamp, get_caller_address}; + use super::IBattle; component!(path: AchievableComponent, storage: achievable, event: AchievableEvent); impl AchievableInternalImpl = AchievableComponent::InternalImpl; - use dojo::event::EventStorage; #[storage] diff --git a/backend/dojo_examples/combat_game/src/systems/beast.cairo b/backend/dojo_examples/combat_game/src/systems/beast.cairo index dffc9d0..deab409 100644 --- a/backend/dojo_examples/combat_game/src/systems/beast.cairo +++ b/backend/dojo_examples/combat_game/src/systems/beast.cairo @@ -1,5 +1,5 @@ -use combat_game::types::beast_type::BeastType; use combat_game::models::beast_stats::BeastStats; +use combat_game::types::beast_type::BeastType; #[starknet::interface] pub trait IBeast { @@ -10,11 +10,11 @@ pub trait IBeast { #[dojo::contract] pub mod beast_system { - use super::{IBeast, BeastType, BeastStats}; + use combat_game::models::beast_stats::BeastStatsActionTrait; + use combat_game::store::StoreTrait; use starknet::get_block_timestamp; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use combat_game::store::{StoreTrait}; - use combat_game::models::beast_stats::BeastStatsActionTrait; + use super::{BeastStats, BeastType, IBeast}; #[storage] struct Storage { diff --git a/backend/dojo_examples/combat_game/src/systems/player.cairo b/backend/dojo_examples/combat_game/src/systems/player.cairo index fdb8dcb..e95c9b4 100644 --- a/backend/dojo_examples/combat_game/src/systems/player.cairo +++ b/backend/dojo_examples/combat_game/src/systems/player.cairo @@ -6,13 +6,11 @@ pub trait IPlayer { #[dojo::contract] pub mod player_system { - use super::IPlayer; - use combat_game::models::player::{Player, PlayerAssert}; - use starknet::storage::{StoragePointerWriteAccess}; - use combat_game::store::{StoreTrait}; - - use starknet::{get_caller_address, get_block_timestamp}; + use combat_game::store::StoreTrait; + use starknet::storage::StoragePointerWriteAccess; + use starknet::{get_block_timestamp, get_caller_address}; + use super::IPlayer; #[storage] struct Storage { diff --git a/backend/dojo_examples/combat_game/src/tests/test_battle.cairo b/backend/dojo_examples/combat_game/src/tests/test_battle.cairo index 1375d7d..0506365 100644 --- a/backend/dojo_examples/combat_game/src/tests/test_battle.cairo +++ b/backend/dojo_examples/combat_game/src/tests/test_battle.cairo @@ -1,24 +1,25 @@ #[cfg(test)] mod battle_system { + use combat_game::models::battle::{Battle, BattleTrait, m_Battle}; + use combat_game::models::beast::m_Beast; + use combat_game::models::beast_skill::m_BeastSkill; + use combat_game::models::beast_stats::m_BeastStats; + use combat_game::models::player::{Player, m_Player}; + use combat_game::models::skill; + use combat_game::models::skill::m_Skill; + use combat_game::store::StoreTrait; + use combat_game::systems::battle::{IBattleDispatcher, IBattleDispatcherTrait, battle_system}; + use combat_game::types::battle_status::BattleStatus; + use combat_game::types::beast_type::BeastType; + use combat_game::types::status_condition::StatusCondition; + use core::num::traits::zero::Zero; + use dojo::model::ModelStorage; + use dojo::world::{WorldStorage, WorldStorageTrait}; use dojo_cairo_test::{ ContractDef, ContractDefTrait, NamespaceDef, TestResource, WorldStorageTestTrait, spawn_test_world, }; - use dojo::world::{WorldStorage, WorldStorageTrait}; - use dojo::model::{ModelStorage}; - use combat_game::systems::battle::{battle_system, IBattleDispatcher, IBattleDispatcherTrait}; - use combat_game::models::{ - player::{Player, m_Player}, battle::{Battle, m_Battle, BattleTrait}, beast::{m_Beast}, - beast_stats::{m_BeastStats}, beast_skill::{m_BeastSkill}, skill::{m_Skill}, skill, - }; - use combat_game::types::{ - battle_status::BattleStatus, beast_type::BeastType, status_condition::StatusCondition, - }; - use combat_game::store::{StoreTrait}; - - use starknet::{contract_address_const, ContractAddress}; - use starknet::{testing}; - use core::num::traits::zero::Zero; + use starknet::{ContractAddress, contract_address_const, testing}; // Constants for testing const BATTLE_TYPE: u8 = 0; diff --git a/backend/dojo_examples/combat_game/src/tests/test_beast.cairo b/backend/dojo_examples/combat_game/src/tests/test_beast.cairo index 8105074..656b324 100644 --- a/backend/dojo_examples/combat_game/src/tests/test_beast.cairo +++ b/backend/dojo_examples/combat_game/src/tests/test_beast.cairo @@ -1,10 +1,11 @@ #[cfg(test)] mod beast_integration_tests { - use combat_game::{ - models::{beast::{BeastTrait}, beast_stats::{BeastStatsActionTrait}}, - types::{beast_type::BeastType, skill::SkillType, status_condition::StatusCondition}, - }; - use starknet::{contract_address_const, ContractAddress}; + use combat_game::models::beast::BeastTrait; + use combat_game::models::beast_stats::BeastStatsActionTrait; + use combat_game::types::beast_type::BeastType; + use combat_game::types::skill::SkillType; + use combat_game::types::status_condition::StatusCondition; + use starknet::{ContractAddress, contract_address_const}; const PLAYER1_ADDRESS: felt252 = 0x1234; diff --git a/backend/dojo_examples/combat_game/src/tests/test_player.cairo b/backend/dojo_examples/combat_game/src/tests/test_player.cairo index ff501a6..2c844a6 100644 --- a/backend/dojo_examples/combat_game/src/tests/test_player.cairo +++ b/backend/dojo_examples/combat_game/src/tests/test_player.cairo @@ -1,31 +1,27 @@ #[cfg(test)] mod player_integration_tests { + use combat_game::constants::SECONDS_PER_DAY; + use combat_game::models::battle::{Battle, BattleTrait, m_Battle}; + use combat_game::models::beast::{Beast, m_Beast}; + use combat_game::models::beast_skill::{BeastSkill, m_BeastSkill}; + use combat_game::models::beast_stats::{BeastStats, BeastStatsActionTrait, m_BeastStats}; + use combat_game::models::player::{Player, PlayerAssert, m_Player}; + use combat_game::models::skill::{Skill, m_Skill}; + use combat_game::store::StoreTrait; + use combat_game::systems::battle::{IBattleDispatcher, IBattleDispatcherTrait, battle_system}; + use combat_game::systems::beast::{IBeastDispatcher, IBeastDispatcherTrait, beast_system}; + use combat_game::systems::player::{IPlayerDispatcher, IPlayerDispatcherTrait, player_system}; + use combat_game::types::battle_status::BattleStatus; + use combat_game::types::beast_type::BeastType; + use combat_game::types::status_condition::StatusCondition; + use core::num::traits::zero::Zero; + use dojo::model::ModelStorage; + use dojo::world::{WorldStorage, WorldStorageTrait}; use dojo_cairo_test::{ ContractDef, ContractDefTrait, NamespaceDef, TestResource, WorldStorageTestTrait, spawn_test_world, }; - use dojo::world::{WorldStorage, WorldStorageTrait}; - use dojo::model::{ModelStorage}; - use combat_game::systems::{ - player::{player_system, IPlayerDispatcher, IPlayerDispatcherTrait}, - beast::{beast_system, IBeastDispatcher, IBeastDispatcherTrait}, - battle::{battle_system, IBattleDispatcher, IBattleDispatcherTrait}, - }; - use combat_game::models::{ - player::{Player, m_Player, PlayerAssert}, beast::{Beast, m_Beast}, - beast_stats::{BeastStats, m_BeastStats, BeastStatsActionTrait}, - beast_skill::{BeastSkill, m_BeastSkill}, skill::{Skill, m_Skill}, - battle::{Battle, m_Battle, BattleTrait}, - }; - use combat_game::types::{ - beast_type::BeastType, battle_status::BattleStatus, status_condition::StatusCondition, - }; - use combat_game::store::{StoreTrait}; - use combat_game::constants::{SECONDS_PER_DAY}; - - use starknet::{contract_address_const, ContractAddress, get_block_timestamp}; - use starknet::{testing}; - use core::num::traits::zero::Zero; + use starknet::{ContractAddress, contract_address_const, get_block_timestamp, testing}; // Test constants const INITIAL_BEAST_ID: u16 = 1; @@ -629,7 +625,7 @@ mod player_integration_tests { while i < 100 { player_system.update_profile(true, beast_id); i += 1; - }; + } let store = StoreTrait::new(world); let player = store.read_player_from_address(PLAYER1()); diff --git a/client/pages/getting-started/basics/models/models-fundamentals.md b/client/pages/getting-started/basics/models/models-fundamentals.md new file mode 100644 index 0000000..31b50e2 --- /dev/null +++ b/client/pages/getting-started/basics/models/models-fundamentals.md @@ -0,0 +1,411 @@ +# Dojo Models Fundamentals + +Welcome to the **Models Fundamentals** guide for Dojo Engine! This document serves as your entry point to understanding models and the Entity Component System (ECS) architecture in Dojo. Whether you're new to game development or blockchain programming, this guide will build your foundation for creating on-chain games with Dojo. + +## Table of Contents + +1. [Introduction to Models and ECS](#introduction-to-models-and-ecs) +2. [Basic Model Anatomy](#basic-model-anatomy) +3. [Keys and Entity Identification](#keys-and-entity-identification) +4. [Field Types and Metadata](#field-types-and-metadata) +5. [Custom Types Implementation](#custom-types-implementation) +6. [Next Steps](#next-steps) + +## Introduction to Models and ECS + +### What are Models in Dojo Engine? + +In the Dojo Engine, a **model** is a fundamental data structure that defines the schema for storing game state on-chain. Think of models as blueprints for the attributes of game objects - like a player's stats, a beast's characteristics, or an item's properties. Models serve as the "data layer" in Dojo's ECS pattern, enabling efficient storage, querying, and updating of game state in a decentralized environment. + +### Understanding the ECS Pattern + +**ECS (Entity Component System)** is a design pattern that organizes game architecture into three core concepts: + +- **Entities**: Unique identifiers that represent game objects (like a specific player or beast) +- **Components**: Data containers that store attributes (in Dojo, these are **models**) +- **Systems**: Logic modules that process and manipulate the data + +### Why ECS Matters for Onchain Games + +The ECS approach provides several key benefits for blockchain game development: + +- **Modularity**: Separate data (models) from logic (systems) for cleaner code +- **Scalability**: Efficiently manage complex game states with many entities +- **Flexibility**: Easily add new features by creating new models and systems +- **Performance**: Optimized for Starknet's execution environment + +### Relationship Between Entities, Components, and Systems + +In Dojo's ECS implementation: + +1. **Entities** are identified by their key values (like a `ContractAddress` or composite key) +2. **Models** act as components, storing specific data about entities +3. **Systems** contain game logic that reads and updates models + +Example relationship: +``` +Entity: Player (ContractAddress: 0x123...) +├── Player model (battles_won, battles_lost, creation_day) +├── Beast model (level, experience, beast_type) +└── Inventory model (items, quantities) +``` + +## Basic Model Anatomy + +Every Dojo model follows a consistent structure using Cairo syntax and specific attributes. + +### The #[dojo::model] Attribute + +The `#[dojo::model]` attribute marks a Cairo struct as a Dojo model, registering it in the world contract for ECS integration: + +```cairo +#[derive(Copy, Drop, Serde, Debug, PartialEq)] +#[dojo::model] +pub struct Player { + #[key] + pub address: ContractAddress, + pub current_beast_id: u16, + pub battles_won: u16, + pub battles_lost: u16, + pub last_active_day: u32, + pub creation_day: u32, +} +``` + +### Simple Model Structure with Single Key + +The simplest model structure includes: +- Required attributes and derives +- A single `#[key]` field for entity identification +- Data fields using appropriate types + +```cairo +#[derive(Drop, Serde)] +#[dojo::model] +struct GameSession { + #[key] + pub session_id: u64, + pub player_count: u8, + pub status: u8, + pub created_at: u32, +} +``` + +### Basic Field Types + +Dojo models support Cairo's primitive types: + +- **`u8`, `u16`, `u32`, `u64`**: Unsigned integers of different sizes +- **`felt252`**: Field element (primary data type in Cairo) +- **`ContractAddress`**: Starknet contract addresses +- **`bool`**: Boolean values + +Choose types based on your data requirements: +- Use `u8` for small values (0-255) like levels or counts +- Use `u32` for larger values like timestamps +- Use `felt252` for strings and identifiers +- Use `ContractAddress` for player or contract references + +### Essential Derives + +Every model requires specific derives for proper functionality: + +- **`Drop`**: Enables safe memory deallocation (required) +- **`Serde`**: Enables serialization/deserialization (required) +- **`Copy`**: Allows copying instead of moving (optional, useful for simple data) +- **`Debug`**: Enables debug output (optional, helpful for development) +- **`PartialEq`**: Enables equality comparisons (optional) + +```cairo +#[derive(Copy, Drop, Serde, Debug, PartialEq)] +#[dojo::model] +pub struct SimpleItem { + #[key] + pub id: u32, + pub name: felt252, + pub value: u16, +} +``` + +## Keys and Entity Identification + +Keys are crucial for identifying and querying entities in your game world. + +### The #[key] Attribute and Its Purpose + +The `#[key]` attribute specifies which fields serve as the unique identifier for a model instance. Every model must have at least one key field, and all key fields must appear before non-key fields in the struct definition. + +### Single Key Pattern + +The most straightforward approach uses a single field as the key: + +```cairo +#[derive(Copy, Drop, Serde, Debug, PartialEq)] +#[dojo::model] +pub struct Player { + #[key] + pub address: ContractAddress, // Single key field + pub current_beast_id: u16, + pub battles_won: u16, + pub battles_lost: u16, +} +``` + +### Composite Key Pattern + +For more complex relationships, use multiple key fields to create composite keys: + +```cairo +#[derive(Copy, Drop, Serde, Debug, PartialEq)] +#[dojo::model] +pub struct Beast { + #[key] + pub player: ContractAddress, // First part of composite key + #[key] + pub beast_id: u16, // Second part of composite key + pub level: u8, + pub experience: u16, + pub beast_type: BeastType, +} +``` + +### Key Ordering Requirements + +Important rules for keys: +1. All `#[key]` fields must come before non-key fields +2. Key order in the struct defines the query order +3. All keys must be provided when querying composite key models + +### Querying Models with Different Key Structures + +How you query models depends on their key structure: + +```cairo +// Single key query +let player = world.read_model(player_address); + +// Composite key query (requires all key values) +let beast = world.read_model((player_address, beast_id)); +``` + +## Field Types and Metadata + +Understanding data types and metadata is essential for creating efficient models. + +### Cairo Primitive Types in Models + +Choose the appropriate type based on your data requirements: + +| Type | Range | Use Cases | +|------|-------|-----------| +| `u8` | 0-255 | Levels, small counters, percentages | +| `u16` | 0-65,535 | Larger counters, experience points | +| `u32` | 0-4.3B | Timestamps, large quantities | +| `u64` | 0-18.4 quintillion | Very large values, IDs | +| `felt252` | Field element | Names, strings, large identifiers | +| `ContractAddress` | Starknet address | Player addresses, contract references | + +### When to Use Each Type + +**Performance considerations:** +- Smaller types (`u8`, `u16`) use less storage and gas +- Larger types provide more range but cost more +- `felt252` is versatile but should be used thoughtfully + +**Example with appropriate type choices:** +```cairo +#[derive(Copy, Drop, Serde, Debug, PartialEq)] +#[dojo::model] +pub struct GameItem { + #[key] + pub id: u64, // Large range for unique IDs + pub name: felt252, // String data + pub level_required: u8, // Small range (1-100) + pub price: u32, // Medium range for currency + pub owner: ContractAddress, // Player reference +} +``` + +### Required vs Optional Derives + +**Required derives:** +- `Drop`: Memory management (always required) +- `Serde`: Data serialization (always required) + +**Optional but recommended derives:** +- `Copy`: For simple data that can be copied cheaply +- `Debug`: For development and testing +- `PartialEq`: For comparing model instances + +### Type Safety Considerations for Game Data + +Ensure your types match your game's requirements: + +```cairo +#[derive(Copy, Drop, Serde, Debug, PartialEq)] +#[dojo::model] +pub struct PlayerStats { + #[key] + pub player: ContractAddress, + pub health: u16, // 0-65535 HP range + pub mana: u16, // 0-65535 MP range + pub level: u8, // 1-255 level cap + pub experience: u32, // Large XP values +} +``` + +## Custom Types Implementation + +Advanced models often require custom types like enums and structs. + +### Implementing Custom Types in Models + +Custom types must implement the `Introspect` trait to be used in models. Here's an example with a custom enum: + +```cairo +#[derive(Copy, Drop, Serde, Debug, PartialEq, Introspect)] +pub enum BeastType { + Fire, + Water, + Earth, + Air, +} + +#[derive(Copy, Drop, Serde, Debug, PartialEq)] +#[dojo::model] +pub struct Beast { + #[key] + pub player: ContractAddress, + #[key] + pub beast_id: u16, + pub level: u8, + pub experience: u16, + pub beast_type: BeastType, // Custom enum type +} +``` + +### Introspect Trait Requirements + +The `Introspect` trait allows Dojo to understand your custom type's structure. For types you define, you can usually auto-derive it: + +```cairo +#[derive(Drop, Serde, Introspect)] +pub struct Stats { + pub attack: u8, + pub defense: u8, + pub speed: u8, +} +``` + +### Automatic Derivation vs Manual Implementation + +**Automatic derivation** (preferred when possible): +```cairo +#[derive(Drop, Serde, Introspect)] +pub enum Rarity { + Common, + Uncommon, + Rare, + Epic, + Legendary, +} +``` + +**Manual implementation** (for complex cases or external types): +```cairo +impl StatsIntrospect of dojo::database::introspect::Introspect { + #[inline(always)] + fn size() -> Option { + Option::Some(3) // 3 u8 fields + } + + fn layout() -> dojo::database::introspect::Layout { + // Custom layout implementation + } + + #[inline(always)] + fn ty() -> dojo::database::introspect::Ty { + // Type definition implementation + } +} +``` + +### IntrospectPacked for Space Efficiency + +Use `IntrospectPacked` when you want to optimize storage space for models with known, fixed sizes: + +```cairo +#[derive(Drop, Serde, IntrospectPacked, Debug)] +#[dojo::model] +struct Potion { + #[key] + id: u64, + name: felt252, + effect: u8, + rarity: Rarity, + power: u32, +} +``` + +**Benefits of IntrospectPacked:** +- Reduced storage costs +- More efficient packing of data + +**Limitations:** +- Cannot use dynamic types (`Array`, `ByteArray`) +- Less flexible for future upgrades + +### Custom Enums and Structs in Game Models + +Practical example combining custom types in a game context: + +```cairo +#[derive(Copy, Drop, Serde, Debug, PartialEq, Introspect)] +pub enum ItemType { + Weapon, + Armor, + Consumable, + Quest, +} + +#[derive(Copy, Drop, Serde, Debug, PartialEq, Introspect)] +pub struct ItemStats { + pub attack_bonus: u8, + pub defense_bonus: u8, + pub durability: u8, +} + +#[derive(Copy, Drop, Serde, Debug, PartialEq)] +#[dojo::model] +pub struct GameItem { + #[key] + pub id: u64, + #[key] + pub owner: ContractAddress, + pub item_type: ItemType, // Custom enum + pub stats: ItemStats, // Custom struct + pub quantity: u16, +} +``` + +## Next Steps + +Congratulations! You now understand the fundamentals of Dojo models and ECS architecture. You should be able to: + +- Define basic model structures with appropriate types +- Use single and composite keys for entity identification +- Implement custom types with the Introspect trait +- Choose appropriate field types for game data + +### Continue Your Learning Journey + +Now that you have a solid foundation, explore these advanced topics: + +- **[Models Patterns](/getting-started/basics/models/models-patterns.md)**: Learn advanced patterns like model relationships, validation, and optimization techniques +- **Systems Integration**: Discover how systems interact with your models +- **World Management**: Understand how the World contract orchestrates your game state +- **Query Optimization**: Advanced techniques for efficient model queries + + +### Remember: +Start simple, test thoroughly, and gradually add complexity as you become more comfortable with the ECS pattern in Dojo! \ No newline at end of file