Author: Magicblock; Source: MetaCat
BOLT v0.1 was launched on March 5, 2024. BOLT is an on-chain development framework that simplifies the development of permissionless, composable, and permanently existing games on the blockchain. BOLT's design includes an SVM-compatible acceleration layer that makes the performance of full-chain games comparable to traditional multiplayer game servers without compromising the composability of Solana's global state. In this early version, we will introduce the BOLT CLI and the Entity Component System (ECS), introduce recent updates, and show how to develop a simple on-chain game on Solana.
ECS Mode
While Bolt is more than just an Entity Component System (ECS) framework, we encourage the use of this powerful design pattern to enhance composability. ECS is a way of organizing code and data to enable modular and extensible games, which is a key feature we seek when building on the full chain. As the name implies, in ECS there are: Entities are entities that represent objects in the game world. They are unique identifiers and do not hold any data or behavior, but simply act as containers. Components are basic data structures that can be "attached" to entities. Systems perform game logic or behavior by acting on entities that hold components. This separation of concerns enables a highly flexible modular architecture. You can check out all the benefits of ECS mode at the following link?
https://github.com/SanderMertens/ecs-faq?tab=readme-ov-file#what-is-ecs.
BOLT CLI
BOLT CLI is an extension of the Anchor framework. It includes all the functionality of the popular Solana development framework, as well as a superset of functionality for creating world instances, components, and systems.
Install BOLT CLI
npm install @magicblock-labs/bolt-cli
You can verify the installation of BOLT CLI by:
bolt -h
To initialize a BOLT-based project, run:
bolt init <new-workspace-name>
Components
The example in the folder programs-ecs/components
defines a Position component with x, y, and z coordinates. Remember that components are simple data structures that contain data related to a specific property of an entity. They do not contain any logic or methods.
use bolt_lang::*;declare_id!("Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ");#[component]#[derive(Copy)]pub struct Position {pub x: i64,pub y: i64,pub z: i64,}
Components themselves are programs deployed on the chain.
declare_id!("Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ");
id defines the unique address of the component above Position.
Systems
Systems contain the logic for operating Components. Systems will typically run on all entities that have a specific set of components. The system_movement
example encapsulates the logic for updating a component's Position
.
use bolt_lang::*;use component_position::Position;declare_id!("FSa6qoJXFBR3a7ThQkTAMrC15p6NkchPEjBdd4n6dXxA");#[system]pub mod system_movement {pub fn execute( ctx:Context<Components>,args_p:Vec<u8>)->Result<Components>{ let position = mut ctx.accounts.position; position.x+= 1; Ok(ctx.accounts) } // Define the input com ponents #[system_input] pub struct Components { pub position: Position, }}
Each System implements an execution instruction that is responsible for applying the system logic to any number of components.
The structure marked with the #[system_input] macro specifies the component packages that the system will receive as input.
Executing the instruction returns the modified component, which the World Program updates in the data structure after checking permissions and business logic.
Again, you don't need to care about the underlying blockchain layer by defining CPIs, retrieving IDLs, or anything else. Just define the bundle of components you want your systems to run!
Putting everything together with the World Program
Now that we have a grasp of how components and systems operate, let's create a game instance using the World Program in the TypeScript SDK. The World Program is the entry point for creating world instances, entities, additional components, and executing systems. The SDK provides convenient interfaces and methods for interacting with BOLT.
Installation
To install the Bolt SDK, run the following command:
npm install @magicblock-labs/bolt-sdk --save-dev
Create a world instance (world instance)
const initNewWorld = await InitializeNewWorld({payer: provider.wallet.publicKey, connection: provider.connection,});const tx = new anchor.web3.Transaction().add(createEntityIx); await provider.sendAndConfirm(initNewWorld.transaction);
Add a new entity
const addEntity = await AddEntity({payer:provider.wallet.publicKey, world:initNewWorld.worldPda, connection:provider.connection,}); await provider.sendAndConfirm(addEntity.transaction);
Add Position Component attached to entity
const initComponent = await InitializeComponent({payer:provider.wallet.publicKey, entity:addEntity.entityPda, componentId:positionComponent.programId,}); await provider.sendAndConfirm(initComponent.transaction); left;">Execute the motion system on the position component
const applySystem=await ApplySystem({authority:provider.wallet.publicKey,system:systemMovement.programId,entity: sp;addEntity.entityPda, components: [positionComponent.programId],});const tx = new anchor.web3.Transaction().add(applySystemIx);await provider.sendAndConfirm(applySystem.transaction);
In this simple example, we created an entity Player which contains a Position component with x,y,z coordinates. We can perform movement systems to change its state. Here is the best part, by defining your game data structures using BOLT ECS, you are not only able to reuse existing systems and components, but also easily allow modifications or extensions to your game. Let's consider a slightly more complex movement dynamics, where a velocity component for changing position is defined as:
use bolt_lang::*;declare_id!("CbHEFbSQdRN4Wnoby9r16umnJ1zWbULBHg4yqzGQonU1");#[component]#[derive(Copy)]pub struct Velocity { pub x: i64}
Someone might want to introduce a new ability to move faster. They could do that by simply adding a new system that uses Velocity acting on the Position component.
#[system]pub mod system_apply_velocity { pub fn execute(ctx: Context<Components>, _args: Vec<u8>) -> Result<Components>{ ctx.accounts.position.x += ctx.accounts.velocity.x; sp; Ok(ctx.accounts) } #[system_input] pub struct Components {
Notice how simple this code is. This new system takes as input a position and velocity component and defines the logic for powering up. There is no concept of Solana accounts or CPI, the Agent World program is handling everything here.
With a few lines of code and almost no blockchain knowledge required, we have just introduced a new game behavior!
Summary
BOLT leverages the ECS pattern to enable game developers to create highly modular, efficient, and composable games. Entities act as containers for components, primitive data structures, allowing for dynamic customization without changing the underlying codebase. Systems interact with these components, injecting logic and behavior into game entities. This separation of concerns not only simplifies the development process, but also makes game logic more reusable and enhances the ability to extend and modify games in a permissionless manner after release.