From 4679918cdf827cb7c4a8b9534d2ee130de6ed6f0 Mon Sep 17 00:00:00 2001 From: Nasr Date: Fri, 10 Oct 2025 14:48:13 -0500 Subject: [PATCH 1/2] feat(1.8): multi world & achievements --- Cargo.lock | 38 ++-- Cargo.toml | 14 +- dojo.h | 463 +++++++++++++++++++++++++++++++++++++--------- dojo.hpp | 203 ++++++++++++++++++-- dojo.pyx | 371 ++++++++++++++++++++++++++++++------- src/c/mod.rs | 311 ++++++++++++++++++++++++++++--- src/c/types.rs | 266 +++++++++++++++++++++++++- src/wasm/mod.rs | 245 ++++++++++++++++++++++-- src/wasm/types.rs | 264 +++++++++++++++++++++++++- 9 files changed, 1942 insertions(+), 233 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d5ae9e..e3dde72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -645,9 +645,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cainome" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0634cab52751732346438836fbdb264d9ab47cfaaff51bffd9667178ce7e5ab5" +checksum = "f296568cda6f1f4934270a321151f9f51afa737450f67ef13a8534f2bd9afdac" dependencies = [ "anyhow", "async-trait", @@ -672,9 +672,9 @@ dependencies = [ [[package]] name = "cainome-cairo-serde" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "362d42b94a2402c8820debc2d05b0308e4aff6df0dc5c86569167437440764af" +checksum = "95ae2d4c21db23c7730a85187c2e9d73fe00c123171839185fb13f31550f3240" dependencies = [ "num-bigint", "serde", @@ -711,9 +711,9 @@ dependencies = [ [[package]] name = "cainome-rs" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a2f63aeefc4d30221469fffcfbdbb13e7311444ab91614729c5137c3a70cbb3" +checksum = "93aa5c33e66b58ff6723d3e43e60fe163ac20719ea89fce921096e654b15bcd6" dependencies = [ "anyhow", "cainome-cairo-serde", @@ -730,9 +730,9 @@ dependencies = [ [[package]] name = "cainome-rs-macro" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a53ebb721a24c3f2351b9214a6c2d2aaafaeecf5da15ee0c244dea95a7490c" +checksum = "9d679cd9a88b9d412ed4ed30264b80f1bec6c1aa9656e238f06be560ddebb652" dependencies = [ "anyhow", "cainome-cairo-serde", @@ -1363,7 +1363,7 @@ dependencies = [ [[package]] name = "dojo-c" -version = "1.7.5" +version = "1.8.3" dependencies = [ "account_sdk", "anyhow", @@ -1412,8 +1412,8 @@ dependencies = [ [[package]] name = "dojo-types" -version = "1.7.0" -source = "git+https://github.com/dojoengine/dojo?rev=6daa3d0#6daa3d0d08e83fd61176d9164a1df42b82ba35be" +version = "1.7.1" +source = "git+https://github.com/dojoengine/dojo?rev=0afeb1bc#0afeb1bc63f04bf6716e4a294366232f594edb78" dependencies = [ "anyhow", "cainome", @@ -1434,8 +1434,8 @@ dependencies = [ [[package]] name = "dojo-world" -version = "1.7.0" -source = "git+https://github.com/dojoengine/dojo?rev=6daa3d0#6daa3d0d08e83fd61176d9164a1df42b82ba35be" +version = "1.7.1" +source = "git+https://github.com/dojoengine/dojo?rev=0afeb1bc#0afeb1bc63f04bf6716e4a294366232f594edb78" dependencies = [ "anyhow", "async-trait", @@ -5167,8 +5167,8 @@ dependencies = [ [[package]] name = "torii-client" -version = "1.7.4" -source = "git+https://github.com/dojoengine/torii?rev=23d0da6#23d0da64bbb635d6a6d16b68b9d4a1755f9be310" +version = "1.8.2" +source = "git+https://github.com/dojoengine/torii?rev=8378c63#8378c636a6904f17ce04a011d2aee1bc3d84751a" dependencies = [ "async-trait", "crypto-bigint", @@ -5191,8 +5191,8 @@ dependencies = [ [[package]] name = "torii-grpc-client" -version = "1.7.4" -source = "git+https://github.com/dojoengine/torii?rev=23d0da6#23d0da64bbb635d6a6d16b68b9d4a1755f9be310" +version = "1.8.2" +source = "git+https://github.com/dojoengine/torii?rev=8378c63#8378c636a6904f17ce04a011d2aee1bc3d84751a" dependencies = [ "crypto-bigint", "dojo-types", @@ -5217,8 +5217,8 @@ dependencies = [ [[package]] name = "torii-proto" -version = "1.7.4" -source = "git+https://github.com/dojoengine/torii?rev=23d0da6#23d0da64bbb635d6a6d16b68b9d4a1755f9be310" +version = "1.8.2" +source = "git+https://github.com/dojoengine/torii?rev=8378c63#8378c636a6904f17ce04a011d2aee1bc3d84751a" dependencies = [ "chrono", "crypto-bigint", diff --git a/Cargo.toml b/Cargo.toml index 794e429..beb7ed9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,17 @@ [package] edition = "2021" name = "dojo-c" -version = "1.7.5" +version = "1.8.3" [lib] crate-type = ["cdylib", "rlib", "staticlib"] [dependencies] -dojo-world = { git = "https://github.com/dojoengine/dojo", rev = "6daa3d0" } -dojo-types = { git = "https://github.com/dojoengine/dojo", rev = "6daa3d0" } -torii-proto = { git = "https://github.com/dojoengine/torii", rev = "23d0da6" } -torii-client = { git = "https://github.com/dojoengine/torii", rev = "23d0da6" } -torii-grpc-client = { git = "https://github.com/dojoengine/torii", rev = "23d0da6" } +dojo-world = { git = "https://github.com/dojoengine/dojo", rev = "0afeb1bc" } +dojo-types = { git = "https://github.com/dojoengine/dojo", rev = "0afeb1bc" } +torii-proto = { git = "https://github.com/dojoengine/torii", rev = "8378c63" } +torii-client = { git = "https://github.com/dojoengine/torii", rev = "8378c63" } +torii-grpc-client = { git = "https://github.com/dojoengine/torii", rev = "8378c63" } starknet = "0.17.0" starknet-crypto = "0.8" @@ -28,7 +28,7 @@ futures = "0.3.30" futures-channel = "0.3.30" wasm-bindgen = "0.2.92" stream-cancel = "0.8.2" -cainome = "0.10.0" +cainome = "0.10.1" lazy_static = "1.5.0" account_sdk = { git = "https://github.com/cartridge-gg/controller-rs", rev = "ab1282b00afdf0a4b330793f1362aee066b1a0c9" } diff --git a/dojo.h b/dojo.h index 06869e7..cac8343 100644 --- a/dojo.h +++ b/dojo.h @@ -15,12 +15,14 @@ struct Controller; struct OrderBy; struct Entity; struct COptionFieldElement; -struct Model; +struct World; struct Transaction; struct Subscription; struct TransactionCall; struct Struct; struct AggregationEntry; +struct Achievement; +struct PlayerAchievementEntry; struct Activity; struct ActionCount; struct Token; @@ -32,8 +34,12 @@ struct TokenTransfer; struct Provider; struct Account; struct Ty; +struct Model; struct Member; +struct AchievementTask; +struct PlayerAchievementProgress; struct EnumOption; +struct TaskProgress; typedef enum BlockTag { Latest, @@ -112,10 +118,6 @@ typedef struct ResultToriiClient { }; } ResultToriiClient; -typedef struct FieldElement { - uint8_t data[32]; -} FieldElement; - typedef enum ResultControllerAccount_Tag { OkControllerAccount, ErrControllerAccount, @@ -133,6 +135,10 @@ typedef struct ResultControllerAccount { }; } ResultControllerAccount; +typedef struct FieldElement { + uint8_t data[32]; +} FieldElement; + typedef enum Resultbool_Tag { Okbool, Errbool, @@ -167,6 +173,23 @@ typedef struct ResultFieldElement { }; } ResultFieldElement; +typedef enum Resultc_char_Tag { + Okc_char, + Errc_char, +} Resultc_char_Tag; + +typedef struct Resultc_char { + Resultc_char_Tag tag; + union { + struct { + const char *ok; + }; + struct { + struct Error err; + }; + }; +} Resultc_char; + typedef struct CArrayFieldElement { struct FieldElement *data; uintptr_t data_len; @@ -175,24 +198,30 @@ typedef struct CArrayFieldElement { typedef struct Message { const char *message; struct CArrayFieldElement signature; + struct FieldElement world_address; } Message; -typedef enum ResultCArrayFieldElement_Tag { - OkCArrayFieldElement, - ErrCArrayFieldElement, -} ResultCArrayFieldElement_Tag; +typedef struct CArrayc_char { + const char **data; + uintptr_t data_len; +} CArrayc_char; -typedef struct ResultCArrayFieldElement { - ResultCArrayFieldElement_Tag tag; +typedef enum ResultCArrayc_char_Tag { + OkCArrayc_char, + ErrCArrayc_char, +} ResultCArrayc_char_Tag; + +typedef struct ResultCArrayc_char { + ResultCArrayc_char_Tag tag; union { struct { - struct CArrayFieldElement ok; + struct CArrayc_char ok; }; struct { struct Error err; }; }; -} ResultCArrayFieldElement; +} ResultCArrayc_char; typedef struct CArrayController { struct Controller *data; @@ -261,11 +290,6 @@ typedef struct Pagination { struct CArrayOrderBy order_by; } Pagination; -typedef struct CArrayc_char { - const char **data; - uintptr_t data_len; -} CArrayc_char; - typedef struct ControllerQuery { struct Pagination pagination; struct CArrayFieldElement contract_addresses; @@ -470,6 +494,7 @@ typedef struct COptionClause { } COptionClause; typedef struct Query { + struct CArrayFieldElement world_addresses; struct Pagination pagination; struct COptionClause clause; bool no_hashed_keys; @@ -477,32 +502,27 @@ typedef struct Query { bool historical; } Query; -typedef struct CArrayModel { - struct Model *data; +typedef struct CArrayWorld { + struct World *data; uintptr_t data_len; -} CArrayModel; - -typedef struct World { - struct FieldElement world_address; - struct CArrayModel models; -} World; +} CArrayWorld; -typedef enum ResultWorld_Tag { - OkWorld, - ErrWorld, -} ResultWorld_Tag; +typedef enum ResultCArrayWorld_Tag { + OkCArrayWorld, + ErrCArrayWorld, +} ResultCArrayWorld_Tag; -typedef struct ResultWorld { - ResultWorld_Tag tag; +typedef struct ResultCArrayWorld { + ResultCArrayWorld_Tag tag; union { struct { - struct World ok; + struct CArrayWorld ok; }; struct { struct Error err; }; }; -} ResultWorld; +} ResultCArrayWorld; typedef struct CArrayTransaction { struct Transaction *data; @@ -616,6 +636,7 @@ typedef struct CArrayStruct { } CArrayStruct; typedef struct Entity { + struct FieldElement world_address; struct FieldElement hashed_keys; struct CArrayStruct models; uint64_t created_at; @@ -663,11 +684,107 @@ typedef struct AggregationEntry { struct U256 value; const char *display_value; uint64_t position; - struct FieldElement model_id; + const char *model_id; uint64_t created_at; uint64_t updated_at; } AggregationEntry; +typedef struct CArrayAchievement { + struct Achievement *data; + uintptr_t data_len; +} CArrayAchievement; + +typedef struct PageAchievement { + struct CArrayAchievement items; + struct COptionc_char next_cursor; +} PageAchievement; + +typedef enum ResultPageAchievement_Tag { + OkPageAchievement, + ErrPageAchievement, +} ResultPageAchievement_Tag; + +typedef struct ResultPageAchievement { + ResultPageAchievement_Tag tag; + union { + struct { + struct PageAchievement ok; + }; + struct { + struct Error err; + }; + }; +} ResultPageAchievement; + +typedef enum COptionbool_Tag { + Somebool, + Nonebool, +} COptionbool_Tag; + +typedef struct COptionbool { + COptionbool_Tag tag; + union { + struct { + bool some; + }; + }; +} COptionbool; + +typedef struct AchievementQuery { + struct CArrayFieldElement world_addresses; + struct CArrayc_char namespaces; + struct COptionbool hidden; + struct Pagination pagination; +} AchievementQuery; + +typedef struct CArrayPlayerAchievementEntry { + struct PlayerAchievementEntry *data; + uintptr_t data_len; +} CArrayPlayerAchievementEntry; + +typedef struct PagePlayerAchievementEntry { + struct CArrayPlayerAchievementEntry items; + struct COptionc_char next_cursor; +} PagePlayerAchievementEntry; + +typedef enum ResultPagePlayerAchievementEntry_Tag { + OkPagePlayerAchievementEntry, + ErrPagePlayerAchievementEntry, +} ResultPagePlayerAchievementEntry_Tag; + +typedef struct ResultPagePlayerAchievementEntry { + ResultPagePlayerAchievementEntry_Tag tag; + union { + struct { + struct PagePlayerAchievementEntry ok; + }; + struct { + struct Error err; + }; + }; +} ResultPagePlayerAchievementEntry; + +typedef struct PlayerAchievementQuery { + struct CArrayFieldElement world_addresses; + struct CArrayc_char namespaces; + struct CArrayFieldElement player_addresses; + struct Pagination pagination; +} PlayerAchievementQuery; + +typedef struct AchievementProgression { + const char *id; + const char *achievement_id; + const char *task_id; + struct FieldElement world_address; + const char *namespace_; + struct FieldElement player_id; + uint32_t count; + bool completed; + struct COptionu64 completed_at; + uint64_t created_at; + uint64_t updated_at; +} AchievementProgression; + typedef struct CArrayActivity { struct Activity *data; uintptr_t data_len; @@ -971,22 +1088,22 @@ typedef struct TokenTransfer { struct COptionc_char event_id; } TokenTransfer; -typedef enum Resultc_char_Tag { - Okc_char, - Errc_char, -} Resultc_char_Tag; +typedef enum ResultCArrayFieldElement_Tag { + OkCArrayFieldElement, + ErrCArrayFieldElement, +} ResultCArrayFieldElement_Tag; -typedef struct Resultc_char { - Resultc_char_Tag tag; +typedef struct ResultCArrayFieldElement { + ResultCArrayFieldElement_Tag tag; union { struct { - const char *ok; + struct CArrayFieldElement ok; }; struct { struct Error err; }; }; -} Resultc_char; +} ResultCArrayFieldElement; typedef struct Signature { /** @@ -1097,6 +1214,24 @@ typedef struct OrderBy { enum OrderDirection direction; } OrderBy; +typedef struct CArrayModel { + struct Model *data; + uintptr_t data_len; +} CArrayModel; + +typedef struct World { + struct FieldElement world_address; + struct CArrayModel models; +} World; + +typedef struct TransactionCall { + struct FieldElement contract_address; + const char *entrypoint; + struct CArrayFieldElement calldata; + enum CallType call_type; + struct FieldElement caller_address; +} TransactionCall; + typedef struct CArrayMember { struct Member *data; uintptr_t data_len; @@ -1107,6 +1242,74 @@ typedef struct Struct { struct CArrayMember children; } Struct; +typedef struct CArrayAchievementTask { + struct AchievementTask *data; + uintptr_t data_len; +} CArrayAchievementTask; + +typedef struct Achievement { + const char *id; + struct FieldElement world_address; + const char *namespace_; + const char *entity_id; + bool hidden; + uint32_t index; + uint32_t points; + const char *start; + const char *end; + const char *group; + const char *icon; + const char *title; + const char *description; + struct CArrayAchievementTask tasks; + const char *data; + uint32_t total_completions; + double completion_rate; + uint64_t created_at; + uint64_t updated_at; +} Achievement; + +typedef struct PlayerAchievementStats { + uint32_t total_points; + uint32_t completed_achievements; + uint32_t total_achievements; + double completion_percentage; + struct COptionu64 last_achievement_at; + uint64_t created_at; + uint64_t updated_at; +} PlayerAchievementStats; + +typedef struct CArrayPlayerAchievementProgress { + struct PlayerAchievementProgress *data; + uintptr_t data_len; +} CArrayPlayerAchievementProgress; + +typedef struct PlayerAchievementEntry { + struct FieldElement player_address; + struct PlayerAchievementStats stats; + struct CArrayPlayerAchievementProgress achievements; +} PlayerAchievementEntry; + +typedef struct ActionCount { + const char *action_name; + uint32_t count; +} ActionCount; + +typedef struct AttributeFilter { + const char *trait_name; + const char *trait_value; +} AttributeFilter; + +typedef struct TokenContract { + struct FieldElement contract_address; + const char *name; + const char *symbol; + uint8_t decimals; + const char *metadata; + const char *token_metadata; + struct COptionU256 total_supply; +} TokenContract; + typedef struct CArrayEnumOption { struct EnumOption *data; uintptr_t data_len; @@ -1166,6 +1369,7 @@ typedef struct Ty { } Ty; typedef struct Model { + struct FieldElement world_address; struct Ty schema; const char *namespace_; const char *name; @@ -1178,45 +1382,44 @@ typedef struct Model { bool use_legacy_store; } Model; -typedef struct TransactionCall { - struct FieldElement contract_address; - const char *entrypoint; - struct CArrayFieldElement calldata; - enum CallType call_type; - struct FieldElement caller_address; -} TransactionCall; - -typedef struct ActionCount { - const char *action_name; - uint32_t count; -} ActionCount; - -typedef struct AttributeFilter { - const char *trait_name; - const char *trait_value; -} AttributeFilter; - -typedef struct TokenContract { - struct FieldElement contract_address; - const char *name; - const char *symbol; - uint8_t decimals; - const char *metadata; - const char *token_metadata; - struct COptionU256 total_supply; -} TokenContract; - typedef struct Member { const char *name; struct Ty *ty; bool key; } Member; +typedef struct AchievementTask { + const char *task_id; + const char *description; + uint32_t total; + uint32_t total_completions; + double completion_rate; + uint64_t created_at; +} AchievementTask; + +typedef struct CArrayTaskProgress { + struct TaskProgress *data; + uintptr_t data_len; +} CArrayTaskProgress; + +typedef struct PlayerAchievementProgress { + struct Achievement achievement; + struct CArrayTaskProgress task_progress; + bool completed; + double progress_percentage; +} PlayerAchievementProgress; + typedef struct EnumOption { const char *name; struct Ty *ty; } EnumOption; +typedef struct TaskProgress { + const char *task_id; + uint32_t count; + bool completed; +} TaskProgress; + #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -1227,12 +1430,11 @@ extern "C" { * # Parameters * * `torii_url` - URL of the Torii server * * `libp2p_relay_url` - URL of the libp2p relay server - * * `world` - World address as a FieldElement * * # Returns * Result containing pointer to new ToriiClient instance or error */ -struct ResultToriiClient client_new(const char *torii_url, struct FieldElement world); +struct ResultToriiClient client_new(const char *torii_url); /** * Initiates a connection to establish a new session account @@ -1401,8 +1603,7 @@ void client_set_logger(struct ToriiClient *client, void (*logger)(const char*)); * # Returns * Result containing byte array or error */ -struct ResultFieldElement client_publish_message(struct ToriiClient *client, - struct Message message); +struct Resultc_char client_publish_message(struct ToriiClient *client, struct Message message); /** * Publishes multiple messages to the network @@ -1415,9 +1616,9 @@ struct ResultFieldElement client_publish_message(struct ToriiClient *client, * # Returns * Result containing array of message IDs or error */ -struct ResultCArrayFieldElement client_publish_message_batch(struct ToriiClient *client, - const struct Message *messages, - uintptr_t messages_len); +struct ResultCArrayc_char client_publish_message_batch(struct ToriiClient *client, + const struct Message *messages, + uintptr_t messages_len); /** * Retrieves controllers for the given contract addresses @@ -1467,7 +1668,9 @@ struct ResultPageEntity client_event_messages(struct ToriiClient *client, struct * # Returns * World structure containing world information */ -struct ResultWorld client_metadata(struct ToriiClient *client); +struct ResultCArrayWorld client_worlds(struct ToriiClient *client, + const struct FieldElement *world_addresses, + uintptr_t world_addresses_len); /** * Retrieves transactions matching the given query @@ -1511,6 +1714,8 @@ struct ResultSubscription client_on_transaction(struct ToriiClient *client, */ struct ResultSubscription client_on_entity_state_update(struct ToriiClient *client, struct COptionClause clause, + const struct FieldElement *world_addresses, + uintptr_t world_addresses_len, void (*callback)(struct Entity)); /** @@ -1527,7 +1732,9 @@ struct ResultSubscription client_on_entity_state_update(struct ToriiClient *clie */ struct Resultbool client_update_entity_subscription(struct ToriiClient *client, struct Subscription *subscription, - struct COptionClause clause); + struct COptionClause clause, + const struct FieldElement *world_addresses, + uintptr_t world_addresses_len); /** * Retrieves aggregations (leaderboards, stats, rankings) matching query parameter @@ -1584,18 +1791,102 @@ struct Resultbool client_update_aggregation_subscription(struct ToriiClient *cli const char *const *entity_ids, uintptr_t entity_ids_len); +/** + * Retrieves achievements matching query parameter + * + * # Parameters + * * `client` - Pointer to ToriiClient instance + * * `query` - AchievementQuery containing world_addresses, namespaces, hidden filter, and pagination + * + * # Returns + * Result containing Page of Achievement or error + */ +struct ResultPageAchievement client_achievements(struct ToriiClient *client, + struct AchievementQuery query); + +/** + * Retrieves player achievement data matching query parameter + * + * # Parameters + * * `client` - Pointer to ToriiClient instance + * * `query` - PlayerAchievementQuery containing world_addresses, namespaces, player_addresses, and pagination + * + * # Returns + * Result containing Page of PlayerAchievementEntry or error + */ +struct ResultPagePlayerAchievementEntry client_player_achievements(struct ToriiClient *client, + struct PlayerAchievementQuery query); + +/** + * Subscribes to achievement progression updates + * + * # Parameters + * * `client` - Pointer to ToriiClient instance + * * `world_addresses` - Array of world addresses to subscribe to + * * `world_addresses_len` - Length of world_addresses array + * * `namespaces` - Array of namespaces to subscribe to + * * `namespaces_len` - Length of namespaces array + * * `player_addresses` - Array of player addresses to subscribe to + * * `player_addresses_len` - Length of player_addresses array + * * `achievement_ids` - Array of achievement IDs to subscribe to + * * `achievement_ids_len` - Length of achievement_ids array + * * `callback` - Function called when updates occur + * + * # Returns + * Result containing pointer to Subscription or error + */ +struct ResultSubscription client_on_achievement_progression_update(struct ToriiClient *client, + const struct FieldElement *world_addresses, + uintptr_t world_addresses_len, + const char *const *namespaces, + uintptr_t namespaces_len, + const struct FieldElement *player_addresses, + uintptr_t player_addresses_len, + const char *const *achievement_ids, + uintptr_t achievement_ids_len, + void (*callback)(struct AchievementProgression)); + +/** + * Updates an existing achievement progression subscription with new parameters + * + * # Parameters + * * `client` - Pointer to ToriiClient instance + * * `subscription` - Pointer to existing Subscription + * * `world_addresses` - Array of world addresses to subscribe to + * * `world_addresses_len` - Length of world_addresses array + * * `namespaces` - Array of namespaces to subscribe to + * * `namespaces_len` - Length of namespaces array + * * `player_addresses` - Array of player addresses to subscribe to + * * `player_addresses_len` - Length of player_addresses array + * * `achievement_ids` - Array of achievement IDs to subscribe to + * * `achievement_ids_len` - Length of achievement_ids array + * + * # Returns + * Result containing success boolean or error + */ +struct Resultbool client_update_achievement_progression_subscription(struct ToriiClient *client, + struct Subscription *subscription, + const struct FieldElement *world_addresses, + uintptr_t world_addresses_len, + const char *const *namespaces, + uintptr_t namespaces_len, + const struct FieldElement *player_addresses, + uintptr_t player_addresses_len, + const char *const *achievement_ids, + uintptr_t achievement_ids_len); + /** * Retrieves activities (user session tracking) matching query parameter * * # Parameters * * `client` - Pointer to ToriiClient instance - * * `query` - ActivityQuery containing world_addresses, namespaces, caller_addresses, and pagination + * * `query` - ActivityQuery containing world_addresses, namespaces, caller_addresses, and + * pagination * * # Returns * Result containing Page of Activity or error */ -struct ResultPageActivity client_activities(struct ToriiClient *client, - struct ActivityQuery query); +struct ResultPageActivity client_activities(struct ToriiClient *client, struct ActivityQuery query); /** * Subscribes to activity updates (user session tracking) @@ -1661,6 +1952,8 @@ struct Resultbool client_update_activity_subscription(struct ToriiClient *client */ struct ResultSubscription client_on_event_message_update(struct ToriiClient *client, struct COptionClause clause, + const struct FieldElement *world_addresses, + uintptr_t world_addresses_len, void (*callback)(struct Entity)); /** @@ -1677,7 +1970,9 @@ struct ResultSubscription client_on_event_message_update(struct ToriiClient *cli */ struct Resultbool client_update_event_message_subscription(struct ToriiClient *client, struct Subscription *subscription, - struct COptionClause clause); + struct COptionClause clause, + const struct FieldElement *world_addresses, + uintptr_t world_addresses_len); /** * Subscribes to Starknet events diff --git a/dojo.hpp b/dojo.hpp index 0139393..9130546 100644 --- a/dojo.hpp +++ b/dojo.hpp @@ -132,6 +132,7 @@ struct CArray { struct Message { const char *message; CArray signature; + FieldElement world_address; }; struct Controller { @@ -214,6 +215,7 @@ struct Struct { }; struct Entity { + FieldElement world_address; FieldElement hashed_keys; CArray models; uint64_t created_at; @@ -666,6 +668,7 @@ struct Clause { }; struct Query { + CArray world_addresses; Pagination pagination; COption clause; bool no_hashed_keys; @@ -818,6 +821,7 @@ struct Ty { }; struct Model { + FieldElement world_address; Ty schema; const char *namespace_; const char *name; @@ -879,7 +883,7 @@ struct AggregationEntry { U256 value; const char *display_value; uint64_t position; - FieldElement model_id; + const char *model_id; uint64_t created_at; uint64_t updated_at; }; @@ -890,6 +894,94 @@ struct AggregationQuery { Pagination pagination; }; +struct AchievementTask { + const char *task_id; + const char *description; + uint32_t total; + uint32_t total_completions; + double completion_rate; + uint64_t created_at; +}; + +struct Achievement { + const char *id; + FieldElement world_address; + const char *namespace_; + const char *entity_id; + bool hidden; + uint32_t index; + uint32_t points; + const char *start; + const char *end; + const char *group; + const char *icon; + const char *title; + const char *description; + CArray tasks; + const char *data; + uint32_t total_completions; + double completion_rate; + uint64_t created_at; + uint64_t updated_at; +}; + +struct AchievementQuery { + CArray world_addresses; + CArray namespaces; + COption hidden; + Pagination pagination; +}; + +struct PlayerAchievementStats { + uint32_t total_points; + uint32_t completed_achievements; + uint32_t total_achievements; + double completion_percentage; + COption last_achievement_at; + uint64_t created_at; + uint64_t updated_at; +}; + +struct TaskProgress { + const char *task_id; + uint32_t count; + bool completed; +}; + +struct PlayerAchievementProgress { + Achievement achievement; + CArray task_progress; + bool completed; + double progress_percentage; +}; + +struct PlayerAchievementEntry { + FieldElement player_address; + PlayerAchievementStats stats; + CArray achievements; +}; + +struct PlayerAchievementQuery { + CArray world_addresses; + CArray namespaces; + CArray player_addresses; + Pagination pagination; +}; + +struct AchievementProgression { + const char *id; + const char *achievement_id; + const char *task_id; + FieldElement world_address; + const char *namespace_; + FieldElement player_id; + uint32_t count; + bool completed; + COption completed_at; + uint64_t created_at; + uint64_t updated_at; +}; + struct ActionCount { const char *action_name; uint32_t count; @@ -1095,11 +1187,10 @@ extern "C" { /// # Parameters /// * `torii_url` - URL of the Torii server /// * `libp2p_relay_url` - URL of the libp2p relay server -/// * `world` - World address as a FieldElement /// /// # Returns /// Result containing pointer to new ToriiClient instance or error -Result client_new(const char *torii_url, FieldElement world); +Result client_new(const char *torii_url); /// Initiates a connection to establish a new session account /// @@ -1246,7 +1337,7 @@ void client_set_logger(ToriiClient *client, void (*logger)(const char*)); /// /// # Returns /// Result containing byte array or error -Result client_publish_message(ToriiClient *client, Message message); +Result client_publish_message(ToriiClient *client, Message message); /// Publishes multiple messages to the network /// @@ -1257,9 +1348,9 @@ Result client_publish_message(ToriiClient *client, Message message /// /// # Returns /// Result containing array of message IDs or error -Result> client_publish_message_batch(ToriiClient *client, - const Message *messages, - uintptr_t messages_len); +Result> client_publish_message_batch(ToriiClient *client, + const Message *messages, + uintptr_t messages_len); /// Retrieves controllers for the given contract addresses /// @@ -1300,7 +1391,9 @@ Result> client_event_messages(ToriiClient *client, Query query); /// /// # Returns /// World structure containing world information -Result client_metadata(ToriiClient *client); +Result> client_worlds(ToriiClient *client, + const FieldElement *world_addresses, + uintptr_t world_addresses_len); /// Retrieves transactions matching the given query /// @@ -1337,6 +1430,8 @@ Result client_on_transaction(ToriiClient *client, /// Result containing pointer to Subscription or error Result client_on_entity_state_update(ToriiClient *client, COption clause, + const FieldElement *world_addresses, + uintptr_t world_addresses_len, void (*callback)(Entity)); /// Updates an existing entity subscription with new clauses @@ -1351,7 +1446,9 @@ Result client_on_entity_state_update(ToriiClient *client, /// Result containing success boolean or error Result client_update_entity_subscription(ToriiClient *client, Subscription *subscription, - COption clause); + COption clause, + const FieldElement *world_addresses, + uintptr_t world_addresses_len); /// Retrieves aggregations (leaderboards, stats, rankings) matching query parameter /// @@ -1401,16 +1498,92 @@ Result client_update_aggregation_subscription(ToriiClient *client, const char *const *entity_ids, uintptr_t entity_ids_len); +/// Retrieves achievements matching query parameter +/// +/// # Parameters +/// * `client` - Pointer to ToriiClient instance +/// * `query` - AchievementQuery containing world_addresses, namespaces, hidden filter, and pagination +/// +/// # Returns +/// Result containing Page of Achievement or error +Result> client_achievements(ToriiClient *client, + AchievementQuery query); + +/// Retrieves player achievement data matching query parameter +/// +/// # Parameters +/// * `client` - Pointer to ToriiClient instance +/// * `query` - PlayerAchievementQuery containing world_addresses, namespaces, player_addresses, and pagination +/// +/// # Returns +/// Result containing Page of PlayerAchievementEntry or error +Result> client_player_achievements(ToriiClient *client, + PlayerAchievementQuery query); + +/// Subscribes to achievement progression updates +/// +/// # Parameters +/// * `client` - Pointer to ToriiClient instance +/// * `world_addresses` - Array of world addresses to subscribe to +/// * `world_addresses_len` - Length of world_addresses array +/// * `namespaces` - Array of namespaces to subscribe to +/// * `namespaces_len` - Length of namespaces array +/// * `player_addresses` - Array of player addresses to subscribe to +/// * `player_addresses_len` - Length of player_addresses array +/// * `achievement_ids` - Array of achievement IDs to subscribe to +/// * `achievement_ids_len` - Length of achievement_ids array +/// * `callback` - Function called when updates occur +/// +/// # Returns +/// Result containing pointer to Subscription or error +Result client_on_achievement_progression_update(ToriiClient *client, + const FieldElement *world_addresses, + uintptr_t world_addresses_len, + const char *const *namespaces, + uintptr_t namespaces_len, + const FieldElement *player_addresses, + uintptr_t player_addresses_len, + const char *const *achievement_ids, + uintptr_t achievement_ids_len, + void (*callback)(AchievementProgression)); + +/// Updates an existing achievement progression subscription with new parameters +/// +/// # Parameters +/// * `client` - Pointer to ToriiClient instance +/// * `subscription` - Pointer to existing Subscription +/// * `world_addresses` - Array of world addresses to subscribe to +/// * `world_addresses_len` - Length of world_addresses array +/// * `namespaces` - Array of namespaces to subscribe to +/// * `namespaces_len` - Length of namespaces array +/// * `player_addresses` - Array of player addresses to subscribe to +/// * `player_addresses_len` - Length of player_addresses array +/// * `achievement_ids` - Array of achievement IDs to subscribe to +/// * `achievement_ids_len` - Length of achievement_ids array +/// +/// # Returns +/// Result containing success boolean or error +Result client_update_achievement_progression_subscription(ToriiClient *client, + Subscription *subscription, + const FieldElement *world_addresses, + uintptr_t world_addresses_len, + const char *const *namespaces, + uintptr_t namespaces_len, + const FieldElement *player_addresses, + uintptr_t player_addresses_len, + const char *const *achievement_ids, + uintptr_t achievement_ids_len); + /// Retrieves activities (user session tracking) matching query parameter /// /// # Parameters /// * `client` - Pointer to ToriiClient instance -/// * `query` - ActivityQuery containing world_addresses, namespaces, caller_addresses, and pagination +/// * `query` - ActivityQuery containing world_addresses, namespaces, caller_addresses, and +/// pagination /// /// # Returns /// Result containing Page of Activity or error -Result> client_activities(ToriiClient *client, - ActivityQuery query); +Result> client_activities(ToriiClient *client, ActivityQuery query); /// Subscribes to activity updates (user session tracking) /// @@ -1470,6 +1643,8 @@ Result client_update_activity_subscription(ToriiClient *client, /// Result containing pointer to Subscription or error Result client_on_event_message_update(ToriiClient *client, COption clause, + const FieldElement *world_addresses, + uintptr_t world_addresses_len, void (*callback)(Entity)); /// Updates an existing event message subscription @@ -1484,7 +1659,9 @@ Result client_on_event_message_update(ToriiClient *client, /// Result containing success boolean or error Result client_update_event_message_subscription(ToriiClient *client, Subscription *subscription, - COption clause); + COption clause, + const FieldElement *world_addresses, + uintptr_t world_addresses_len); /// Subscribes to Starknet events /// diff --git a/dojo.pyx b/dojo.pyx index 447f332..b6ba67a 100644 --- a/dojo.pyx +++ b/dojo.pyx @@ -81,9 +81,6 @@ cdef extern from *: ToriiClient *ok; Error err; - cdef struct FieldElement: - uint8_t data[32]; - cdef enum ResultControllerAccount_Tag: OkControllerAccount, ErrControllerAccount, @@ -93,6 +90,9 @@ cdef extern from *: ControllerAccount *ok; Error err; + cdef struct FieldElement: + uint8_t data[32]; + cdef enum Resultbool_Tag: Okbool, Errbool, @@ -111,6 +111,15 @@ cdef extern from *: FieldElement ok; Error err; + cdef enum Resultc_char_Tag: + Okc_char, + Errc_char, + + cdef struct Resultc_char: + Resultc_char_Tag tag; + const char *ok; + Error err; + cdef struct CArrayFieldElement: FieldElement *data; uintptr_t data_len; @@ -118,14 +127,19 @@ cdef extern from *: cdef struct Message: const char *message; CArrayFieldElement signature; + FieldElement world_address; - cdef enum ResultCArrayFieldElement_Tag: - OkCArrayFieldElement, - ErrCArrayFieldElement, + cdef struct CArrayc_char: + const char **data; + uintptr_t data_len; - cdef struct ResultCArrayFieldElement: - ResultCArrayFieldElement_Tag tag; - CArrayFieldElement ok; + cdef enum ResultCArrayc_char_Tag: + OkCArrayc_char, + ErrCArrayc_char, + + cdef struct ResultCArrayc_char: + ResultCArrayc_char_Tag tag; + CArrayc_char ok; Error err; cdef struct CArrayController: @@ -171,10 +185,6 @@ cdef extern from *: PaginationDirection direction; CArrayOrderBy order_by; - cdef struct CArrayc_char: - const char **data; - uintptr_t data_len; - cdef struct ControllerQuery: Pagination pagination; CArrayFieldElement contract_addresses; @@ -297,27 +307,24 @@ cdef extern from *: Clause some; cdef struct Query: + CArrayFieldElement world_addresses; Pagination pagination; COptionClause clause; bool no_hashed_keys; CArrayc_char models; bool historical; - cdef struct CArrayModel: - Model *data; + cdef struct CArrayWorld: + World *data; uintptr_t data_len; - cdef struct World: - FieldElement world_address; - CArrayModel models; - - cdef enum ResultWorld_Tag: - OkWorld, - ErrWorld, + cdef enum ResultCArrayWorld_Tag: + OkCArrayWorld, + ErrCArrayWorld, - cdef struct ResultWorld: - ResultWorld_Tag tag; - World ok; + cdef struct ResultCArrayWorld: + ResultCArrayWorld_Tag tag; + CArrayWorld ok; Error err; cdef struct CArrayTransaction: @@ -397,6 +404,7 @@ cdef extern from *: uintptr_t data_len; cdef struct Entity: + FieldElement world_address; FieldElement hashed_keys; CArrayStruct models; uint64_t created_at; @@ -432,7 +440,74 @@ cdef extern from *: U256 value; const char *display_value; uint64_t position; - FieldElement model_id; + const char *model_id; + uint64_t created_at; + uint64_t updated_at; + + cdef struct CArrayAchievement: + Achievement *data; + uintptr_t data_len; + + cdef struct PageAchievement: + CArrayAchievement items; + COptionc_char next_cursor; + + cdef enum ResultPageAchievement_Tag: + OkPageAchievement, + ErrPageAchievement, + + cdef struct ResultPageAchievement: + ResultPageAchievement_Tag tag; + PageAchievement ok; + Error err; + + cdef enum COptionbool_Tag: + Somebool, + Nonebool, + + cdef struct COptionbool: + COptionbool_Tag tag; + bool some; + + cdef struct AchievementQuery: + CArrayFieldElement world_addresses; + CArrayc_char namespaces; + COptionbool hidden; + Pagination pagination; + + cdef struct CArrayPlayerAchievementEntry: + PlayerAchievementEntry *data; + uintptr_t data_len; + + cdef struct PagePlayerAchievementEntry: + CArrayPlayerAchievementEntry items; + COptionc_char next_cursor; + + cdef enum ResultPagePlayerAchievementEntry_Tag: + OkPagePlayerAchievementEntry, + ErrPagePlayerAchievementEntry, + + cdef struct ResultPagePlayerAchievementEntry: + ResultPagePlayerAchievementEntry_Tag tag; + PagePlayerAchievementEntry ok; + Error err; + + cdef struct PlayerAchievementQuery: + CArrayFieldElement world_addresses; + CArrayc_char namespaces; + CArrayFieldElement player_addresses; + Pagination pagination; + + cdef struct AchievementProgression: + const char *id; + const char *achievement_id; + const char *task_id; + FieldElement world_address; + const char *namespace_; + FieldElement player_id; + uint32_t count; + bool completed; + COptionu64 completed_at; uint64_t created_at; uint64_t updated_at; @@ -652,13 +727,13 @@ cdef extern from *: uint64_t executed_at; COptionc_char event_id; - cdef enum Resultc_char_Tag: - Okc_char, - Errc_char, + cdef enum ResultCArrayFieldElement_Tag: + OkCArrayFieldElement, + ErrCArrayFieldElement, - cdef struct Resultc_char: - Resultc_char_Tag tag; - const char *ok; + cdef struct ResultCArrayFieldElement: + ResultCArrayFieldElement_Tag tag; + CArrayFieldElement ok; Error err; cdef struct Signature: @@ -725,6 +800,21 @@ cdef extern from *: const char *field; OrderDirection direction; + cdef struct CArrayModel: + Model *data; + uintptr_t data_len; + + cdef struct World: + FieldElement world_address; + CArrayModel models; + + cdef struct TransactionCall: + FieldElement contract_address; + const char *entrypoint; + CArrayFieldElement calldata; + CallType call_type; + FieldElement caller_address; + cdef struct CArrayMember: Member *data; uintptr_t data_len; @@ -733,6 +823,66 @@ cdef extern from *: const char *name; CArrayMember children; + cdef struct CArrayAchievementTask: + AchievementTask *data; + uintptr_t data_len; + + cdef struct Achievement: + const char *id; + FieldElement world_address; + const char *namespace_; + const char *entity_id; + bool hidden; + uint32_t index; + uint32_t points; + const char *start; + const char *end; + const char *group; + const char *icon; + const char *title; + const char *description; + CArrayAchievementTask tasks; + const char *data; + uint32_t total_completions; + double completion_rate; + uint64_t created_at; + uint64_t updated_at; + + cdef struct PlayerAchievementStats: + uint32_t total_points; + uint32_t completed_achievements; + uint32_t total_achievements; + double completion_percentage; + COptionu64 last_achievement_at; + uint64_t created_at; + uint64_t updated_at; + + cdef struct CArrayPlayerAchievementProgress: + PlayerAchievementProgress *data; + uintptr_t data_len; + + cdef struct PlayerAchievementEntry: + FieldElement player_address; + PlayerAchievementStats stats; + CArrayPlayerAchievementProgress achievements; + + cdef struct ActionCount: + const char *action_name; + uint32_t count; + + cdef struct AttributeFilter: + const char *trait_name; + const char *trait_value; + + cdef struct TokenContract: + FieldElement contract_address; + const char *name; + const char *symbol; + uint8_t decimals; + const char *metadata; + const char *token_metadata; + COptionU256 total_supply; + cdef struct CArrayEnumOption: EnumOption *data; uintptr_t data_len; @@ -770,6 +920,7 @@ cdef extern from *: const char *byte_array; cdef struct Model: + FieldElement world_address; Ty schema; const char *namespace_; const char *name; @@ -781,49 +932,47 @@ cdef extern from *: const char *layout; bool use_legacy_store; - cdef struct TransactionCall: - FieldElement contract_address; - const char *entrypoint; - CArrayFieldElement calldata; - CallType call_type; - FieldElement caller_address; - - cdef struct ActionCount: - const char *action_name; - uint32_t count; - - cdef struct AttributeFilter: - const char *trait_name; - const char *trait_value; - - cdef struct TokenContract: - FieldElement contract_address; - const char *name; - const char *symbol; - uint8_t decimals; - const char *metadata; - const char *token_metadata; - COptionU256 total_supply; - cdef struct Member: const char *name; Ty *ty; bool key; + cdef struct AchievementTask: + const char *task_id; + const char *description; + uint32_t total; + uint32_t total_completions; + double completion_rate; + uint64_t created_at; + + cdef struct CArrayTaskProgress: + TaskProgress *data; + uintptr_t data_len; + + cdef struct PlayerAchievementProgress: + Achievement achievement; + CArrayTaskProgress task_progress; + bool completed; + double progress_percentage; + cdef struct EnumOption: const char *name; Ty *ty; + cdef struct TaskProgress: + const char *task_id; + uint32_t count; + bool completed; + # Creates a new Torii client instance # # # Parameters # * `torii_url` - URL of the Torii server # * `libp2p_relay_url` - URL of the libp2p relay server - # * `world` - World address as a FieldElement # # # Returns # Result containing pointer to new ToriiClient instance or error - ResultToriiClient client_new(const char *torii_url, FieldElement world); + ResultToriiClient client_new(const char *torii_url); # Initiates a connection to establish a new session account # @@ -970,7 +1119,7 @@ cdef extern from *: # # # Returns # Result containing byte array or error - ResultFieldElement client_publish_message(ToriiClient *client, Message message); + Resultc_char client_publish_message(ToriiClient *client, Message message); # Publishes multiple messages to the network # @@ -981,9 +1130,9 @@ cdef extern from *: # # # Returns # Result containing array of message IDs or error - ResultCArrayFieldElement client_publish_message_batch(ToriiClient *client, - const Message *messages, - uintptr_t messages_len); + ResultCArrayc_char client_publish_message_batch(ToriiClient *client, + const Message *messages, + uintptr_t messages_len); # Retrieves controllers for the given contract addresses # @@ -1024,7 +1173,9 @@ cdef extern from *: # # # Returns # World structure containing world information - ResultWorld client_metadata(ToriiClient *client); + ResultCArrayWorld client_worlds(ToriiClient *client, + const FieldElement *world_addresses, + uintptr_t world_addresses_len); # Retrieves transactions matching the given query # @@ -1061,6 +1212,8 @@ cdef extern from *: # Result containing pointer to Subscription or error ResultSubscription client_on_entity_state_update(ToriiClient *client, COptionClause clause, + const FieldElement *world_addresses, + uintptr_t world_addresses_len, void (*callback)(Entity)); # Updates an existing entity subscription with new clauses @@ -1075,7 +1228,9 @@ cdef extern from *: # Result containing success boolean or error Resultbool client_update_entity_subscription(ToriiClient *client, Subscription *subscription, - COptionClause clause); + COptionClause clause, + const FieldElement *world_addresses, + uintptr_t world_addresses_len); # Retrieves aggregations (leaderboards, stats, rankings) matching query parameter # @@ -1125,16 +1280,92 @@ cdef extern from *: const char *const *entity_ids, uintptr_t entity_ids_len); + # Retrieves achievements matching query parameter + # + # # Parameters + # * `client` - Pointer to ToriiClient instance + # * `query` - AchievementQuery containing world_addresses, namespaces, hidden filter, and pagination + # + # # Returns + # Result containing Page of Achievement or error + ResultPageAchievement client_achievements(ToriiClient *client, + AchievementQuery query); + + # Retrieves player achievement data matching query parameter + # + # # Parameters + # * `client` - Pointer to ToriiClient instance + # * `query` - PlayerAchievementQuery containing world_addresses, namespaces, player_addresses, and pagination + # + # # Returns + # Result containing Page of PlayerAchievementEntry or error + ResultPagePlayerAchievementEntry client_player_achievements(ToriiClient *client, + PlayerAchievementQuery query); + + # Subscribes to achievement progression updates + # + # # Parameters + # * `client` - Pointer to ToriiClient instance + # * `world_addresses` - Array of world addresses to subscribe to + # * `world_addresses_len` - Length of world_addresses array + # * `namespaces` - Array of namespaces to subscribe to + # * `namespaces_len` - Length of namespaces array + # * `player_addresses` - Array of player addresses to subscribe to + # * `player_addresses_len` - Length of player_addresses array + # * `achievement_ids` - Array of achievement IDs to subscribe to + # * `achievement_ids_len` - Length of achievement_ids array + # * `callback` - Function called when updates occur + # + # # Returns + # Result containing pointer to Subscription or error + ResultSubscription client_on_achievement_progression_update(ToriiClient *client, + const FieldElement *world_addresses, + uintptr_t world_addresses_len, + const char *const *namespaces, + uintptr_t namespaces_len, + const FieldElement *player_addresses, + uintptr_t player_addresses_len, + const char *const *achievement_ids, + uintptr_t achievement_ids_len, + void (*callback)(AchievementProgression)); + + # Updates an existing achievement progression subscription with new parameters + # + # # Parameters + # * `client` - Pointer to ToriiClient instance + # * `subscription` - Pointer to existing Subscription + # * `world_addresses` - Array of world addresses to subscribe to + # * `world_addresses_len` - Length of world_addresses array + # * `namespaces` - Array of namespaces to subscribe to + # * `namespaces_len` - Length of namespaces array + # * `player_addresses` - Array of player addresses to subscribe to + # * `player_addresses_len` - Length of player_addresses array + # * `achievement_ids` - Array of achievement IDs to subscribe to + # * `achievement_ids_len` - Length of achievement_ids array + # + # # Returns + # Result containing success boolean or error + Resultbool client_update_achievement_progression_subscription(ToriiClient *client, + Subscription *subscription, + const FieldElement *world_addresses, + uintptr_t world_addresses_len, + const char *const *namespaces, + uintptr_t namespaces_len, + const FieldElement *player_addresses, + uintptr_t player_addresses_len, + const char *const *achievement_ids, + uintptr_t achievement_ids_len); + # Retrieves activities (user session tracking) matching query parameter # # # Parameters # * `client` - Pointer to ToriiClient instance - # * `query` - ActivityQuery containing world_addresses, namespaces, caller_addresses, and pagination + # * `query` - ActivityQuery containing world_addresses, namespaces, caller_addresses, and + # pagination # # # Returns # Result containing Page of Activity or error - ResultPageActivity client_activities(ToriiClient *client, - ActivityQuery query); + ResultPageActivity client_activities(ToriiClient *client, ActivityQuery query); # Subscribes to activity updates (user session tracking) # @@ -1194,6 +1425,8 @@ cdef extern from *: # Result containing pointer to Subscription or error ResultSubscription client_on_event_message_update(ToriiClient *client, COptionClause clause, + const FieldElement *world_addresses, + uintptr_t world_addresses_len, void (*callback)(Entity)); # Updates an existing event message subscription @@ -1208,7 +1441,9 @@ cdef extern from *: # Result containing success boolean or error Resultbool client_update_event_message_subscription(ToriiClient *client, Subscription *subscription, - COptionClause clause); + COptionClause clause, + const FieldElement *world_addresses, + uintptr_t world_addresses_len); # Subscribes to Starknet events # diff --git a/src/c/mod.rs b/src/c/mod.rs index fc68ff1..7e1ac13 100644 --- a/src/c/mod.rs +++ b/src/c/mod.rs @@ -51,15 +51,17 @@ use torii_client::Client as TClient; use torii_proto::Message; use tower_http::cors::{AllowOrigin, CorsLayer}; use types::{ - Activity, AggregationEntry, BlockId, CArray, COption, Call, Clause, Contract, Controller, - Entity, Error, Event, KeysClause, Page, Policy, Query, Result, Signature, Struct, Token, - TokenBalance, TokenContract, TokenTransfer, TokenTransferQuery, ToriiClient, Ty, World, + Achievement, AchievementProgression, Activity, AggregationEntry, BlockId, CArray, COption, + Call, Clause, Contract, Controller, Entity, Error, Event, KeysClause, Page, + PlayerAchievementEntry, Policy, Query, Result, Signature, Struct, Token, TokenBalance, + TokenContract, TokenTransfer, TokenTransferQuery, ToriiClient, Ty, World, }; use url::Url; use crate::c::types::{ - ActivityQuery, AggregationQuery, ContractQuery, ControllerQuery, TokenBalanceQuery, - TokenContractQuery, TokenQuery, Transaction, TransactionFilter, TransactionQuery, + AchievementQuery, ActivityQuery, AggregationQuery, ContractQuery, ControllerQuery, + PlayerAchievementQuery, TokenBalanceQuery, TokenContractQuery, TokenQuery, Transaction, + TransactionFilter, TransactionQuery, }; use crate::constants; use crate::types::{ @@ -78,17 +80,15 @@ lazy_static! { /// # Parameters /// * `torii_url` - URL of the Torii server /// * `libp2p_relay_url` - URL of the libp2p relay server -/// * `world` - World address as a FieldElement /// /// # Returns /// Result containing pointer to new ToriiClient instance or error #[no_mangle] pub unsafe extern "C" fn client_new( torii_url: *const c_char, - world: types::FieldElement, ) -> Result<*mut ToriiClient> { let torii_url = unsafe { CStr::from_ptr(torii_url).to_string_lossy().into_owned() }; - let client_future = TClient::new(torii_url, world.into()); + let client_future = TClient::new(torii_url); let client = match RUNTIME.block_on(client_future) { Ok(client) => client, @@ -697,11 +697,11 @@ pub unsafe extern "C" fn client_set_logger( pub unsafe extern "C" fn client_publish_message( client: *mut ToriiClient, message: types::Message, -) -> Result { +) -> Result<*const c_char> { let client_future = unsafe { (*client).inner.publish_message(message.into()) }; match RUNTIME.block_on(client_future) { - Ok(data) => Result::Ok(data.into()), + Ok(data) => Result::Ok(CString::new(data).unwrap().into_raw() as *const c_char), Err(e) => Result::Err(e.into()), } } @@ -720,15 +720,15 @@ pub unsafe extern "C" fn client_publish_message_batch( client: *mut ToriiClient, messages: *const types::Message, messages_len: usize, -) -> Result> { +) -> Result> { let messages = unsafe { std::slice::from_raw_parts(messages, messages_len) }; let messages: Vec = messages.iter().cloned().map(|msg| msg.into()).collect(); let client_future = unsafe { (*client).inner.publish_message_batch(messages) }; match RUNTIME.block_on(client_future) { Ok(message_ids) => { - let ids: Vec = - message_ids.into_iter().map(|id| id.into()).collect(); + let ids: Vec<*const c_char> = + message_ids.into_iter().map(|id| CString::new(id).unwrap().into_raw() as *const c_char).collect(); Result::Ok(ids.into()) } Err(e) => Result::Err(e.into()), @@ -811,8 +811,10 @@ pub unsafe extern "C" fn client_event_messages( /// # Returns /// World structure containing world information #[no_mangle] -pub unsafe extern "C" fn client_metadata(client: *mut ToriiClient) -> Result { - let metadata_future = unsafe { (*client).inner.metadata() }; +pub unsafe extern "C" fn client_worlds(client: *mut ToriiClient, world_addresses: *const types::FieldElement, world_addresses_len: usize) -> Result> { + let world_addresses = unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; + let world_addresses = world_addresses.iter().map(|addr| addr.clone().into()).collect(); + let metadata_future = unsafe { (*client).inner.worlds(world_addresses) }; match RUNTIME.block_on(metadata_future) { Ok(metadata) => Result::Ok(metadata.into()), Err(e) => Result::Err(e.into()), @@ -925,6 +927,8 @@ pub unsafe extern "C" fn client_on_transaction( pub unsafe extern "C" fn client_on_entity_state_update( client: *mut ToriiClient, clause: COption, + world_addresses: *const types::FieldElement, + world_addresses_len: usize, callback: unsafe extern "C" fn(Entity), ) -> Result<*mut Subscription> { let client = Arc::new(unsafe { &*client }); @@ -934,7 +938,8 @@ pub unsafe extern "C" fn client_on_entity_state_update( let mut sub_id_tx = Some(sub_id_tx); let clause: Option = clause.map(|c| c.into()).into(); - + let world_addresses = unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; + let world_addresses = world_addresses.iter().map(|addr| addr.clone().into()).collect::>(); // Spawn a new thread to handle the stream and reconnections let client_clone = client.clone(); RUNTIME.spawn(async move { @@ -942,7 +947,7 @@ pub unsafe extern "C" fn client_on_entity_state_update( let max_backoff = Duration::from_secs(60); loop { - let rcv = client_clone.inner.on_entity_updated(clause.clone()).await; + let rcv = client_clone.inner.on_entity_updated(clause.clone(), world_addresses.clone()).await; if let Ok(rcv) = rcv { backoff = Duration::from_secs(1); // Reset backoff on successful connection @@ -997,10 +1002,13 @@ pub unsafe extern "C" fn client_update_entity_subscription( client: *mut ToriiClient, subscription: *mut Subscription, clause: COption, + world_addresses: *const types::FieldElement, + world_addresses_len: usize, ) -> Result { let clause: Option = clause.map(|c| c.into()).into(); - - match RUNTIME.block_on((*client).inner.update_entity_subscription((*subscription).id, clause)) { + let world_addresses = unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; + let world_addresses = world_addresses.iter().map(|addr| addr.clone().into()).collect(); + match RUNTIME.block_on((*client).inner.update_entity_subscription((*subscription).id, clause, world_addresses)) { Ok(_) => Result::Ok(true), Err(e) => Result::Err(e.into()), } @@ -1178,6 +1186,257 @@ pub unsafe extern "C" fn client_update_aggregation_subscription( } } +/// Retrieves achievements matching query parameter +/// +/// # Parameters +/// * `client` - Pointer to ToriiClient instance +/// * `query` - AchievementQuery containing world_addresses, namespaces, hidden filter, and pagination +/// +/// # Returns +/// Result containing Page of Achievement or error +#[no_mangle] +pub unsafe extern "C" fn client_achievements( + client: *mut ToriiClient, + query: AchievementQuery, +) -> Result> { + let query = query.into(); + let achievements_future = unsafe { (*client).inner.achievements(query) }; + + match RUNTIME.block_on(achievements_future) { + Ok(achievements) => Result::Ok(achievements.into()), + Err(e) => Result::Err(e.into()), + } +} + +/// Retrieves player achievement data matching query parameter +/// +/// # Parameters +/// * `client` - Pointer to ToriiClient instance +/// * `query` - PlayerAchievementQuery containing world_addresses, namespaces, player_addresses, and pagination +/// +/// # Returns +/// Result containing Page of PlayerAchievementEntry or error +#[no_mangle] +pub unsafe extern "C" fn client_player_achievements( + client: *mut ToriiClient, + query: PlayerAchievementQuery, +) -> Result> { + let query = query.into(); + let player_achievements_future = unsafe { (*client).inner.player_achievements(query) }; + + match RUNTIME.block_on(player_achievements_future) { + Ok(player_achievements) => Result::Ok(player_achievements.into()), + Err(e) => Result::Err(e.into()), + } +} + +/// Subscribes to achievement progression updates +/// +/// # Parameters +/// * `client` - Pointer to ToriiClient instance +/// * `world_addresses` - Array of world addresses to subscribe to +/// * `world_addresses_len` - Length of world_addresses array +/// * `namespaces` - Array of namespaces to subscribe to +/// * `namespaces_len` - Length of namespaces array +/// * `player_addresses` - Array of player addresses to subscribe to +/// * `player_addresses_len` - Length of player_addresses array +/// * `achievement_ids` - Array of achievement IDs to subscribe to +/// * `achievement_ids_len` - Length of achievement_ids array +/// * `callback` - Function called when updates occur +/// +/// # Returns +/// Result containing pointer to Subscription or error +#[no_mangle] +pub unsafe extern "C" fn client_on_achievement_progression_update( + client: *mut ToriiClient, + world_addresses: *const types::FieldElement, + world_addresses_len: usize, + namespaces: *const *const c_char, + namespaces_len: usize, + player_addresses: *const types::FieldElement, + player_addresses_len: usize, + achievement_ids: *const *const c_char, + achievement_ids_len: usize, + callback: unsafe extern "C" fn(AchievementProgression), +) -> Result<*mut Subscription> { + let client = Arc::new(unsafe { &*client }); + + // Convert world_addresses array to Vec if not empty + let world_addresses = if world_addresses.is_null() || world_addresses_len == 0 { + Vec::new() + } else { + let addrs = unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; + addrs.iter().map(|addr| addr.clone().into()).collect::>() + }; + + // Convert namespaces array to Vec if not empty + let namespaces = if namespaces.is_null() || namespaces_len == 0 { + Vec::new() + } else { + let ns = unsafe { std::slice::from_raw_parts(namespaces, namespaces_len) }; + ns.iter() + .map(|n| unsafe { CStr::from_ptr(*n).to_string_lossy().to_string() }) + .collect::>() + }; + + // Convert player_addresses array to Vec if not empty + let player_addresses = if player_addresses.is_null() || player_addresses_len == 0 { + Vec::new() + } else { + let addrs = unsafe { std::slice::from_raw_parts(player_addresses, player_addresses_len) }; + addrs.iter().map(|addr| addr.clone().into()).collect::>() + }; + + // Convert achievement_ids array to Vec if not empty + let achievement_ids = if achievement_ids.is_null() || achievement_ids_len == 0 { + Vec::new() + } else { + let ids = unsafe { std::slice::from_raw_parts(achievement_ids, achievement_ids_len) }; + ids.iter() + .map(|id| unsafe { CStr::from_ptr(*id).to_string_lossy().to_string() }) + .collect::>() + }; + + let (sub_id_tx, sub_id_rx) = oneshot::channel(); + let mut sub_id_tx = Some(sub_id_tx); + let (trigger, tripwire) = Tripwire::new(); + + // Spawn a new thread to handle the stream and reconnections + let client_clone = client.clone(); + RUNTIME.spawn(async move { + let mut backoff = Duration::from_secs(1); + let max_backoff = Duration::from_secs(60); + + loop { + let rcv = client_clone + .inner + .on_achievement_progression_updated( + world_addresses.clone(), + namespaces.clone(), + player_addresses.clone(), + achievement_ids.clone(), + ) + .await; + + if let Ok(rcv) = rcv { + backoff = Duration::from_secs(1); // Reset backoff on successful connection + + let mut rcv = rcv.take_until_if(tripwire.clone()); + + while let Some(Ok((id, progression))) = rcv.next().await { + // Our first message will be the subscription ID + if let Some(tx) = sub_id_tx.take() { + tx.send(id).expect("Failed to send subscription ID"); + } else { + let progression: AchievementProgression = progression.into(); + callback(progression); + } + } + } + + // If we've reached this point, the stream has ended (possibly due to disconnection) + // We'll try to reconnect after a delay, unless the tripwire has been triggered + if tripwire.clone().now_or_never().unwrap_or_default() { + break; // Exit the loop if the subscription has been cancelled + } + sleep(backoff).await; + backoff = std::cmp::min(backoff * 2, max_backoff); + } + }); + + let subscription_id = match RUNTIME.block_on(sub_id_rx) { + Ok(id) => id, + Err(_) => { + return Result::Err(Error { + message: CString::new("Failed to establish achievement progression subscription") + .unwrap() + .into_raw(), + }); + } + }; + + let subscription = Subscription { id: subscription_id, trigger }; + + Result::Ok(Box::into_raw(Box::new(subscription))) +} + +/// Updates an existing achievement progression subscription with new parameters +/// +/// # Parameters +/// * `client` - Pointer to ToriiClient instance +/// * `subscription` - Pointer to existing Subscription +/// * `world_addresses` - Array of world addresses to subscribe to +/// * `world_addresses_len` - Length of world_addresses array +/// * `namespaces` - Array of namespaces to subscribe to +/// * `namespaces_len` - Length of namespaces array +/// * `player_addresses` - Array of player addresses to subscribe to +/// * `player_addresses_len` - Length of player_addresses array +/// * `achievement_ids` - Array of achievement IDs to subscribe to +/// * `achievement_ids_len` - Length of achievement_ids array +/// +/// # Returns +/// Result containing success boolean or error +#[no_mangle] +pub unsafe extern "C" fn client_update_achievement_progression_subscription( + client: *mut ToriiClient, + subscription: *mut Subscription, + world_addresses: *const types::FieldElement, + world_addresses_len: usize, + namespaces: *const *const c_char, + namespaces_len: usize, + player_addresses: *const types::FieldElement, + player_addresses_len: usize, + achievement_ids: *const *const c_char, + achievement_ids_len: usize, +) -> Result { + // Convert world_addresses array to Vec if not empty + let world_addresses = if world_addresses.is_null() || world_addresses_len == 0 { + Vec::new() + } else { + let addrs = unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; + addrs.iter().map(|addr| addr.clone().into()).collect::>() + }; + + // Convert namespaces array to Vec if not empty + let namespaces = if namespaces.is_null() || namespaces_len == 0 { + Vec::new() + } else { + let ns = unsafe { std::slice::from_raw_parts(namespaces, namespaces_len) }; + ns.iter() + .map(|n| unsafe { CStr::from_ptr(*n).to_string_lossy().to_string() }) + .collect::>() + }; + + // Convert player_addresses array to Vec if not empty + let player_addresses = if player_addresses.is_null() || player_addresses_len == 0 { + Vec::new() + } else { + let addrs = unsafe { std::slice::from_raw_parts(player_addresses, player_addresses_len) }; + addrs.iter().map(|addr| addr.clone().into()).collect::>() + }; + + // Convert achievement_ids array to Vec if not empty + let achievement_ids = if achievement_ids.is_null() || achievement_ids_len == 0 { + Vec::new() + } else { + let ids = unsafe { std::slice::from_raw_parts(achievement_ids, achievement_ids_len) }; + ids.iter() + .map(|id| unsafe { CStr::from_ptr(*id).to_string_lossy().to_string() }) + .collect::>() + }; + + match RUNTIME.block_on((*client).inner.update_achievement_progression_subscription( + (*subscription).id, + world_addresses, + namespaces, + player_addresses, + achievement_ids, + )) { + Ok(_) => Result::Ok(true), + Err(e) => Result::Err(e.into()), + } +} + /// Retrieves activities (user session tracking) matching query parameter /// /// # Parameters @@ -1394,11 +1653,14 @@ pub unsafe extern "C" fn client_update_activity_subscription( pub unsafe extern "C" fn client_on_event_message_update( client: *mut ToriiClient, clause: COption, + world_addresses: *const types::FieldElement, + world_addresses_len: usize, callback: unsafe extern "C" fn(Entity), ) -> Result<*mut Subscription> { let client = Arc::new(unsafe { &*client }); let clause: Option = clause.map(|c| c.into()).into(); - + let world_addresses = unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; + let world_addresses = world_addresses.iter().map(|addr| addr.clone().into()).collect::>(); let (trigger, tripwire) = Tripwire::new(); let (sub_id_tx, sub_id_rx) = oneshot::channel(); let mut sub_id_tx = Some(sub_id_tx); @@ -1410,7 +1672,7 @@ pub unsafe extern "C" fn client_on_event_message_update( let max_backoff = Duration::from_secs(60); loop { - let rcv = client_clone.inner.on_event_message_updated(clause.clone()).await; + let rcv = client_clone.inner.on_event_message_updated(clause.clone(), world_addresses.clone()).await; if let Ok(rcv) = rcv { backoff = Duration::from_secs(1); // Reset backoff on successful connection @@ -1465,11 +1727,14 @@ pub unsafe extern "C" fn client_update_event_message_subscription( client: *mut ToriiClient, subscription: *mut Subscription, clause: COption, + world_addresses: *const types::FieldElement, + world_addresses_len: usize, ) -> Result { let clause: Option = clause.map(|c| c.into()).into(); - + let world_addresses = unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; + let world_addresses = world_addresses.iter().map(|addr| addr.clone().into()).collect(); match RUNTIME - .block_on((*client).inner.update_event_message_subscription((*subscription).id, clause)) + .block_on((*client).inner.update_event_message_subscription((*subscription).id, clause, world_addresses)) { Ok(_) => Result::Ok(true), Err(e) => Result::Err(e.into()), diff --git a/src/c/types.rs b/src/c/types.rs index 66e6e03..06d9339 100644 --- a/src/c/types.rs +++ b/src/c/types.rs @@ -776,6 +776,7 @@ impl From for TransactionCall { #[derive(Clone, Debug)] #[repr(C)] pub struct Query { + pub world_addresses: CArray, pub pagination: Pagination, pub clause: COption, pub no_hashed_keys: bool, @@ -1022,6 +1023,7 @@ pub enum ValueType { #[derive(Clone, Debug)] #[repr(C)] pub struct Entity { + pub world_address: FieldElement, pub hashed_keys: FieldElement, pub models: CArray, pub created_at: u64, @@ -1035,6 +1037,7 @@ impl From for torii_proto::schema::Entity { let models = models.into_iter().map(|m| m.into()).collect(); torii_proto::schema::Entity { + world_address: val.world_address.into(), hashed_keys: val.hashed_keys.into(), models, created_at: DateTime::from_timestamp(val.created_at as i64, 0).unwrap(), @@ -1049,6 +1052,7 @@ impl From for Entity { let models = val.models.into_iter().map(|m| m.into()).collect::>(); Entity { + world_address: val.world_address.into(), hashed_keys: val.hashed_keys.into(), models: models.into(), created_at: val.created_at.timestamp() as u64, @@ -1400,6 +1404,7 @@ impl From for torii_proto::Query { let clause = val.clause.map(|c| c.into()).into(); torii_proto::Query { + world_addresses: val.world_addresses.into(), pagination: val.pagination.into(), clause, models, @@ -1414,6 +1419,7 @@ impl From for Query { let models = StringVec(val.models).into(); Query { + world_addresses: val.world_addresses.into(), pagination: val.pagination.into(), clause: val.clause.into(), models, @@ -1627,6 +1633,7 @@ impl From for torii_proto::World { #[derive(Clone, Debug)] #[repr(C)] pub struct Model { + pub world_address: FieldElement, pub schema: Ty, pub namespace: *const c_char, pub name: *const c_char, @@ -1645,6 +1652,7 @@ impl From for Model { let layout = CString::new(layout).unwrap().into_raw(); Model { + world_address: value.world_address.into(), schema: value.schema.into(), name: CString::new(value.name.clone()).unwrap().into_raw(), namespace: CString::new(value.namespace.clone()).unwrap().into_raw(), @@ -1665,6 +1673,7 @@ impl From for torii_proto::Model { let layout = serde_json::from_str(&layout).unwrap(); torii_proto::Model { + world_address: value.world_address.into(), schema: value.schema.into(), namespace: unsafe { CString::from_raw(value.namespace as *mut c_char).into_string().unwrap() @@ -1716,6 +1725,7 @@ impl From for Event { pub struct Message { pub message: *const c_char, pub signature: CArray, + pub world_address: FieldElement, } impl From for torii_proto::Message { @@ -1725,7 +1735,7 @@ impl From for torii_proto::Message { unsafe { std::slice::from_raw_parts(val.signature.data, val.signature.data_len) }; let signature = signature_slice.iter().map(|f| f.clone().into()).collect(); - torii_proto::Message { message, signature } + torii_proto::Message { message, signature, world_address: val.world_address.into() } } } @@ -1798,7 +1808,7 @@ pub struct AggregationEntry { pub value: U256, pub display_value: *const c_char, pub position: u64, - pub model_id: FieldElement, + pub model_id: *const c_char, pub created_at: u64, pub updated_at: u64, } @@ -1812,7 +1822,7 @@ impl From for AggregationEntry { value: val.value.into(), display_value: CString::new(val.display_value.clone()).unwrap().into_raw(), position: val.position, - model_id: val.model_id.into(), + model_id: CString::new(val.model_id.clone()).unwrap().into_raw(), created_at: val.created_at.timestamp() as u64, updated_at: val.updated_at.timestamp() as u64, } @@ -1864,3 +1874,253 @@ impl From for Activity { } } } + +#[derive(Clone, Debug)] +#[repr(C)] +pub struct AchievementQuery { + pub world_addresses: CArray, + pub namespaces: CArray<*const c_char>, + pub hidden: COption, + pub pagination: Pagination, +} + +impl From for torii_proto::AchievementQuery { + fn from(val: AchievementQuery) -> Self { + let namespaces: Vec<*const c_char> = val.namespaces.into(); + let namespaces = namespaces + .into_iter() + .map(|ns| unsafe { CStr::from_ptr(ns).to_string_lossy().to_string() }) + .collect::>(); + + torii_proto::AchievementQuery { + world_addresses: val.world_addresses.into(), + namespaces, + hidden: val.hidden.into(), + pagination: val.pagination.into(), + } + } +} + +#[derive(Clone, Debug)] +#[repr(C)] +pub struct PlayerAchievementQuery { + pub world_addresses: CArray, + pub namespaces: CArray<*const c_char>, + pub player_addresses: CArray, + pub pagination: Pagination, +} + +impl From for torii_proto::PlayerAchievementQuery { + fn from(val: PlayerAchievementQuery) -> Self { + let namespaces: Vec<*const c_char> = val.namespaces.into(); + let namespaces = namespaces + .into_iter() + .map(|ns| unsafe { CStr::from_ptr(ns).to_string_lossy().to_string() }) + .collect::>(); + + torii_proto::PlayerAchievementQuery { + world_addresses: val.world_addresses.into(), + namespaces, + player_addresses: val.player_addresses.into(), + pagination: val.pagination.into(), + } + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct Achievement { + pub id: *const c_char, + pub world_address: FieldElement, + pub namespace: *const c_char, + pub entity_id: *const c_char, + pub hidden: bool, + pub index: u32, + pub points: u32, + pub start: *const c_char, + pub end: *const c_char, + pub group: *const c_char, + pub icon: *const c_char, + pub title: *const c_char, + pub description: *const c_char, + pub tasks: CArray, + pub data: *const c_char, + pub total_completions: u32, + pub completion_rate: f64, + pub created_at: u64, + pub updated_at: u64, +} + +impl From for Achievement { + fn from(val: torii_proto::Achievement) -> Self { + let tasks: Vec = val.tasks.into_iter().map(|t| t.into()).collect(); + + Achievement { + id: CString::new(val.id).unwrap().into_raw(), + world_address: val.world_address.into(), + namespace: CString::new(val.namespace).unwrap().into_raw(), + entity_id: CString::new(val.entity_id).unwrap().into_raw(), + hidden: val.hidden, + index: val.index, + points: val.points, + start: CString::new(val.start).unwrap().into_raw(), + end: CString::new(val.end).unwrap().into_raw(), + group: CString::new(val.group).unwrap().into_raw(), + icon: CString::new(val.icon).unwrap().into_raw(), + title: CString::new(val.title).unwrap().into_raw(), + description: CString::new(val.description).unwrap().into_raw(), + tasks: tasks.into(), + data: CString::new(val.data.unwrap_or_default()).unwrap().into_raw(), + total_completions: val.total_completions, + completion_rate: val.completion_rate, + created_at: val.created_at.timestamp() as u64, + updated_at: val.updated_at.timestamp() as u64, + } + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct AchievementTask { + pub task_id: *const c_char, + pub description: *const c_char, + pub total: u32, + pub total_completions: u32, + pub completion_rate: f64, + pub created_at: u64, +} + +impl From for AchievementTask { + fn from(val: torii_proto::AchievementTask) -> Self { + AchievementTask { + task_id: CString::new(val.task_id).unwrap().into_raw(), + description: CString::new(val.description).unwrap().into_raw(), + total: val.total, + total_completions: val.total_completions, + completion_rate: val.completion_rate, + created_at: val.created_at.timestamp() as u64, + } + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct PlayerAchievementEntry { + pub player_address: FieldElement, + pub stats: PlayerAchievementStats, + pub achievements: CArray, +} + +impl From for PlayerAchievementEntry { + fn from(val: torii_proto::PlayerAchievementEntry) -> Self { + let achievements: Vec = + val.achievements.into_iter().map(|a| a.into()).collect(); + + PlayerAchievementEntry { + player_address: val.player_address.into(), + stats: val.stats.into(), + achievements: achievements.into(), + } + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct PlayerAchievementStats { + pub total_points: u32, + pub completed_achievements: u32, + pub total_achievements: u32, + pub completion_percentage: f64, + pub last_achievement_at: COption, + pub created_at: u64, + pub updated_at: u64, +} + +impl From for PlayerAchievementStats { + fn from(val: torii_proto::PlayerAchievementStats) -> Self { + PlayerAchievementStats { + total_points: val.total_points, + completed_achievements: val.completed_achievements, + total_achievements: val.total_achievements, + completion_percentage: val.completion_percentage, + last_achievement_at: val.last_achievement_at.map(|t| t.timestamp() as u64).into(), + created_at: val.created_at.timestamp() as u64, + updated_at: val.updated_at.timestamp() as u64, + } + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct PlayerAchievementProgress { + pub achievement: Achievement, + pub task_progress: CArray, + pub completed: bool, + pub progress_percentage: f64, +} + +impl From for PlayerAchievementProgress { + fn from(val: torii_proto::PlayerAchievementProgress) -> Self { + let task_progress: Vec = + val.task_progress.into_iter().map(|t| t.into()).collect(); + + PlayerAchievementProgress { + achievement: val.achievement.into(), + task_progress: task_progress.into(), + completed: val.completed, + progress_percentage: val.progress_percentage, + } + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct TaskProgress { + pub task_id: *const c_char, + pub count: u32, + pub completed: bool, +} + +impl From for TaskProgress { + fn from(val: torii_proto::TaskProgress) -> Self { + TaskProgress { + task_id: CString::new(val.task_id).unwrap().into_raw(), + count: val.count, + completed: val.completed, + } + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct AchievementProgression { + pub id: *const c_char, + pub achievement_id: *const c_char, + pub task_id: *const c_char, + pub world_address: FieldElement, + pub namespace: *const c_char, + pub player_id: FieldElement, + pub count: u32, + pub completed: bool, + pub completed_at: COption, + pub created_at: u64, + pub updated_at: u64, +} + +impl From for AchievementProgression { + fn from(val: torii_proto::AchievementProgression) -> Self { + AchievementProgression { + id: CString::new(val.id).unwrap().into_raw(), + achievement_id: CString::new(val.achievement_id).unwrap().into_raw(), + task_id: CString::new(val.task_id).unwrap().into_raw(), + world_address: val.world_address.into(), + namespace: CString::new(val.namespace).unwrap().into_raw(), + player_id: val.player_id.into(), + count: val.count, + completed: val.completed, + completed_at: val.completed_at.map(|t| t.timestamp() as u64).into(), + created_at: val.created_at.timestamp() as u64, + updated_at: val.updated_at.timestamp() as u64, + } + } +} diff --git a/src/wasm/mod.rs b/src/wasm/mod.rs index c59533e..7232a00 100644 --- a/src/wasm/mod.rs +++ b/src/wasm/mod.rs @@ -42,10 +42,12 @@ use crate::wasm::types::{ mod types; use types::{ - Activities, Activity, AggregationEntry, Aggregations, BlockId, Call, Calls, Clause, - ClientConfig, Contract, Contracts, Controller, Controllers, Entities, Entity, KeysClause, - KeysClauses, Message, Model, Page, Query, Signature, Token, TokenBalance, TokenBalances, - TokenContracts, TokenTransfer, TokenTransfers, Tokens, Transaction, Transactions, WasmU256, + Achievement, AchievementProgression, AchievementQuery, Achievements, Activities, Activity, + AggregationEntry, Aggregations, BlockId, Call, Calls, Clause, ClientConfig, Contract, + Contracts, Controller, Controllers, Entities, Entity, KeysClause, KeysClauses, Message, Model, + Page, PlayerAchievementQuery, PlayerAchievements, Query, Signature, Token, TokenBalance, + TokenBalances, TokenContracts, TokenTransfer, TokenTransfers, Tokens, Transaction, + Transactions, WasmU256, }; const JSON_COMPAT_SERIALIZER: serde_wasm_bindgen::Serializer = @@ -697,12 +699,9 @@ impl ToriiClient { #[cfg(feature = "console-error-panic")] console_error_panic_hook::set_once(); - let ClientConfig { torii_url, world_address } = config; + let ClientConfig { torii_url, .. } = config; - let world_address = Felt::from_str(&world_address) - .map_err(|err| JsValue::from(format!("failed to parse world address: {err}")))?; - - let client = torii_client::Client::new(torii_url, world_address) + let client = torii_client::Client::new(torii_url) .await .map_err(|err| JsValue::from(format!("failed to build client: {err}")))?; @@ -1042,6 +1041,55 @@ impl ToriiClient { Ok(Aggregations(aggregations.into())) } + /// Retrieves achievements matching the query + /// + /// # Parameters + /// * `query` - AchievementQuery parameters + /// + /// # Returns + /// Result containing achievements or error + #[wasm_bindgen(js_name = getAchievements)] + pub async fn get_achievements(&self, query: AchievementQuery) -> Result { + #[cfg(feature = "console-error-panic")] + console_error_panic_hook::set_once(); + + let query = query.into(); + + let achievements = self + .inner + .achievements(query) + .await + .map_err(|err| JsValue::from(format!("failed to get achievements: {err}")))?; + + Ok(Achievements(achievements.into())) + } + + /// Retrieves player achievement data matching the query + /// + /// # Parameters + /// * `query` - PlayerAchievementQuery parameters + /// + /// # Returns + /// Result containing player achievements or error + #[wasm_bindgen(js_name = getPlayerAchievements)] + pub async fn get_player_achievements( + &self, + query: PlayerAchievementQuery, + ) -> Result { + #[cfg(feature = "console-error-panic")] + console_error_panic_hook::set_once(); + + let query = query.into(); + + let player_achievements = self + .inner + .player_achievements(query) + .await + .map_err(|err| JsValue::from(format!("failed to get player achievements: {err}")))?; + + Ok(PlayerAchievements(player_achievements.into())) + } + /// Gets activities (user session tracking) matching query parameters /// /// # Parameters @@ -1103,6 +1151,7 @@ impl ToriiClient { let results = self .inner .entities(torii_proto::Query { + world_addresses: vec![], pagination: torii_proto::Pagination { limit: Some(limit), cursor, @@ -1154,12 +1203,18 @@ impl ToriiClient { pub async fn on_entity_updated( &self, clause: Option, + world_addresses: Option>, callback: js_sys::Function, ) -> Result { #[cfg(feature = "console-error-panic")] console_error_panic_hook::set_once(); let clause = clause.map(|c| c.into()); + let world_addresses = world_addresses + .unwrap_or_default() + .into_iter() + .map(|addr| Felt::from_hex(&addr).unwrap()) + .collect::>(); let (sub_id_tx, sub_id_rx) = oneshot::channel(); let mut sub_id_tx = Some(sub_id_tx); let (trigger, tripwire) = Tripwire::new(); @@ -1171,7 +1226,7 @@ impl ToriiClient { let max_backoff = 60000; loop { - if let Ok(stream) = client.on_entity_updated(clause.clone()).await { + if let Ok(stream) = client.on_entity_updated(clause.clone(), world_addresses.clone()).await { backoff = 1000; // Reset backoff on successful connection let mut stream = stream.take_until_if(tripwire.clone()); @@ -1224,10 +1279,16 @@ impl ToriiClient { &self, subscription: &Subscription, clause: Option, + world_addresses: Option>, ) -> Result<(), JsValue> { let clause = clause.map(|c| c.into()); + let world_addresses = world_addresses + .unwrap_or_default() + .into_iter() + .map(|addr| Felt::from_hex(&addr).unwrap()) + .collect::>(); self.inner - .update_entity_subscription(subscription.id, clause) + .update_entity_subscription(subscription.id, clause, world_addresses) .await .map_err(|err| JsValue::from(format!("failed to update subscription: {err}"))) } @@ -1244,12 +1305,18 @@ impl ToriiClient { pub async fn on_event_message_updated( &self, clause: Option, + world_addresses: Option>, callback: js_sys::Function, ) -> Result { #[cfg(feature = "console-error-panic")] console_error_panic_hook::set_once(); let clause = clause.map(|c| c.into()); + let world_addresses = world_addresses + .unwrap_or_default() + .into_iter() + .map(|addr| Felt::from_hex(&addr).unwrap()) + .collect::>(); let (sub_id_tx, sub_id_rx) = oneshot::channel(); let mut sub_id_tx = Some(sub_id_tx); let (trigger, tripwire) = Tripwire::new(); @@ -1261,7 +1328,7 @@ impl ToriiClient { let max_backoff = 60000; loop { - if let Ok(stream) = client.on_event_message_updated(clause.clone()).await { + if let Ok(stream) = client.on_event_message_updated(clause.clone(), world_addresses.clone()).await { backoff = 1000; // Reset backoff on successful connection let mut stream = stream.take_until_if(tripwire.clone()); @@ -1314,10 +1381,16 @@ impl ToriiClient { &self, subscription: &Subscription, clause: Option, + world_addresses: Option>, ) -> Result<(), JsValue> { let clause = clause.map(|c| c.into()); + let world_addresses = world_addresses + .unwrap_or_default() + .into_iter() + .map(|addr| Felt::from_hex(&addr).unwrap()) + .collect::>(); self.inner - .update_event_message_subscription(subscription.id, clause) + .update_event_message_subscription(subscription.id, clause, world_addresses) .await .map_err(|err| JsValue::from(format!("failed to update subscription: {err}"))) } @@ -1958,6 +2031,148 @@ impl ToriiClient { .map_err(|err| JsValue::from(format!("failed to update subscription: {err}"))) } + /// Subscribes to achievement progression updates + /// + /// # Parameters + /// * `world_addresses` - Optional array of world addresses as hex strings + /// * `namespaces` - Optional array of namespaces + /// * `player_addresses` - Optional array of player addresses as hex strings + /// * `achievement_ids` - Optional array of achievement IDs + /// * `callback` - JavaScript function to call on updates + /// + /// # Returns + /// Result containing subscription handle or error + #[wasm_bindgen(js_name = onAchievementProgressionUpdated)] + pub async fn on_achievement_progression_updated( + &self, + world_addresses: Option>, + namespaces: Option>, + player_addresses: Option>, + achievement_ids: Option>, + callback: js_sys::Function, + ) -> Result { + #[cfg(feature = "console-error-panic")] + console_error_panic_hook::set_once(); + + let world_addresses = world_addresses + .unwrap_or_default() + .into_iter() + .map(|addr| Felt::from_hex(&addr).unwrap()) + .collect::>(); + + let namespaces = namespaces.unwrap_or_default(); + + let player_addresses = player_addresses + .unwrap_or_default() + .into_iter() + .map(|addr| Felt::from_hex(&addr).unwrap()) + .collect::>(); + + let achievement_ids = achievement_ids.unwrap_or_default(); + + let (sub_id_tx, sub_id_rx) = oneshot::channel(); + let mut sub_id_tx = Some(sub_id_tx); + let (trigger, tripwire) = Tripwire::new(); + + // Spawn a new task to handle the stream and reconnections + let client = self.inner.clone(); + wasm_bindgen_futures::spawn_local(async move { + let mut backoff = 1000; + let max_backoff = 60000; + + loop { + if let Ok(stream) = client + .on_achievement_progression_updated( + world_addresses.clone(), + namespaces.clone(), + player_addresses.clone(), + achievement_ids.clone(), + ) + .await + { + backoff = 1000; // Reset backoff on successful connection + + let mut stream = stream.take_until_if(tripwire.clone()); + + while let Some(Ok((id, progression))) = stream.next().await { + if let Some(tx) = sub_id_tx.take() { + tx.send(id).expect("Failed to send subscription ID"); + } else { + let progression: AchievementProgression = progression.into(); + + let _ = callback.call1( + &JsValue::null(), + &progression.serialize(&JSON_COMPAT_SERIALIZER).unwrap(), + ); + } + } + } + + // If we've reached this point, the stream has ended (possibly due to disconnection) + // We'll try to reconnect after a delay, unless the tripwire has been triggered + if tripwire.clone().now_or_never().unwrap_or_default() { + break; // Exit the loop if the subscription has been cancelled + } + gloo_timers::future::TimeoutFuture::new(backoff).await; + backoff = std::cmp::min(backoff * 2, max_backoff); + } + }); + + let subscription_id = match sub_id_rx.await { + Ok(id) => id, + Err(_) => { + return Err(JsValue::from( + "Failed to establish achievement progression subscription", + )); + } + }; + let subscription = Subscription { id: subscription_id, trigger }; + + Ok(subscription) + } + + /// Updates achievement progression subscription + /// + /// # Parameters + /// * `subscription` - Existing subscription to update + /// * `world_addresses` - Array of world addresses + /// * `namespaces` - Array of namespaces + /// * `player_addresses` - Array of player addresses + /// * `achievement_ids` - Array of achievement IDs + /// + /// # Returns + /// Result containing unit or error + #[wasm_bindgen(js_name = updateAchievementProgressionSubscription)] + pub async fn update_achievement_progression_subscription( + &self, + subscription: &Subscription, + world_addresses: Vec, + namespaces: Vec, + player_addresses: Vec, + achievement_ids: Vec, + ) -> Result<(), JsValue> { + let world_addresses = world_addresses + .into_iter() + .map(|addr| Felt::from_hex(&addr).unwrap()) + .collect::>(); + + let player_addresses = player_addresses + .into_iter() + .map(|addr| Felt::from_hex(&addr).unwrap()) + .collect::>(); + + self.inner + .update_achievement_progression_subscription( + subscription.id, + world_addresses, + namespaces, + player_addresses, + achievement_ids, + ) + .await + .map_err(|err| JsValue::from(format!("failed to update subscription: {err}"))) + } + /// Updates an existing activity subscription /// /// # Parameters @@ -2022,7 +2237,7 @@ impl ToriiClient { .await .map_err(|err| JsValue::from(err.to_string()))?; - Ok(format!("{:#x}", entity_id)) + Ok(entity_id) } /// Publishes multiple messages to the network @@ -2049,7 +2264,7 @@ impl ToriiClient { .await .map_err(|err| JsValue::from(err.to_string()))?; - Ok(entity_ids.into_iter().map(|id| format!("{:#x}", id)).collect()) + Ok(entity_ids) } } diff --git a/src/wasm/types.rs b/src/wasm/types.rs index b396486..40cd3b0 100644 --- a/src/wasm/types.rs +++ b/src/wasm/types.rs @@ -621,6 +621,7 @@ impl From for Model { #[derive(Tsify, Serialize, Deserialize, Debug)] #[tsify(into_wasm_abi, from_wasm_abi, hashmap_as_object)] pub struct Entity { + pub world_address: String, pub hashed_keys: String, pub models: HashMap, pub created_at: u64, @@ -631,6 +632,7 @@ pub struct Entity { impl From for Entity { fn from(value: torii_proto::schema::Entity) -> Self { Self { + world_address: format!("{:#x}", value.world_address), hashed_keys: format!("{:#x}", value.hashed_keys), models: value.models.into_iter().map(|m| (m.name.clone(), m.into())).collect(), created_at: value.created_at.timestamp() as u64, @@ -711,6 +713,7 @@ impl From for starknet::core::types::BlockId { #[derive(Tsify, Serialize, Deserialize, Debug)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Query { + pub world_addresses: Vec, pub pagination: Pagination, pub clause: Option, pub no_hashed_keys: bool, @@ -786,6 +789,11 @@ impl From for torii_proto::OrderDirection { impl From for torii_proto::Query { fn from(value: Query) -> Self { Self { + world_addresses: value + .world_addresses + .into_iter() + .map(|addr| Felt::from_hex(&addr).unwrap()) + .collect(), pagination: value.pagination.into(), clause: value.clause.map(|c| c.into()), no_hashed_keys: value.no_hashed_keys, @@ -1067,6 +1075,7 @@ pub struct Event { pub struct Message { pub message: String, pub signature: Vec, + pub world_address: String, } impl From for torii_proto::Message { @@ -1074,6 +1083,7 @@ impl From for torii_proto::Message { torii_proto::Message { message: val.message, signature: val.signature.iter().map(|s| Felt::from_str(s).unwrap()).collect(), + world_address: Felt::from_hex(&val.world_address).unwrap(), } } } @@ -1151,7 +1161,7 @@ impl From for AggregationEntry { value: format!("0x{:x}", value.value), display_value: value.display_value, position: value.position, - model_id: format!("{:#x}", value.model_id), + model_id: value.model_id, created_at: value.created_at.timestamp() as u64, updated_at: value.updated_at.timestamp() as u64, } @@ -1195,3 +1205,255 @@ pub struct Aggregations(pub Page); #[derive(Tsify, Serialize, Deserialize, Debug)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Activities(pub Page); + +// ===== Achievement Types ===== + +#[derive(Tsify, Serialize, Deserialize, Debug)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct AchievementQuery { + pub world_addresses: Vec, + pub namespaces: Vec, + pub hidden: Option, + pub pagination: Pagination, +} + +impl From for torii_proto::AchievementQuery { + fn from(value: AchievementQuery) -> Self { + Self { + world_addresses: value + .world_addresses + .into_iter() + .map(|addr| Felt::from_hex(&addr).unwrap()) + .collect(), + namespaces: value.namespaces, + hidden: value.hidden, + pagination: value.pagination.into(), + } + } +} + +#[derive(Tsify, Serialize, Deserialize, Debug)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct PlayerAchievementQuery { + pub world_addresses: Vec, + pub namespaces: Vec, + pub player_addresses: Vec, + pub pagination: Pagination, +} + +impl From for torii_proto::PlayerAchievementQuery { + fn from(value: PlayerAchievementQuery) -> Self { + Self { + world_addresses: value + .world_addresses + .into_iter() + .map(|addr| Felt::from_hex(&addr).unwrap()) + .collect(), + namespaces: value.namespaces, + player_addresses: value + .player_addresses + .into_iter() + .map(|addr| Felt::from_hex(&addr).unwrap()) + .collect(), + pagination: value.pagination.into(), + } + } +} + +#[derive(Tsify, Serialize, Deserialize, Debug)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct Achievement { + pub id: String, + pub world_address: String, + pub namespace: String, + pub entity_id: String, + pub hidden: bool, + pub index: u32, + pub points: u32, + pub start: String, + pub end: String, + pub group: String, + pub icon: String, + pub title: String, + pub description: String, + pub tasks: Vec, + pub data: String, + pub total_completions: u32, + pub completion_rate: f64, + pub created_at: u64, + pub updated_at: u64, +} + +impl From for Achievement { + fn from(value: torii_proto::Achievement) -> Self { + Self { + id: value.id, + world_address: format!("{:#x}", value.world_address), + namespace: value.namespace, + entity_id: value.entity_id, + hidden: value.hidden, + index: value.index, + points: value.points, + start: value.start, + end: value.end, + group: value.group, + icon: value.icon, + title: value.title, + description: value.description, + tasks: value.tasks.into_iter().map(|t| t.into()).collect(), + data: value.data.unwrap_or_default(), + total_completions: value.total_completions, + completion_rate: value.completion_rate, + created_at: value.created_at.timestamp() as u64, + updated_at: value.updated_at.timestamp() as u64, + } + } +} + +#[derive(Tsify, Serialize, Deserialize, Debug)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct AchievementTask { + pub task_id: String, + pub description: String, + pub total: u32, + pub total_completions: u32, + pub completion_rate: f64, + pub created_at: u64, +} + +impl From for AchievementTask { + fn from(value: torii_proto::AchievementTask) -> Self { + Self { + task_id: value.task_id, + description: value.description, + total: value.total, + total_completions: value.total_completions, + completion_rate: value.completion_rate, + created_at: value.created_at.timestamp() as u64, + } + } +} + +#[derive(Tsify, Serialize, Deserialize, Debug)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct PlayerAchievementEntry { + pub player_address: String, + pub stats: PlayerAchievementStats, + pub achievements: Vec, +} + +impl From for PlayerAchievementEntry { + fn from(value: torii_proto::PlayerAchievementEntry) -> Self { + Self { + player_address: format!("{:#x}", value.player_address), + stats: value.stats.into(), + achievements: value.achievements.into_iter().map(|a| a.into()).collect(), + } + } +} + +#[derive(Tsify, Serialize, Deserialize, Debug)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct PlayerAchievementStats { + pub total_points: u32, + pub completed_achievements: u32, + pub total_achievements: u32, + pub completion_percentage: f64, + pub last_achievement_at: Option, + pub created_at: u64, + pub updated_at: u64, +} + +impl From for PlayerAchievementStats { + fn from(value: torii_proto::PlayerAchievementStats) -> Self { + Self { + total_points: value.total_points, + completed_achievements: value.completed_achievements, + total_achievements: value.total_achievements, + completion_percentage: value.completion_percentage, + last_achievement_at: value.last_achievement_at.map(|t| t.timestamp() as u64), + created_at: value.created_at.timestamp() as u64, + updated_at: value.updated_at.timestamp() as u64, + } + } +} + +#[derive(Tsify, Serialize, Deserialize, Debug)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct PlayerAchievementProgress { + pub achievement: Achievement, + pub task_progress: Vec, + pub completed: bool, + pub progress_percentage: f64, +} + +impl From for PlayerAchievementProgress { + fn from(value: torii_proto::PlayerAchievementProgress) -> Self { + Self { + achievement: value.achievement.into(), + task_progress: value.task_progress.into_iter().map(|t| t.into()).collect(), + completed: value.completed, + progress_percentage: value.progress_percentage, + } + } +} + +#[derive(Tsify, Serialize, Deserialize, Debug)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct TaskProgress { + pub task_id: String, + pub count: u32, + pub completed: bool, +} + +impl From for TaskProgress { + fn from(value: torii_proto::TaskProgress) -> Self { + Self { + task_id: value.task_id, + count: value.count, + completed: value.completed, + } + } +} + +#[derive(Tsify, Serialize, Deserialize, Debug)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct AchievementProgression { + pub id: String, + pub achievement_id: String, + pub task_id: String, + pub world_address: String, + pub namespace: String, + pub player_id: String, + pub count: u32, + pub completed: bool, + pub completed_at: Option, + pub created_at: u64, + pub updated_at: u64, +} + +impl From for AchievementProgression { + fn from(value: torii_proto::AchievementProgression) -> Self { + Self { + id: value.id, + achievement_id: value.achievement_id, + task_id: value.task_id, + world_address: format!("{:#x}", value.world_address), + namespace: value.namespace, + player_id: format!("{:#x}", value.player_id), + count: value.count, + completed: value.completed, + completed_at: value.completed_at.map(|t| t.timestamp() as u64), + created_at: value.created_at.timestamp() as u64, + updated_at: value.updated_at.timestamp() as u64, + } + } +} + +#[derive(Tsify, Serialize, Deserialize, Debug)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct Achievements(pub Page); + +#[derive(Tsify, Serialize, Deserialize, Debug)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct PlayerAchievements(pub Page); From 5ae57f93858aee319d3e11f0bc1fc3a46d64187a Mon Sep 17 00:00:00 2001 From: Nasr Date: Fri, 10 Oct 2025 14:48:37 -0500 Subject: [PATCH 2/2] f --- src/c/mod.rs | 65 ++++++++++++++++++++++++++++++++--------------- src/wasm/mod.rs | 17 ++++++++----- src/wasm/types.rs | 6 +---- 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/src/c/mod.rs b/src/c/mod.rs index 7e1ac13..d1d99dd 100644 --- a/src/c/mod.rs +++ b/src/c/mod.rs @@ -84,9 +84,7 @@ lazy_static! { /// # Returns /// Result containing pointer to new ToriiClient instance or error #[no_mangle] -pub unsafe extern "C" fn client_new( - torii_url: *const c_char, -) -> Result<*mut ToriiClient> { +pub unsafe extern "C" fn client_new(torii_url: *const c_char) -> Result<*mut ToriiClient> { let torii_url = unsafe { CStr::from_ptr(torii_url).to_string_lossy().into_owned() }; let client_future = TClient::new(torii_url); @@ -727,8 +725,10 @@ pub unsafe extern "C" fn client_publish_message_batch( match RUNTIME.block_on(client_future) { Ok(message_ids) => { - let ids: Vec<*const c_char> = - message_ids.into_iter().map(|id| CString::new(id).unwrap().into_raw() as *const c_char).collect(); + let ids: Vec<*const c_char> = message_ids + .into_iter() + .map(|id| CString::new(id).unwrap().into_raw() as *const c_char) + .collect(); Result::Ok(ids.into()) } Err(e) => Result::Err(e.into()), @@ -811,8 +811,13 @@ pub unsafe extern "C" fn client_event_messages( /// # Returns /// World structure containing world information #[no_mangle] -pub unsafe extern "C" fn client_worlds(client: *mut ToriiClient, world_addresses: *const types::FieldElement, world_addresses_len: usize) -> Result> { - let world_addresses = unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; +pub unsafe extern "C" fn client_worlds( + client: *mut ToriiClient, + world_addresses: *const types::FieldElement, + world_addresses_len: usize, +) -> Result> { + let world_addresses = + unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; let world_addresses = world_addresses.iter().map(|addr| addr.clone().into()).collect(); let metadata_future = unsafe { (*client).inner.worlds(world_addresses) }; match RUNTIME.block_on(metadata_future) { @@ -938,8 +943,10 @@ pub unsafe extern "C" fn client_on_entity_state_update( let mut sub_id_tx = Some(sub_id_tx); let clause: Option = clause.map(|c| c.into()).into(); - let world_addresses = unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; - let world_addresses = world_addresses.iter().map(|addr| addr.clone().into()).collect::>(); + let world_addresses = + unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; + let world_addresses = + world_addresses.iter().map(|addr| addr.clone().into()).collect::>(); // Spawn a new thread to handle the stream and reconnections let client_clone = client.clone(); RUNTIME.spawn(async move { @@ -947,7 +954,8 @@ pub unsafe extern "C" fn client_on_entity_state_update( let max_backoff = Duration::from_secs(60); loop { - let rcv = client_clone.inner.on_entity_updated(clause.clone(), world_addresses.clone()).await; + let rcv = + client_clone.inner.on_entity_updated(clause.clone(), world_addresses.clone()).await; if let Ok(rcv) = rcv { backoff = Duration::from_secs(1); // Reset backoff on successful connection @@ -1006,9 +1014,14 @@ pub unsafe extern "C" fn client_update_entity_subscription( world_addresses_len: usize, ) -> Result { let clause: Option = clause.map(|c| c.into()).into(); - let world_addresses = unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; + let world_addresses = + unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; let world_addresses = world_addresses.iter().map(|addr| addr.clone().into()).collect(); - match RUNTIME.block_on((*client).inner.update_entity_subscription((*subscription).id, clause, world_addresses)) { + match RUNTIME.block_on((*client).inner.update_entity_subscription( + (*subscription).id, + clause, + world_addresses, + )) { Ok(_) => Result::Ok(true), Err(e) => Result::Err(e.into()), } @@ -1190,7 +1203,8 @@ pub unsafe extern "C" fn client_update_aggregation_subscription( /// /// # Parameters /// * `client` - Pointer to ToriiClient instance -/// * `query` - AchievementQuery containing world_addresses, namespaces, hidden filter, and pagination +/// * `query` - AchievementQuery containing world_addresses, namespaces, hidden filter, and +/// pagination /// /// # Returns /// Result containing Page of Achievement or error @@ -1212,7 +1226,8 @@ pub unsafe extern "C" fn client_achievements( /// /// # Parameters /// * `client` - Pointer to ToriiClient instance -/// * `query` - PlayerAchievementQuery containing world_addresses, namespaces, player_addresses, and pagination +/// * `query` - PlayerAchievementQuery containing world_addresses, namespaces, player_addresses, and +/// pagination /// /// # Returns /// Result containing Page of PlayerAchievementEntry or error @@ -1659,8 +1674,10 @@ pub unsafe extern "C" fn client_on_event_message_update( ) -> Result<*mut Subscription> { let client = Arc::new(unsafe { &*client }); let clause: Option = clause.map(|c| c.into()).into(); - let world_addresses = unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; - let world_addresses = world_addresses.iter().map(|addr| addr.clone().into()).collect::>(); + let world_addresses = + unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; + let world_addresses = + world_addresses.iter().map(|addr| addr.clone().into()).collect::>(); let (trigger, tripwire) = Tripwire::new(); let (sub_id_tx, sub_id_rx) = oneshot::channel(); let mut sub_id_tx = Some(sub_id_tx); @@ -1672,7 +1689,10 @@ pub unsafe extern "C" fn client_on_event_message_update( let max_backoff = Duration::from_secs(60); loop { - let rcv = client_clone.inner.on_event_message_updated(clause.clone(), world_addresses.clone()).await; + let rcv = client_clone + .inner + .on_event_message_updated(clause.clone(), world_addresses.clone()) + .await; if let Ok(rcv) = rcv { backoff = Duration::from_secs(1); // Reset backoff on successful connection @@ -1731,11 +1751,14 @@ pub unsafe extern "C" fn client_update_event_message_subscription( world_addresses_len: usize, ) -> Result { let clause: Option = clause.map(|c| c.into()).into(); - let world_addresses = unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; + let world_addresses = + unsafe { std::slice::from_raw_parts(world_addresses, world_addresses_len) }; let world_addresses = world_addresses.iter().map(|addr| addr.clone().into()).collect(); - match RUNTIME - .block_on((*client).inner.update_event_message_subscription((*subscription).id, clause, world_addresses)) - { + match RUNTIME.block_on((*client).inner.update_event_message_subscription( + (*subscription).id, + clause, + world_addresses, + )) { Ok(_) => Result::Ok(true), Err(e) => Result::Err(e.into()), } diff --git a/src/wasm/mod.rs b/src/wasm/mod.rs index 7232a00..eb7c8a9 100644 --- a/src/wasm/mod.rs +++ b/src/wasm/mod.rs @@ -1081,11 +1081,10 @@ impl ToriiClient { let query = query.into(); - let player_achievements = self - .inner - .player_achievements(query) - .await - .map_err(|err| JsValue::from(format!("failed to get player achievements: {err}")))?; + let player_achievements = + self.inner.player_achievements(query).await.map_err(|err| { + JsValue::from(format!("failed to get player achievements: {err}")) + })?; Ok(PlayerAchievements(player_achievements.into())) } @@ -1226,7 +1225,9 @@ impl ToriiClient { let max_backoff = 60000; loop { - if let Ok(stream) = client.on_entity_updated(clause.clone(), world_addresses.clone()).await { + if let Ok(stream) = + client.on_entity_updated(clause.clone(), world_addresses.clone()).await + { backoff = 1000; // Reset backoff on successful connection let mut stream = stream.take_until_if(tripwire.clone()); @@ -1328,7 +1329,9 @@ impl ToriiClient { let max_backoff = 60000; loop { - if let Ok(stream) = client.on_event_message_updated(clause.clone(), world_addresses.clone()).await { + if let Ok(stream) = + client.on_event_message_updated(clause.clone(), world_addresses.clone()).await + { backoff = 1000; // Reset backoff on successful connection let mut stream = stream.take_until_if(tripwire.clone()); diff --git a/src/wasm/types.rs b/src/wasm/types.rs index 40cd3b0..42dba94 100644 --- a/src/wasm/types.rs +++ b/src/wasm/types.rs @@ -1408,11 +1408,7 @@ pub struct TaskProgress { impl From for TaskProgress { fn from(value: torii_proto::TaskProgress) -> Self { - Self { - task_id: value.task_id, - count: value.count, - completed: value.completed, - } + Self { task_id: value.task_id, count: value.count, completed: value.completed } } }