-
Notifications
You must be signed in to change notification settings - Fork 194
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
59 changed files
with
11,924 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
cache | ||
out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
17 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"printWidth": 120, | ||
"semi": true, | ||
"tabWidth": 2, | ||
"useTabs": false, | ||
"bracketSpacing": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"extends": "solhint:recommended", | ||
"rules": { | ||
"compiler-version": ["error", ">=0.8.0"], | ||
"avoid-low-level-calls": "off" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# @mud/solecs | ||
|
||
Solidity Entity Component System |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[default] | ||
ffi = false | ||
fuzz_runs = 256 | ||
optimizer = true | ||
optimizer_runs = 1000000 | ||
verbosity = 1 | ||
libs = ["node_modules"] | ||
src = "src" | ||
out = "out" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#! usr/bin/bash | ||
giturl=https://github.com/$1.git | ||
head=$(git ls-remote $giturl HEAD | head -n1 | awk '{print $1;}') | ||
yarn add $giturl#$head | ||
echo "Installed $giturl#$head" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
"name": "@mud/solecs", | ||
"license": "MIT", | ||
"version": "0.0.1", | ||
"description": "Solidity Entity Component System", | ||
"types": "./types/ethers-contracts/", | ||
"scripts": { | ||
"prepare": "chmod u+x git-install.sh", | ||
"git:install": "bash git-install.sh", | ||
"test": "forge test", | ||
"build": "rimraf out && forge build && yarn dist:abi && yarn types", | ||
"dist:abi": "rimraf abi && mkdir abi && cp out/**/*.json abi/", | ||
"types": "rimraf types && typechain --target=ethers-v5 abi/*.json", | ||
"prettier": "prettier 'src/**/*.sol'", | ||
"solhint": "solhint --config ./.solhint.json 'src/**/*.sol'", | ||
"lint": "yarn prettier && yarn solhint" | ||
}, | ||
"devDependencies": { | ||
"@typechain/ethers-v5": "^9.0.0", | ||
"prettier-plugin-solidity": "^1.0.0-beta.19", | ||
"rimraf": "^3.0.2", | ||
"solhint": "^3.3.7", | ||
"typechain": "^8.0.0" | ||
}, | ||
"dependencies": { | ||
"@rari-capital/solmate": "https://github.com/Rari-Capital/solmate.git#b6ae78e6ff490f8fec7695c7b65d06e5614f1b65", | ||
"ds-test": "https://github.com/dapphub/ds-test.git#c7a36fb236f298e04edf28e2fee385b80f53945f", | ||
"forge-std": "https://github.com/foundry-rs/forge-std.git#37a3fe48c3a4d8239cda93445f0b5e76b1507436", | ||
"memmove": "https://github.com/brockelmore/memmove.git#d577ecd1bc43656f4032edf4daa9797f756a8ad2", | ||
"openzeppelin-solidity": "https://github.com/OpenZeppelin/openzeppelin-contracts.git#cd2da98d4d21f070f56476b40042279f5500fc59" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
ds-test/=node_modules/ds-test/src/ | ||
forge-std/=node_modules/forge-std/src/ | ||
@openzeppelin/=node_modules/openzeppelin-solidity/ | ||
memmove/=node_modules/memmove/src/ | ||
solmate=node_modules/@rari-capital/solmate/src/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// SPDX-License-Identifier: Unlicense | ||
pragma solidity >=0.8.0; | ||
|
||
import '@openzeppelin/contracts/utils/introspection/ERC165Checker.sol'; | ||
|
||
import { IEntityIndexer } from './interfaces/IEntityIndexer.sol'; | ||
|
||
import { Set } from './Set.sol'; | ||
import { World } from './World.sol'; | ||
|
||
abstract contract Component { | ||
address public world; | ||
address public owner; | ||
|
||
Set private entities; | ||
mapping(bytes => address) private valueToEntities; | ||
mapping(uint256 => bytes) private entityToValue; | ||
IEntityIndexer[] internal indexers; | ||
|
||
constructor(address _world) { | ||
world = _world; | ||
World(_world).registerComponent(address(this), getID()); | ||
entities = new Set(); | ||
owner = msg.sender; | ||
} | ||
|
||
modifier onlyContractOwner() { | ||
require(msg.sender == owner, 'ONLY_CONTRACT_OWNER'); | ||
_; | ||
} | ||
|
||
function transferOwnership(address newOwner) public onlyContractOwner { | ||
owner = newOwner; | ||
} | ||
|
||
function getID() public pure virtual returns (uint256); | ||
|
||
function set(uint256 entity, bytes memory value) public onlyContractOwner { | ||
// Store the entity | ||
entities.add(entity); | ||
|
||
// Store the entity's value; | ||
entityToValue[entity] = value; | ||
|
||
// Store the reverse mapping | ||
if (valueToEntities[value] == address(0)) { | ||
valueToEntities[value] = address(new Set()); | ||
} | ||
Set(valueToEntities[value]).add(entity); | ||
|
||
for (uint256 i = 0; i < indexers.length; i++) { | ||
indexers[i].update(entity, value); | ||
} | ||
|
||
// Emit global event | ||
World(world).registerComponentValueSet(address(this), entity, value); | ||
} | ||
|
||
function remove(uint256 entity) public onlyContractOwner { | ||
// if there is no entity with this value, return | ||
if (valueToEntities[entityToValue[entity]] == address(0)) return; | ||
|
||
// Remove the entity from the reverse mapping | ||
Set(valueToEntities[entityToValue[entity]]).remove(entity); | ||
|
||
// Remove the entity from the entity list | ||
entities.remove(entity); | ||
|
||
// Remove the entity from the mapping | ||
delete entityToValue[entity]; | ||
|
||
for (uint256 i = 0; i < indexers.length; i++) { | ||
indexers[i].remove(entity); | ||
} | ||
|
||
// Emit global event | ||
World(world).registerComponentValueRemoved(address(this), entity); | ||
} | ||
|
||
function has(uint256 entity) public view returns (bool) { | ||
return entities.has(entity); | ||
} | ||
|
||
function getRawValue(uint256 entity) public view returns (bytes memory) { | ||
// Return the entity's component value | ||
return entityToValue[entity]; | ||
} | ||
|
||
function getEntities() public view returns (uint256[] memory) { | ||
return entities.getItems(); | ||
} | ||
|
||
function getEntitiesWithValue(bytes memory value) public view returns (uint256[] memory) { | ||
if (valueToEntities[value] == address(0)) { | ||
return new uint256[](0); | ||
} | ||
|
||
// Return all entities with this component value | ||
return Set(valueToEntities[value]).getItems(); | ||
} | ||
|
||
function registerIndexer(address indexer) external onlyContractOwner { | ||
require( | ||
ERC165Checker.supportsInterface(indexer, type(IEntityIndexer).interfaceId), | ||
'Given address is not an indexer.' | ||
); | ||
indexers.push(IEntityIndexer(indexer)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
// SPDX-License-Identifier: Unlicense | ||
pragma solidity >=0.8.0; | ||
|
||
import { IERC165 } from './interfaces/IERC165.sol'; | ||
import { IEntityContainer } from './interfaces/IEntityContainer.sol'; | ||
import { IEntityIndexer } from './interfaces/IEntityIndexer.sol'; | ||
|
||
import { Set } from './Set.sol'; | ||
import { Component } from './Component.sol'; | ||
|
||
contract Indexer is IEntityIndexer, IERC165 { | ||
Set private entities; | ||
mapping(bytes => address) internal valueToEntities; | ||
mapping(uint256 => bytes) internal entityToValue; | ||
Component[] private components; | ||
bool[] private trackValueForComponent; | ||
|
||
constructor(Component[] memory _components, bool[] memory _trackValueForComponent) { | ||
components = _components; | ||
trackValueForComponent = _trackValueForComponent; | ||
entities = new Set(); | ||
} | ||
|
||
function update(uint256 entity, bytes memory value) external { | ||
Component component = Component(msg.sender); | ||
bytes[] memory values = new bytes[](components.length); | ||
uint256 index; | ||
bool foundSenderComponent; | ||
|
||
for (uint256 i; i < components.length; i++) { | ||
if (components[i] == component) { | ||
foundSenderComponent = true; | ||
} | ||
} | ||
|
||
require(foundSenderComponent, 'Message Sender is not indexed!'); | ||
|
||
// If data for entity doesn't exist yet create it | ||
if (entityToValue[entity].length == 0) { | ||
// Get all component values from ECS | ||
for (uint256 i = 0; i < components.length; i++) { | ||
// Get ECS Value (or set new value) | ||
values[i] = components[i] == component ? value : components[i].getRawValue(entity); | ||
|
||
// If one of the tracked values does not exist yet we can't index | ||
if (values[i].length == 0) { | ||
return; | ||
} | ||
|
||
// Ignore non tracked values | ||
if (!trackValueForComponent[i]) { | ||
values[i] = new bytes(0); | ||
} | ||
} | ||
|
||
_set(entity, values); | ||
return; | ||
} | ||
|
||
// Get index of current component | ||
for (uint256 i; i < components.length; i++) { | ||
if (components[i] == component) { | ||
index = i; | ||
} | ||
} | ||
|
||
values = abi.decode(entityToValue[entity], (bytes[])); | ||
// Set value at index | ||
values[index] = value; | ||
|
||
// Remove old value | ||
Set(valueToEntities[entityToValue[entity]]).remove(entity); | ||
|
||
// Add new value | ||
bytes memory indexerValue = abi.encode(values); | ||
|
||
entityToValue[entity] = indexerValue; | ||
|
||
// Store the reverse mapping | ||
if (valueToEntities[indexerValue] == address(0)) { | ||
valueToEntities[indexerValue] = address(new Set()); | ||
} | ||
Set(valueToEntities[indexerValue]).add(entity); | ||
} | ||
|
||
function remove(uint256 entity) external { | ||
Component component = Component(msg.sender); | ||
bool foundSenderComponent; | ||
|
||
for (uint256 i; i < components.length; i++) { | ||
if (components[i] == component) { | ||
foundSenderComponent = true; | ||
} | ||
} | ||
|
||
require(foundSenderComponent, 'Message Sender is not indexed!'); | ||
|
||
// if there is no entity with this value, return | ||
if (!has(entity)) return; | ||
|
||
// Remove the entity from the reverse mapping | ||
Set(valueToEntities[entityToValue[entity]]).remove(entity); | ||
|
||
// Remove the entity from the entity list | ||
entities.remove(entity); | ||
|
||
// Remove the entity from the mapping | ||
delete entityToValue[entity]; | ||
} | ||
|
||
function supportsInterface(bytes4 interfaceId) external view returns (bool) { | ||
return interfaceId == type(IERC165).interfaceId || interfaceId == type(IEntityIndexer).interfaceId; | ||
} | ||
|
||
function getEntities() external view returns (uint256[] memory) { | ||
return entities.getItems(); | ||
} | ||
|
||
function getEntitiesWithValue(bytes memory value) external view returns (uint256[] memory) { | ||
if (valueToEntities[value] == address(0)) { | ||
return new uint256[](0); | ||
} | ||
|
||
// Return all entities with this component value | ||
return Set(valueToEntities[value]).getItems(); | ||
} | ||
|
||
function has(uint256 entity) public view returns (bool) { | ||
return entities.has(entity); | ||
} | ||
|
||
function _set(uint256 entity, bytes[] memory newValues) internal { | ||
require(newValues.length == components.length, 'Need values for all components.'); | ||
|
||
// Store the entity | ||
entities.add(entity); | ||
|
||
bytes memory indexerValue = abi.encode(newValues); | ||
|
||
// Store the entity's value; | ||
entityToValue[entity] = indexerValue; | ||
|
||
// Store the reverse mapping | ||
if (valueToEntities[indexerValue] == address(0)) { | ||
valueToEntities[indexerValue] = address(new Set()); | ||
} | ||
|
||
Set(valueToEntities[indexerValue]).add(entity); | ||
} | ||
} |
Oops, something went wrong.