From d576c24eaa773ff485391d2e1d77c374ca5bb6c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 03:59:51 +0000 Subject: [PATCH] Add bitmap storage for engine hooks interface Store a stepsBitmap alongside each engine hook address in the same storage slot (EngineHookInstance), mirroring the pattern used for effects. This avoids unnecessary external calls to hooks that don't implement a given lifecycle stage. - Add EngineHookStep enum (OnBattleStart, OnRoundStart, OnRoundEnd, OnBattleEnd) - Add getStepsBitmap() to IEngineHook interface - Add EngineHookInstance struct packing hook address (160 bits) + bitmap (16 bits) - Update BattleConfig to use mapping(uint256 => EngineHookInstance) - Update Engine dispatch to check bitmap before calling each hook stage - Implement getStepsBitmap() in BattleHistory and GachaRegistry https://claude.ai/code/session_01RQwVGB44zTVHRg5s4tmtx3 --- src/Engine.sol | 22 ++++++++++++++++------ src/Enums.sol | 7 +++++++ src/IEngineHook.sol | 4 ++++ src/Structs.sol | 8 +++++++- src/gacha/GachaRegistry.sol | 7 +++++++ src/hooks/BattleHistory.sol | 9 ++++++++- 6 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/Engine.sol b/src/Engine.sol index ca3adc54..c99eb3df 100644 --- a/src/Engine.sol +++ b/src/Engine.sol @@ -268,7 +268,9 @@ contract Engine is IEngine, MappingAllocator { uint256 numHooks = battle.engineHooks.length; if (numHooks > 0) { for (uint256 i; i < numHooks;) { - config.engineHooks[i] = battle.engineHooks[i]; + IEngineHook hook = battle.engineHooks[i]; + config.engineHooks[i].hook = hook; + config.engineHooks[i].stepsBitmap = hook.getStepsBitmap(); unchecked { ++i; } @@ -296,8 +298,10 @@ contract Engine is IEngine, MappingAllocator { } } - for (uint256 i = 0; i < battle.engineHooks.length;) { - battle.engineHooks[i].onBattleStart(battleKey); + for (uint256 i = 0; i < numHooks;) { + if ((config.engineHooks[i].stepsBitmap & (1 << uint8(EngineHookStep.OnBattleStart))) != 0) { + config.engineHooks[i].hook.onBattleStart(battleKey); + } unchecked { ++i; } @@ -388,7 +392,9 @@ contract Engine is IEngine, MappingAllocator { uint256 numHooks = config.engineHooksLength; for (uint256 i = 0; i < numHooks;) { - config.engineHooks[i].onRoundStart(battleKey); + if ((config.engineHooks[i].stepsBitmap & (1 << uint8(EngineHookStep.OnRoundStart))) != 0) { + config.engineHooks[i].hook.onRoundStart(battleKey); + } unchecked { ++i; } @@ -590,7 +596,9 @@ contract Engine is IEngine, MappingAllocator { // Run the round end hooks for (uint256 i = 0; i < numHooks;) { - config.engineHooks[i].onRoundEnd(battleKey); + if ((config.engineHooks[i].stepsBitmap & (1 << uint8(EngineHookStep.OnRoundEnd))) != 0) { + config.engineHooks[i].hook.onRoundEnd(battleKey); + } unchecked { ++i; } @@ -737,7 +745,9 @@ contract Engine is IEngine, MappingAllocator { } for (uint256 i = 0; i < config.engineHooksLength;) { - config.engineHooks[i].onBattleEnd(battleKey); + if ((config.engineHooks[i].stepsBitmap & (1 << uint8(EngineHookStep.OnBattleEnd))) != 0) { + config.engineHooks[i].hook.onBattleEnd(battleKey); + } unchecked { ++i; } diff --git a/src/Enums.sol b/src/Enums.sol index b5754f69..23fb3a52 100644 --- a/src/Enums.sol +++ b/src/Enums.sol @@ -78,3 +78,10 @@ enum ExtraDataType { SelfTeamIndex, OpponentNonKOTeamIndex } + +enum EngineHookStep { + OnBattleStart, + OnRoundStart, + OnRoundEnd, + OnBattleEnd +} diff --git a/src/IEngineHook.sol b/src/IEngineHook.sol index 2ad4ef7d..dd6ada03 100644 --- a/src/IEngineHook.sol +++ b/src/IEngineHook.sol @@ -2,6 +2,10 @@ pragma solidity ^0.8.0; interface IEngineHook { + // Returns pre-computed bitmap of steps this hook runs at (set at deploy time) + // Bit layout: OnBattleStart=0x01, OnRoundStart=0x02, OnRoundEnd=0x04, OnBattleEnd=0x08 + function getStepsBitmap() external view returns (uint16); + function onBattleStart(bytes32 battleKey) external; function onRoundStart(bytes32 battleKey) external; function onRoundEnd(bytes32 battleKey) external; diff --git a/src/Structs.sol b/src/Structs.sol index cfeb8cc5..b72c6748 100644 --- a/src/Structs.sol +++ b/src/Structs.sol @@ -91,7 +91,7 @@ struct BattleConfig { mapping(uint256 => EffectInstance) globalEffects; mapping(uint256 => EffectInstance) p0Effects; mapping(uint256 => EffectInstance) p1Effects; - mapping(uint256 => IEngineHook) engineHooks; + mapping(uint256 => EngineHookInstance) engineHooks; } struct EffectInstance { @@ -101,6 +101,12 @@ struct EffectInstance { bytes32 data; // 256 bits in slot 1 } +struct EngineHookInstance { + IEngineHook hook; // 160 bits (packed with stepsBitmap in slot 0) + uint16 stepsBitmap; // 16 bits - packs with hook in slot 0 (bit i = runs at EngineHookStep(i)) + // 80 bits unused in slot 0 +} + // View struct for getBattle - contains array instead of mapping for memory return struct BattleConfigView { IValidator validator; diff --git a/src/gacha/GachaRegistry.sol b/src/gacha/GachaRegistry.sol index a315bca2..b3fd8567 100644 --- a/src/gacha/GachaRegistry.sol +++ b/src/gacha/GachaRegistry.sol @@ -15,6 +15,9 @@ import {IMoveSet} from "../moves/IMoveSet.sol"; contract GachaRegistry is IMonRegistry, IEngineHook, IOwnableMon, IGachaRNG { using EnumerableSetLib for EnumerableSetLib.Uint256Set; + // Only runs at OnBattleEnd (bit 3 = 0x08) + uint16 public constant STEPS_BITMAP = 0x08; + uint256 public constant INITIAL_ROLLS = 4; uint256 public constant ROLL_COST = 7; uint256 public constant POINTS_PER_WIN = 2; @@ -118,6 +121,10 @@ contract GachaRegistry is IMonRegistry, IEngineHook, IOwnableMon, IGachaRNG { } // IEngineHook implementation + function getStepsBitmap() external pure override returns (uint16) { + return STEPS_BITMAP; + } + function onBattleStart(bytes32 battleKey) external override {} function onRoundStart(bytes32 battleKey) external override {} diff --git a/src/hooks/BattleHistory.sol b/src/hooks/BattleHistory.sol index be905e70..6f90fad8 100644 --- a/src/hooks/BattleHistory.sol +++ b/src/hooks/BattleHistory.sol @@ -11,8 +11,11 @@ import {EnumerableSetLib} from "../lib/EnumerableSetLib.sol"; contract BattleHistory is IEngineHook { using EnumerableSetLib for EnumerableSetLib.AddressSet; + // Only runs at OnBattleEnd (bit 3 = 0x08) + uint16 public constant STEPS_BITMAP = 0x08; + IEngine public immutable engine; - + mapping(address => uint256) private _numBattles; // Mapping from player address to set of all opponents fought @@ -31,6 +34,10 @@ contract BattleHistory is IEngineHook { engine = _engine; } + function getStepsBitmap() external pure override returns (uint16) { + return STEPS_BITMAP; + } + function onBattleStart(bytes32 battleKey) external {} function onRoundStart(bytes32 battleKey) external {}