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

docs: update Cardinal documentation with latest features #820

Closed
106 changes: 96 additions & 10 deletions docs/cardinal/game/component.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: 'How to define and register a component'
If you are unfamiliar with Entity Component System (ECS), we recommend reading [Introduction to ECS](/cardinal/ecs) before proceeding.
</Warning>

Components are data attributes attached to an entity. They define the properties of an entity, but it does not implement logic or behavior.
Components are data attributes attached to an entity. They define the properties of an entity, but it does not implement logic or behavior.

In Cardinal, components are defined as Go structs.

Expand All @@ -19,47 +19,133 @@ In Cardinal, components are defined as Go structs.

## Defining Components

By convention, components are defined in the `component` directory in its own separate files.
By convention, components are defined in the `component` directory in its own separate files.

You can easily create a new component and register it to the world by following these steps:

<Steps>
<Step title="Define the component struct">
A component is defined as a Go struct. It must implement the `Name()` method which returns a unique name of the component. This is used to identify the component in the world.

```go /component/component_health.go
package component

type Health struct {
Current int,
Max int
Current int `json:"current" validate:"gte=0,lte=maxHealth"`
Max int `json:"max" validate:"required,gt=0"`
}

func (Health) Name() string {
return "Health"
}

func (Health) DefaultValue() Health {
return Health{
Current: 100,
Max: 100,
}
}
```
</Step>
<Step title="Register the component in main.go">
Components must be registered in the world before they can be used. This is done by calling the `RegisterComponent` function.

```go main.go
package main

func main() {
w, err := cardinal.NewWorld()
if err != nil {
log.Fatal().Err(err).Msg("failed to create world")
}

// Register components
err := cardinal.RegisterComponent[component.Health](w)
if err != nil {
log.Fatal().Err(err).Msg("failed to register component")
}

// ...
}
```
</Step>
</Steps>

## Schema Validation

Components support field validation using struct tags. Cardinal uses the `validate` tag to enforce constraints on component fields.

```go
type Inventory struct {
Capacity int `validate:"required,gt=0,lte=100"`
Owner string `validate:"required,min=3,max=32"`
Items []Item `validate:"dive,required"`
}
```

Common validation tags:
- `required`: Field must not be zero value
- `gt`, `gte`: Greater than (or equal)
- `lt`, `lte`: Less than (or equal)
- `min`, `max`: Minimum/maximum length for strings
- `dive`: Validate slice elements
- `oneof`: Value must be one of the specified values

<Note>
Validation occurs when components are created or updated. Invalid values will result in errors.
</Note>

## Default Values

Components can provide default values by implementing the optional `DefaultValue()` method:

```go
type Position struct {
X float64
Y float64
Z float64
}

func (Position) Name() string {
return "Position"
}

func (Position) DefaultValue() Position {
return Position{
X: 0,
Y: 0,
Z: 0,
}
}
```

Default values are used when:
- Creating new entities without specifying component values
- Resetting components to their initial state
- Providing baseline values for game objects

<Warning>
Default values should be immutable and deterministic to ensure consistent game state.
</Warning>

## Best Practices

1. **Validation**
- Use meaningful validation rules
- Document validation constraints
- Handle validation errors gracefully

2. **Default Values**
- Keep defaults simple and predictable
- Consider game balance when setting defaults
- Document default value behavior

3. **Component Design**
- Keep components focused and minimal
- Use appropriate data types
- Consider serialization implications

## Related Documentation
- [Entity Management](/cardinal/game/system/entity)
- [System Implementation](/cardinal/game/system)
- [Game State Management](/cardinal/game/world/overview)
131 changes: 121 additions & 10 deletions docs/cardinal/game/query.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ You can easily create a new query and register it to the world by following thes
type PlayerHealthResponse struct {
HP int
}

func PlayerHealth(world cardinal.WorldContext, req *PlayerHealthRequest) (*PlayerHealthResponse, error) {
// Handle PlayerHealthRequest -> PlayerHealthResponse here
}
Expand Down Expand Up @@ -75,28 +75,139 @@ You can easily create a new query and register it to the world by following thes
</Step>
</Steps>

