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

feat(store): add foundation for v2 data modelling #352

Merged
merged 82 commits into from
Feb 7, 2023
Merged

Conversation

alvrs
Copy link
Member

@alvrs alvrs commented Jan 19, 2023

TODO

  • Streamline naming in Storage and Memory libraries (probably just use load and store instead of read and write?)
  • Move all internal functions to a separate StoreCoreInternal library to discourage using it
  • Change Storage library functions to make it clearer which argument is offset and which is length
  • Turn all storage pointer to uint256 for consistency (uint256 is better than bytes32 because it's easier to do arithmetic on)
  • change bytes.concat to abi.encodePacked where possible
  • Change all manual gas logs in tests to // !gasreport comments to use the mud gas-report cli command

Follow ups

  • Combine or further split Bytes, Buffer, Memory - they have some overlap and it’s not entirely clear where the boundaries are right now

Low hanging fruits for gas improvements

  • Add pushToDynamicField and popFromDynamicField functions to store core lib to avoid writing entire array when adding a single element (eg CallbackArray)
  • Storage.write and Storage.read
  • Decoding values returned by store core library using as little memory copies as possible
  • All low level libraries (Bytes, Buffer, Memory, Storage, SchemaLib, PackedCounterLib)
  • Maybe: use abi.encodePacked for passing data into StoreCore? We'd need to construct the PackedCounter in StoreCore, but gas implications would be interesting. Maybe we can even use abi.encodePacked for data coming out of StoreCore, and have utils to decode them into non-packed with a given schema.

@alvrs
Copy link
Member Author

alvrs commented Jan 25, 2023

very much WIP - don't look

@dk1a
Copy link
Contributor

dk1a commented Jan 31, 2023

a tiny improvement suggestion:
abi.encodePacked(x, y); vs bytes.concat(bytes4(x), bytes4(y)); etc - shorter and no need for typecasts

packages/store/src/Schema.sol Outdated Show resolved Hide resolved
}

// Allocate a buffer for the full data (static and dynamic)
Buffer buffer = Buffer_.allocate(uint128(outputLength));
Copy link
Contributor

@dk1a dk1a Feb 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming back to the Buffer comment - here it could be something like:

bytes memory result = new bytes(outputLength);
Buffer buffer = Buffer_.fromBytes(result);

And you use Buffer to append, but return the original bytes.
I guess I'm suggesting a Slice at this point, not Buffer

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with this!

packages/store/src/System.sol Outdated Show resolved Hide resolved
/**
* Get the length of the data for the given schema type
* (Because Solidity doesn't support constant arrays, we need to use a function)
* TODO: add more types and make it more efficient (avoid linear search)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoiding both linear and binary searches is probably a good idea, something like https://github.com/dk1a/mud/blob/c4cae23ac0284d6d475b683ad145daaf748e910e/packages/solecs/v2/SchemaType.sol#L225

Comment on lines 11 to 20
* @dev Left-aligned bit mask (e.g. for partial mload/mstore).
* For length >= 32 returns type(uint256).max
*
* length 0: 0x000000...000000
* length 1: 0xff0000...000000
* length 2: 0xffff00...000000
* ...
* length 30: 0xffffff...ff0000
* length 31: 0xffffff...ffff00
* length 32+: 0xffffff...ffffff
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you changed it from bytes to bits the comments are confusing.
But it should probably be inlined into _loadPartialWord and _storePartialWord and combined with the other bit manipulation for efficiency anyways

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, removed the comments for now, but agree with inlining it later

Copy link
Contributor

@dk1a dk1a left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IStore looks good; though I'm not sure how useful MudStoreDeleteRecord is, since it's basically like MudStoreSetRecord with all zeros. There doesn't seem to be a built-in has like with components (which makes sense, since this is generic storage and you usually don't waste a slot on an existence flag)

To summarize about Buffer - imo it currently combines the roles of a static buffer and slice. I'd explore not storing capacity, refactoring it to a Slice, and pairing it with bytes where necessary (and bytes length can be reduced if that's required).
Tried to avoid talking about gas for the most part.

Should I wait for merge before moving to gas optimizations and codegen?

@alvrs
Copy link
Member Author

alvrs commented Feb 7, 2023

First of all, thanks a lot for your review!

though I'm not sure how useful MudStoreDeleteRecord is, since it's basically like MudStoreSetRecord with all zeros. There doesn't seem to be a built-in has like with components (which makes sense, since this is generic storage and you usually don't waste a slot on an existence flag)

There is a subtle difference: with support for callbacks via the IStoreHook, it's possible to create an "on-chain indexer" to store whether "an entity has a component", because the hooks for setRecord and deleteRecord are different. To make off-chain indexers consistent with this, they have to distinguish between MudStoreSetRecord and MudStoreDeleteRecord.

To summarize about Buffer - imo it currently combines the roles of a static buffer and slice. I'd explore not storing capacity, refactoring it to a Slice, and pairing it with bytes where necessary (and bytes length can be reduced if that's required).

I agree with this, commented above!

Should I wait for merge before moving to gas optimizations and codegen?

I just pushed fixes for the comments marked as resolved, and think we should work on the unresolved ones in follow up PRs.
Happy to merge into v2 tomorrow if you agree, so you can get started on optimization and autogen.

@dk1a
Copy link
Contributor

dk1a commented Feb 7, 2023

There is a subtle difference: with support for callbacks via the IStoreHook, it's possible to create an "on-chain indexer" to store whether "an entity has a component", because the hooks for setRecord and deleteRecord are different. To make off-chain indexers consistent with this, they have to distinguish between MudStoreSetRecord and MudStoreDeleteRecord.

Right, makes sense. Later I'd been thinking it might have to do with indexers, hadn't thought about hooks.

Mind changing this in a follow up PR?

Sure, especially with Buffer comments I was expecting to do the changes myself, it overlaps with gas stuff too.

Happy to merge into v2 tomorrow if you agree, so you can get started on optimization and autogen.

lgtm

dk1a
dk1a previously approved these changes Feb 7, 2023
@alvrs alvrs changed the base branch from v2 to main February 7, 2023 15:29
@alvrs alvrs dismissed dk1a’s stale review February 7, 2023 15:29

The base branch was changed.

@alvrs alvrs changed the base branch from main to v2 February 7, 2023 15:32
@alvrs alvrs merged commit 9ed6f2a into v2 Feb 7, 2023
@alvrs alvrs deleted the alvrs/datamodel branch February 7, 2023 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants