Mimic Digivolves

Hey, so I didn’t really make a big scene about this initially, I think I got 2 comments… but I’ve been doing a huge amount of work on Mimic over the past months.

Its first incarnation was more of a Swiss army knife for the IC, wrapping most of the ic-cdk functions and auto-generating code via a build.rs script. I wanted a framework that would allow us to do most of the heavy lifting for canisters. It became too slow, unwieldy, and made too many assumptions about how developers develop.

I’ve stripped it down to be a data modelling, query language with a focus on types and validation. It does have a re-exported ic crate but it’s optional. I’ve thrown in a few helper functions that help our specific use case.

Error mapping has been a huge pain, especially things like call:: because RejectionCode doesn’t derive anything useful. The latest update means that there’s one single error enum, like the Diesel framework.

This is the only open source thing Im ever going to do, because it’s an absolutely indispensible tool that I need to create Dragginz.

Anyway please check it out, I’d like nothing more than people to deep dive and provide feedback. I want to emphasise that …


THE DOCUMENTATION IS OUT OF DATE AND AWFUL

but a few code snippets here can help understand what im trying to create

use api::prelude::*;
use design::{
    config::{
        api::manifest::component::{CharacterCreator, WorldBuilder},
        game::Rarity,
    },
    db::config::registry::Feature as RegistryFeature,
};
use game::Error;
use mimic::{prelude::*, types::SortDirection};

// mimic
mimic_start!("../../../mimic.toml");
mimic_memory_manager!(MEMORY_MANAGER);
mimic_stores!(MEMORY_MANAGER, CONFIG_STORE, 10);

#[init]
fn init() {
    mimic_init();
}

//
// ENDPOINTS
//

// rarities
// for testing
#[query(guard = "guard_query")]
pub fn rarities() -> Result<Vec<Rarity>, Error> {
    perf!();

    let rarities =
        mimic::query::load::<Rarity>().debug().all().execute(&CONFIG_STORE)?.entities().collect();

    Ok(rarities)
}

// rarities_query
// for testing
#[query(guard = "guard_query")]
pub fn rarities_query(
    filter: String,
    order: Vec<(String, SortDirection)>,
    offset: u32,
    limit: Option<u32>,
) -> Result<Vec<Rarity>, Error> {
    perf!();

    let rarities = mimic::query::load::<Rarity>()
        .debug()
        .all()
        .offset(offset)
        .filter_all(&filter)
        .order(order)
        .limit_option(limit)
        .execute(&CONFIG_STORE)?
        .entities()
        .collect();

    Ok(rarities)
}
///
/// Rarity
///
/// affects the chance of an item dropping or an event occurring
///
/// order  : frontend logic to see which order they come in, common lowest
///          and rarest highest
///

#[entity(
    sk(entity = "Rarity", field = "id"),
    fields(
        field(name = "id", value(item(is = "Ulid"), default = "Ulid::generate")),
        field(name = "name", value(item(is = "game::text::Name"))),
        field(name = "description", value(item(is = "game::text::Description"))),
        field(name = "level", value(item(is = "U8"))),
        field(name = "color", value(item(is = "types::color::RgbHex"))),
        field(name = "key", value(item(is = "game::client::Key"))),
        order(field = "level", direction = "asc"),
    ),
    traits(remove(EntityFixture))
)]
pub struct Rarity {}

impl EntityFixture for Rarity {
    fn fixtures() -> FixtureList {
        use RarityId as Id;

        let data = [
            (Id::Common, "Common", "ffffff", "common"),
            (Id::Uncommon, "Uncommon", "1eff00", "uncommon"),
            (Id::Rare, "Rare", "0070dd", "rare"),
            (Id::Epic, "Epic", "a335ee", "epic"),
            (Id::Legendary, "Legendary", "ff8000", "legendary"),
            (Id::Mythical, "Mythical", "e6cc80", "mythical"),
            (Id::Inconceivable, "Inconceivable", "ef0a6a", "inconceivable"),
        ];

        let mut fixtures = FixtureList::new();
        for (id, name, color, key) in data {
            fixtures.push(Self {
                id: id.into(),
                name: name.into(),
                color: color.into(),
                key: key.into(),
                ..Default::default()
            });
        }

        fixtures
    }
}

///
/// RarityId
///

#[entity_id(
    key = "Common",
    key = "Uncommon",
    key = "Rare",
    key = "Epic",
    key = "Legendary",
    key = "Mythical",
    key = "Inconceivable"
)]
pub struct RarityId {}

I’ll keep working on it, updating the forums. Help will be absolutely insanely appreciated.

Adam

12 Likes

going for at least ONE comment this time

2 Likes

Relentless dev detected!. Keep up the good work Adam! ; )

2 Likes

I will try to take a look at it over the weekend, else early next week!

1 Like

Nice! This looks very promising. I’ll definitely try using it in my next canister to provide more detailed feedback. In our projects, we built a repository layer to encapsulate all the headaches in one place with the ability for local testing, but it’s not a silver bullet. Using macros to define fields is an interesting solution. How is extending or modifying the structure implemented? If I need to add or remove fields during a new release, can this be done smoothly?

3 Likes

Ok wow never saw this sorry.

It’s still a mess because I’ve been super busy with other stuff. Every entity derives Default, and the deserialization is set up so that it’s always going to succeed (serde default on everything).

So if you add a field it has to have a default, if you remove a field it won’t break on deserialization.