---
---

## Query Options

### EVM Support

Queries can be sent by EVM smart contracts by using the `WithQueryEVMSupport` option when you register your queries. This will generate the ABI types necessary for interactions with smart contracts.
### Custom Query Groups

Queries can be organized into logical groups using the `WithCustomQueryGroup` option. This helps organize related queries and enables group-specific handling.

```go
import (
"pkg.world.dev/world-engine/cardinal"
qry "pkg.world.dev/world-engine/cardinal/query"
"github.com/argus-labs/starter-game-template/cardinal/query"
)

cardinal.RegisterQuery[query.PlayerHealthRequest, query.PlayerHealthResponse](w, "player-health",
qry.WithQueryEVMSupport[query.PlayerHealthRequest, query.PlayerHealthResponse]())
// Register query in the "player" group
err := cardinal.RegisterQuery[PlayerHealthRequest, PlayerHealthResponse](
w,
"player-health",
PlayerHealth,
qry.WithCustomQueryGroup[PlayerHealthRequest, PlayerHealthResponse]("player"),
)
```

Query groups can be used for:
- API organization
- Access control
- Rate limiting
- Metrics and monitoring

### EVM Support

Queries can be accessed by EVM smart contracts using the `WithQueryEVMSupport` option. This generates the necessary ABI types for smart contract interaction.

```go
// Register query with EVM support
err := cardinal.RegisterQuery[PlayerHealthRequest, PlayerHealthResponse](
w,
"player-health",
PlayerHealth,
qry.WithQueryEVMSupport[PlayerHealthRequest, PlayerHealthResponse](),
)
```

#### Supported Types for EVM Queries

<Note>
Not all Go types are supported for the fields in your query structs when using this option. See [EVM+ Message and Query](/cardinal/game/evm) to learn more.
When using EVM support, query struct fields must use EVM-compatible types:
</Note>

---
```go
// Supported types
type EVMCompatibleQuery struct {
IntValue int64
UintValue uint64
BoolValue bool
StringValue string
BytesValue []byte
Address common.Address
}
```

#### Smart Contract Integration

```solidity
// Solidity interface for the query
interface IPlayerHealth {
struct PlayerHealthRequest {
string nickname;
}

struct PlayerHealthResponse {
uint256 hp;
}

function PlayerHealth(PlayerHealthRequest calldata request)
external view returns (PlayerHealthResponse memory);
}
```

### Multiple Options

You can combine multiple query options:

```go
err := cardinal.RegisterQuery[PlayerHealthRequest, PlayerHealthResponse](
w,
"player-health",
PlayerHealth,
qry.WithQueryEVMSupport[PlayerHealthRequest, PlayerHealthResponse](),
qry.WithCustomQueryGroup[PlayerHealthRequest, PlayerHealthResponse]("player"),
)
```

## Best Practices

1. **Query Organization**
- Use meaningful group names
- Keep related queries in the same group
- Document group purposes

2. **EVM Integration**
- Test EVM queries thoroughly
- Handle type conversions carefully
- Document ABI interfaces

3. **Performance**
- Keep queries focused and efficient
- Consider caching strategies
- Monitor query latency

4. **Error Handling**
- Return meaningful error messages
- Validate input parameters
- Handle edge cases

## Common Pitfalls

1. **EVM Type Mismatches**
- Using unsupported types in EVM queries
- Incorrect type mappings
- Missing type conversions

2. **Query Group Management**
- Inconsistent group naming
- Missing group documentation
- Unclear group purposes

3. **Performance Issues**
- Inefficient query implementations
- Missing indexes
- Unnecessary data fetching

## Related Documentation
- [EVM Integration](/cardinal/game/evm)
- [Query Language](/cardinal/game/cql)
- [System Overview](/cardinal/game/system/overview)
Loading
Loading