Interoperability
The Core contract facilitates app-to-app communication via the Interoperability Trait:
use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait};
use pixelaw::core::models::pixel::PixelUpdate;
use pixelaw::core::models::registry::App;
use starknet::ContractAddress;
#[starknet::interface]
trait IInteroperability<TContractState> {
fn on_pre_update(
self: @TContractState,
pixel_update: PixelUpdate,
app_caller: App,
player_caller: ContractAddress
);
fn on_post_update(
self: @TContractState,
pixel_update: PixelUpdate,
app_caller: App,
player_caller: ContractAddress
);
}
These functions are then called during update by the Core contract like so:
fn update_pixel(
self: @ContractState,
for_player: ContractAddress,
for_system: ContractAddress,
pixel_update: PixelUpdate
) {
'update_pixel'.print();
let world = self.world_dispatcher.read();
let mut pixel = get!(world, (pixel_update.x, pixel_update.y), (Pixel));
assert(
self.has_write_access(for_player, for_system, pixel, pixel_update), 'No access!'
);
let old_pixel_app = pixel.app;
old_pixel_app.print();
// pre update is done after checking if an update can be done
if !old_pixel_app.is_zero() {
let interoperable_app = IInteroperabilityDispatcher { contract_address: old_pixel_app };
let app_caller = get!(world, for_system, (App));
interoperable_app.on_pre_update(pixel_update, app_caller, for_player)
}
/// pixel updates are done here
// post updates are called after the updates are done
if !old_pixel_app.is_zero() {
let interoperable_app = IInteroperabilityDispatcher { contract_address: old_pixel_app };
let app_caller = get!(world, for_system, (App));
interoperable_app.on_post_update(pixel_update, app_caller, for_player)
}
'update_pixel DONE'.print();
}
Implementing Interoperability
To use these functions an app just has to follow these steps:
Step 1: Import the trait
Inside the dojo contract, import the interoperability trait
#[dojo::contract]
mod paint_actions {
// put import here
use pixelaw::core::traits::IInteroperability;
Step 2: Implement the trait
Like any trait, just implement it like so:
#[dojo::contract]
mod paint_actions {
// put import here
use pixelaw::core::traits::IInteroperability;
#[external(v0)] // makes sure that this can be called by the Core contract
impl ActionsInteroperability of IInteroperability<ContractState> {
fn on_pre_update(
self: @ContractState,
pixel_update: PixelUpdate,
app_caller: App,
player_caller: ContractAddress
) {
// put pre_update_code here
}
fn on_post_update(
self: @ContractState,
pixel_update: PixelUpdate,
app_caller: App,
player_caller: ContractAddress
){
// put post_update_code here
}
}
Examples
Snake passing through a Paint Pixel
When Snake is done passing through a pixel, it reverts the pixel back to its original pixel state. To demonstrate interoperability, when a Snake reverts back a Paint Pixel, it gets the Paint pixel to use fade on it.
Snake first imports the trait
#[dojo::contract]
mod snake_actions {
use pixelaw::core::traits::IInteroperability;
Snake implements the trait
#[external(v0)]
impl ActionsInteroperability of IInteroperability<ContractState> {
fn on_pre_update(
self: @ContractState,
pixel_update: PixelUpdate,
app_caller: App,
player_caller: ContractAddress
) {
// do nothing
}
fn on_post_update(
self: @ContractState,
pixel_update: PixelUpdate,
app_caller: App,
player_caller: ContractAddress
){
// put in code here
}
}
Snake first determines that the on_post_update is being called by the core contract:
fn on_post_update(
self: @ContractState,
pixel_update: PixelUpdate,
app_caller: App,
player_caller: ContractAddress
){
let core_actions = get_core_actions(self.world_dispatcher.read());
let core_actions_address = get_core_actions_address(self.world_dispatcher.read());
assert(core_actions_address == get_caller_address(), 'caller is not core_actions');
}
Next it makes sure that this is indeed a reversal of pixel state, and it's been called by the Snake Contract:
fn on_post_update(
self: @ContractState,
pixel_update: PixelUpdate,
app_caller: App,
player_caller: ContractAddress
){
let core_actions = get_core_actions(self.world_dispatcher.read());
let core_actions_address = get_core_actions_address(self.world_dispatcher.read());
assert(core_actions_address == get_caller_address(), 'caller is not core_actions');
// checks if this is a pixel state reversal called by the Snake Contract
if pixel_update.app.is_some() && app_caller.system == get_contract_address() {
}
}
Then, Snake needs to check if it's reverting back to a Paint pixel
fn on_post_update(
self: @ContractState,
pixel_update: PixelUpdate,
app_caller: App,
player_caller: ContractAddress
){
let core_actions = get_core_actions(self.world_dispatcher.read());
let core_actions_address = get_core_actions_address(self.world_dispatcher.read());
assert(core_actions_address == get_caller_address(), 'caller is not core_actions');
// checks if this is a pixel state reversal called by the Snake Contract
if pixel_update.app.is_some() && app_caller.system == get_contract_address() {
let old_app = pixel_update.app.unwrap();
let world = self.world_dispatcher.read();
let old_app = get!(world, old_app, (App));
// is this reverting back to paint
if old_app.name == 'paint' {
}
}
}
Lastly, it calls the paint contract to let it fade
fn on_post_update(
self: @ContractState,
pixel_update: PixelUpdate,
app_caller: App,
player_caller: ContractAddress
){
let core_actions = get_core_actions(self.world_dispatcher.read());
let core_actions_address = get_core_actions_address(self.world_dispatcher.read());
assert(core_actions_address == get_caller_address(), 'caller is not core_actions');
// checks if this is a pixel state reversal called by the Snake Contract
if pixel_update.app.is_some() && app_caller.system == get_contract_address() {
let old_app = pixel_update.app.unwrap();
let world = self.world_dispatcher.read();
let old_app = get!(world, old_app, (App));
// is this reverting back to paint
if old_app.name == 'paint' {
// creating fade params
let mut calldata: Array<felt252> = ArrayTrait::new();
let pixel = get!(world, (pixel_update.x, pixel_update.y), (Pixel));
calldata.append(pixel.owner.into());
calldata.append(old_app.system.into());
calldata.append(pixel_update.x.into());
calldata.append(pixel_update.y.into());
calldata.append(pixel_update.color.unwrap().into());
// 0x89ce6748d77414b79f2312bb20f6e67d3aa4a9430933a0f461fedc92983084 is fade as a selector
starknet::call_contract_syscall(old_app.system, 0x89ce6748d77414b79f2312bb20f6e67d3aa4a9430933a0f461fedc92983084, calldata.span());
}
}
}