Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Taxonomy of the "prototype" idea #534

Open
ludns opened this issue Mar 29, 2023 · 3 comments
Open

Taxonomy of the "prototype" idea #534

ludns opened this issue Mar 29, 2023 · 3 comments
Labels
discussion A discussion (without clear action/outcome)

Comments

@ludns
Copy link
Member

ludns commented Mar 29, 2023

A taxonomy of the “prototype” idea

Different projects using MUD have been exploring the idea of representing templates for entities either in bytecode or in storage.
Sky Strife calls them “prototypes”, other projects call them “factories”, and dk1a plugin calls them “prototype”.
In order to reduce confusion, I propose a taxonomy of the 3 types of “prototypes” I identified. I attached a name for each of them. These are very much not meant to be final — I tried to describe the underlying implementation with each name, and added a justification.

Macro

Key characteristic:

  • The “content” to be copied is stored in bytecode. It is created at compile time, just like macros.
  • The content cannot be changed without redeploying contracts.
  • When a macro is used, the content is copied into new records. There is no reference to the original content (with a macro it wouldn’t make too much sense anyway given macros are immutable).
    Example of implementation: dk1a plugin
    Why macro? In languages supporting macro, a macro is a “shortcut” created a compile time. Macros can’t be edited at runtime usually.
    How to implement it? as a plugin to tablegen (there is a PR for it: feat(cli): add prototypes to tablegen #506)

Factory

Key characteristic:

  • The content to be copied is stored in storage. It is created at deploy time
  • The content can be changed at runtime by changing the storage. (in our case by editing the Solecs data — MUD v1 — or the records in Store — MUD v2)
  • When a factory builds an entity, the content is copied into new records. There is no reference to the original content (beyond a simple pointer to the “name” of the prototype — which is what Sky Strife does). If the underlying storage changes (eg: an archer attack goes from 3 to 4), only new instantiations using the Factory will have the new value. Previously created entities / records will need to be updated
    Example of implementation: Sky Strife
    Why factory? Because in Java / other OOO languages, it’s called the Factory pattern, and the object built by the Factory can be edited at runtime by editing the Factory class
    How to implement it? as a plugin to tablegen, with additional user-code too (to define the prototypes at deploy time, and functions to edit them)

Prototype

Key characteristic

  • The content to be copied is stored in storage. It is created at deploy time. This content is called a prototype.
  • The content can be changed at runtime by changing the storage. (in our case by editing the Solecs data — MUD v1 — or the records in Store — MUD v2)
  • When a prototype gets instantiated into an instance, a new set of records (v2) or entity-component mapping (v1) is created with a pointer to the prototype for each table (v2) or component (v1) in the prototype. The content is not copied. This makes it cheap to instantiate prototypes compared to macro (cheapest) and factories.
  • Reads to an instance is proxied to the prototype.
  • Changing the prototype in storage will “change” each of its instantiations (lazily, because reads are proxied).
  • A Prototype pattern can optionally implement copy-on-write, where — for a specific component (v1) or table (v2) — the content of the prototype is copied when a write happen on an entity/record that was instantiated by the prototype and then edited. This specific record then looses its reference to the prototype.
  • Prototypes makes querying more complicated as reads to an instance need to be proxied to the prototype. Indexers need to be aware of the prototype implementation in order to edit all instances when the prototype is edited.
    Example of implementation: None
    Why prototype? In javascript, this is how classes used to work (and how a lot of the standard library is implemented). An object is created and functions get added to its prototype. Then any copy of this object inherits the functions, and when a function is added at runtime to the prototype objects, all of its clones “receive” the function too. It’s a reference, not a copy.
    How to implement it?
  • with a plugin to tablegen that
    • adds a “fromPrototype” column to each table that can have a prototype instance
    • hide the “fromPrototype” from read, and prevent direct writes
    • proxies read if “fromPrototype” is set
    • copy-on-write if “fromPrototype” is set
  • with a plugin to MODE that
    • re-indexes all instances of a prototype when a prototype changes
  • with a plugin to the network client stack that
    • re-indexes all instances of a prototype when a prototype changes

Alternatively
Proxies are a form of materialized view (they require a Join on the fromPrototype column).
We would need support for: [MODE /networking] Materialized View, [tablegen] read proxying (fake materialized view -- can't do real materialized view on-chain), [tablegen] foreign key support.
cc @dk1a

@ludns ludns added the discussion A discussion (without clear action/outcome) label Mar 29, 2023
@dk1a
Copy link
Contributor

dk1a commented Mar 29, 2023

Very much aligns with how I see prototypes, except I didn't have names for the 3 varieties.
Macro is a bit special in that the other 2 essentially use them at deploy time too, so the initial plugin version wasn't designed just for macros, but rather as a stepping stone to Prototype.
(I haven't thought too much about "fromPrototype" implementation yet, but your outline looks good)

@yonadaaa
Copy link
Contributor

The third Prototype idea is great because it makes a space-time tradeoff suited for blockchain. You replace an unbounded "copy the entire prototype" operation with constant cost "copy-on-write". Each interaction at runtime has a little more overhead, but in exchange, the network is not overwhelmed with a bunch of gas-guzzling upload tx's.

It reminds me OPCraft, where each tile had a "lazy" Perlin value that needed to be computed at runtime to interact with the tile. The value of the tile is only written on-chain when it changes.

It would be great to see this in a game lobby system, where map designs can be uploaded once then proxied across potentially thousands of sessions.

@Kooshaba
Copy link
Contributor

Kooshaba commented Apr 1, 2023

very keen on getting to version 3. IMO it has the most impact for developer experience. there must be so many different ways that people are implementing this same pattern out there in the wild.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion A discussion (without clear action/outcome)
Projects
None yet
Development

No branches or pull requests

4 participants