Hi, thanks for getting back to me. Here’s a quick overview of our ORM. We’re building a very complex game (WoW, Dwarf Fortress, Neopets)
We built a DSL in Go that allows us to express the design as a tree. When you execute the go code, it builds .mo files.
DB.mo
public let rarity = NatMap.empty<E.Rarity>();
Rarity.mo
public type Rarity = ORM.Entity<R.Rarity, M.Rarity>;
Main.mo
// Rarity
public shared query({ caller }) func loadManyRarity(ids : [Nat]) : async (Result, [E.Rarity]) {
Query<R.Rarity, M.Rarity>(caller, stores.rarity, "root.admin.load").loadMany(ids);
};
public shared query({ caller }) func loadRangeRarity(start : Nat, count : Nat) : async (Result, [E.Rarity]) {
Query<R.Rarity, M.Rarity>(caller, stores.rarity, "root.admin.load").loadRange(start, count);
};
public shared({ caller }) func createRarity(r : R.Rarity) : async (Result, ?E.Rarity) {
Query<R.Rarity, M.Rarity>(caller, stores.rarity, "root").create(r);
};
public shared({ caller }) func updaSteRarity(id : Nat, r : R.Rarity) : async (Result, ?E.Rarity) {
Query<R.Rarity, M.Rarity>(caller, stores.rarity, "root").update(id, r);
};
Metadata.mo
// Rarity
public type Rarity = {
created : T.Timestamp;
modified : T.Timestamp;
};
public type RarityM = {
var created : T.Timestamp;
var modified : T.Timestamp;
};
public func mutateRarity(r : Rarity) : RarityM {
{
var created = r.created;
var modified = r.modified;
};
};
public func restoreRarity(r : RarityM) : Rarity {
{
created = r.created;
modified = r.modified;
};
};
public func newRarity(deps : Deps) : Rarity {
let value = Value.Value(deps);
{
created = value.timeNow();
modified = value.timeNow();
};
};
Record.mo
// Rarity
//
// Rarity
// We don't store weightings here because they would unbalance the game
// too much if ever changed.
//
public type Rarity = {
variant : T.Rarity;
name : Text;
description : Text;
icon : Nat;
};
public type RarityM = {
var variant : T.Rarity;
var name : Text;
var description : Text;
var icon : Nat;
};
public func mutateRarity(r : Rarity) : RarityM {
{
var variant = r.variant;
var name = r.name;
var description = r.description;
var icon = r.icon;
};
};
public func restoreRarity(r : RarityM) : Rarity {
{
variant = r.variant;
name = r.name;
description = r.description;
icon = r.icon;
};
};
public func validateRarity(r : R.Rarity) : Result {
let m = Result.Map();
m.add("name", VR.Game.name(r.name));
m.add("description", VR.Game.description(r.description));
m.result();
};
Store.mo
//
// Rarity
//
public let rarity = object {
public let name = "Rarity";
// read
public func load(id : Nat) : ?E.Rarity {
deps.db.rarity.get(id);
};
public func loadMany(ids : [Nat]) : Iter.Iter<E.Rarity> {
queryLoadMany<E.Rarity>(deps.db.rarity, ids);
};
public func loadRange(start : Nat, count : Nat) : Iter.Iter<E.Rarity> {
queryLoadRange<E.Rarity>(deps.db.rarity, start, count);
};
public func loadAll() : Iter.Iter<E.Rarity> {
deps.db.rarity.vals();
};
func validate(e : E.Rarity) : Result {
let m = Result.Map();
m.merge(R.validateRarity(e.data));
m.add("icon", hasOne<E.Icon>(deps.db.icon, e.data.icon));
m.result();
};
// create
public func create(id : Nat, r : R.Rarity) : ORM.StoreResult<E.Rarity> {
// must be null
let prev = deps.db.rarity.get(id);
if (prev != null) {
return storeResultError(#err("ID " # Nat.toText(id) # " already exists"), prev);
};
// validate
let e = {
id = id;
data = r;
metadata = M.newRarity(deps);
};
var err = validate(e);
if (err != #ok) { return storeResultError(err, null) };
// create
deps.db.rarity.put(id, e);
{
result = #ok;
entity = ?e;
onSuccess = func() {
};
onError = func() {
deps.db.rarity.delete(id);
};
};
};
// update
public func update(id : Nat, r : R.Rarity) : ORM.StoreResult<E.Rarity> {
// switch on previous value
switch (deps.db.rarity.get(id)) {
case (null) {
return storeResultError(#err("ID " # Nat.toText(id) # " not found"), null);
};
case (?prev) {
// validate
let e = {
id = id;
data = r;
metadata = updateMetadata(id);
};
var err = validate(e);
if (err != #ok) { return storeResultError(err, ?prev) };
// update
deps.db.rarity.put(id, e);
{
result = #ok;
entity = ?e;
onSuccess = func() {
};
onError = func() {
deps.db.rarity.put(id, prev);
};
};
};
};
};
// write
func updateMetadata(id : Nat) : M.Rarity {
let md = switch(deps.db.rarity.get(id)) {
case (?e) { e.metadata };
case (null) { M.newRarity(deps) };
};
let mut = M.mutateRarity(md);
mut.modified := Value.Value(deps).timeNow();
M.restoreRarity(mut);
};
};
This is exactly the system we need to build this game. The issue is that with a function limit of 6000 (after a morning’s optimisation we’re at 5998), we need a huge architectural rewrite and will probably lose a lot of the features that we’ve implemented so far.