Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default withMermaid(defineConfig({
"**/PLAN.md",
"**/examples/**",
"bench/**",
"assets/**",
"editors/**",
"studio/**"
],
Expand Down
2 changes: 0 additions & 2 deletions docs/.vitepress/packages.mts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ export const packageGroups: PackageGroup[] = [
text: "Assets",
packages: [
{ dir: "asset", text: "Asset" },
{ dir: "assets/pixel-art", text: "Pixel Art Assets" },
{ dir: "assets/voxel-map", text: "Voxel Map Assets" },
{ dir: "asset-source", text: "Asset Source" },
{ dir: "asset-server", text: "Asset Server" },
{ dir: "event-store", text: "Event Store" }
Expand Down
118 changes: 79 additions & 39 deletions packages/assets/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,57 +1,97 @@
# Asset workspace architecture
# Asset workspaces architecture

Each directory in `packages/assets` owns one editable format. Its `src/asset/` code defines the kind, stored state, codec integration, and asset dependencies. Its `src/network/` code defines the edit protocol and client sync. A format can keep other domain code elsewhere, such as voxel-model's `src/model/`.
Each workspace in this directory owns one or two editable asset kinds. The
server stores and arbitrates them; the editors open and edit them.

Each kind keeps two files in `src/asset/`. `<kind>.ts` holds what an editor may import: the kind constants, the descriptor and the file builders. `<kind>AssetKind.ts` holds what only the server loads: the state, the file decoder and the kind handler. Decoders check stored files with the kind's zod schemas through `SchemaParser` from `@jolly-pixel/network`, and stay out of the client entry point. A malformed file throws `InvalidAssetFileError` from `@jolly-pixel/asset-server`, so the server can tell corrupt content from a handler bug.
## Inside one workspace

```mermaid
flowchart TB
Editor["Editor or client"]
subgraph Server["Server side (package root, ./server)"]
Kind["src/asset<br/>kind handler, state, file codec"]
Arbiter["src/network<br/>command schemas and arbiter"]
end

subgraph Workspace["One asset workspace"]
Asset["src/asset<br/>kind and state"]
Network["src/network<br/>commands and client sync"]
subgraph Client["Browser side (./client)"]
Document["src/model or a renderer<br/>document the editor edits"]
Sync["src/network<br/>room client and sync"]
History["src/history<br/>undo keys"]
end

Core["@jolly-pixel/asset<br/>references and catalog"]
Server["@jolly-pixel/asset-server<br/>rooms and event history"]
Source["@jolly-pixel/asset-source<br/>stored bytes"]
Sync -->|"commands"| Arbiter
Kind --> Document
Sync --> Document
History --> Document
```

- The server and the editor share the same model: the server replays commands
on it, the editor draws it.
- Browser code imports `./client` only; the root pulls in server dependencies.
- Code: `src/asset/`, `src/model/`, `src/network/`, `src/history/`; pixel-art
and voxel-map take their document from the renderer packages instead.

## Where a workspace plugs in

Editor <--> Network
Editor -->|"resolve asset IDs"| Core
Network <--> Server
Asset -->|"register kind handler"| Server
Server -->|"publish records"| Core
Server -->|"read and write"| Source
```mermaid
flowchart TB
Editor["Editor<br/>(editors/*)"]
Workspace["Asset workspace"]
Server["@jolly-pixel/asset-server<br/>catalog, rooms, event history"]
Source["@jolly-pixel/asset-source<br/>files on disk or in memory"]

Editor -->|"opens the asset room"| Workspace
Workspace -->|"registers its kind handler"| Server
Workspace <-->|"commands and snapshots"| Server
Server -->|"writes snapshots"| Source
```

The server owns rooms, the catalog, event history, and persistence. Each asset workspace supplies format-specific state and commands through an asset kind handler. The catalog room handles creation, rename, and deletion; an asset room handles edits to one open asset.
- The catalog room creates, renames and deletes assets; one asset room per
open asset carries its edits.
- An asset keeps its ID when its path or content changes.

## From edit to stored asset

```mermaid
sequenceDiagram
participant Editor
participant Client as Asset client
participant Room as Asset room
participant Store as Event store
participant Source as Asset source

Editor->>Client: edit an asset
Client->>Room: send command
Room->>Room: validate and arbitrate
alt refused or narrowed
Room-->>Client: correction or snapshot to the author
else admitted
Room->>Store: append accepted command
Store-->>Room: appended
Room->>Room: apply command to kind state
Room-->>Client: broadcast command
end
Room->>Store: append later asset.updated
Room->>Source: write serialized snapshot
flowchart TB
Edit["Editor applies the edit locally"]
Room["Asset room arbitrates the command"]
Log["Command appended to the event history"]
Broadcast["Command broadcast to every peer"]
Correct["Author receives a correction or a snapshot"]
Save["Snapshot written to the source after editing stops"]

Edit --> Room
Room -->|"accepted"| Log
Room -->|"refused"| Correct
Log --> Broadcast
Broadcast --> Save
```

The server replays recorded commands to restore live state and writes serialized snapshots after editing. A kind's apply function is the sole writer of its authoritative state; arbitration is committed after the event append succeeds. The author has already applied its command, so a refused, narrowed or unappended command is followed by a message to the author alone: a correction that restores only the refused part when the kind can build one (pixel-art and blockset pixel commands, voxel-map cell writes), otherwise a snapshot.
- Edits show at once; the room has the final word.
- On restart, the server replays the history to rebuild each asset.

## Assets reference each other

```mermaid
flowchart TB
Scene["scene"]
Map["voxelmap"]
Model["voxelmodel"]
Blockset["blockset"]
Animation["voxelanimation"]
PixelArt["pixelart"]
Terrain["terrain"]
Texture["texture (image)"]

Scene -->|"components"| Map
Map --> Blockset
Model --> PixelArt
Model --> Animation
Terrain -->|"material maps"| Texture
```

See the [asset server architecture](../asset-server/ARCHITECTURE.md) for the server contract. [Asset source architecture](../asset-source/ARCHITECTURE.md) describes the storage boundary. Format-specific rules live in the [pixel-art](./pixel-art/ARCHITECTURE.md), [voxel-map](./voxel-map/ARCHITECTURE.md), [voxel-model](./voxel-model/ARCHITECTURE.md), [voxel-animation](./voxel-animation/ARCHITECTURE.md), and [scene](./scene/ARCHITECTURE.md) architecture pages.
- Scene components may reference assets of any kind.
- A reference is an asset ID and kind; the catalog lists each asset's
references as its dependencies.
- Creating a voxel map also creates its blockset; creating a voxel model also
creates its pixel-art texture.
27 changes: 9 additions & 18 deletions packages/assets/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
# Asset workspaces

The packages in this directory define editable asset formats. Each format
provides an asset kind for the server and a client-side way to work with its
content. An asset keeps its ID when its path or content changes; the catalog
connects that ID to its current kind, path, and revision.

See [architecture](./ARCHITECTURE.md) for the common workspace structure,
command flow, and persistence lifecycle.

## Formats in this directory

- [pixel-art](./pixel-art/README.md): pixel-art textures and canvas edits
- [voxel-map](./voxel-map/README.md): voxel worlds with optional pixel-art blockset references
- [voxel-model](./voxel-model/README.md): model trees with a pixel-art texture reference
- [voxel-animation](./voxel-animation/README.md): animation sets of keyframed clips that models link
- [terrain](./terrain/README.md): heightfield terrains loaded chunk by chunk 🚧
Private workspaces that define the editable asset kinds served by
`@jolly-pixel/asset-server` and opened by the editors. The
[architecture](./ARCHITECTURE.md) shows how they fit together.

- [pixel-art](./pixel-art/README.md): pixel-art textures
- [voxel-map](./voxel-map/README.md): voxel worlds and their blocksets
- [voxel-model](./voxel-model/README.md): voxel model trees
- [voxel-animation](./voxel-animation/README.md): animation sets that voxel models link
- [terrain](./terrain/README.md): heightfield terrains 🚧
- [scene](./scene/README.md): actor trees whose components reference other assets 🚧

For the shared meaning of asset ID, kind, reference, record, and catalog, see
the [asset glossary](../asset/GLOSSARY.md).
31 changes: 0 additions & 31 deletions packages/assets/pixel-art/ARCHITECTURE.md

This file was deleted.

69 changes: 19 additions & 50 deletions packages/assets/pixel-art/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,37 @@
<img src="./docs/banner.png" alt="JollyPixel">
</p>

## 💃 Getting Started
## 📌 About

This workspace-private package stores `.pixelart` documents. Add `"@jolly-pixel/asset.pixel-art": "workspace:*"` to another workspace's dependencies.
Private asset workspace for `.pixelart` textures (kind `pixelart`). Textures are edited in `@jolly-pixel/editor.pixel-art` and referenced by voxel maps, voxel models and scenes.

## 👀 Usage example
Browser code imports `@jolly-pixel/asset.pixel-art/client`; the package root and `@jolly-pixel/asset.pixel-art/server` pull in server dependencies.

### Register the kind
## 🚀 Building

```ts
import { pixelArtAssetKind } from "@jolly-pixel/asset.pixel-art";
import { createAssetBackend } from "@jolly-pixel/asset-server";
import { FilesystemAssetSource } from "@jolly-pixel/asset-source";
import * as EventStore from "@jolly-pixel/event-store";

using eventStore = await EventStore.persistence.sqlite(
"./assets/.jollypixel/events.db"
);
await createAssetBackend({
source: new FilesystemAssetSource("./assets"),
eventStore,
handlers: [pixelArtAssetKind({ defaultSize: { x: 32, y: 32 } })]
});
```bash
$ pnpm install
$ pnpm --filter @jolly-pixel/asset.pixel-art build
```

The handler claims `.pixelart` files. `defaultSize` is the size of a texture created without content; it defaults to 32 by 32. A deleted texture keeps its size and loses its pixels and UV regions. The renderer package owns the file codec. Use `MemoryAssetSource` and an in-memory event store for an ephemeral workspace.

### Connect a document

Create the synced document before joining the room so it receives the first snapshot.

```ts
import { Client } from "@jolly-pixel/network/client";
import {
SyncedPixelDocument,
pixelArtRoom
} from "@jolly-pixel/asset.pixel-art/client";

const client = new Client();
const room = pixelArtRoom(client, assetId);
const synced = new SyncedPixelDocument(room);
Editors load the built `dist/` output, so rebuild after a change before running them.

room.join();
await synced.ready;
## 🧪 Tests and checks

// Pass synced.document to PixelArtCanvas or another document consumer.
// On teardown: synced.dispose(); room.leave(); client.destroy();
```bash
$ pnpm --filter @jolly-pixel/asset.pixel-art test
$ pnpm --filter @jolly-pixel/asset.pixel-art lint
$ pnpm --filter @jolly-pixel/asset.pixel-art bench
```

`PixelCollaboration` adds cursors and in-progress previews to a `PixelArtCanvas` built on that document. See the [network API](./docs/network.md) for its constructor and lifecycle.
## Contributors Guide

## 📚 API
If you are a developer **looking to contribute** to the project, you must first read the [CONTRIBUTING][contributing] guide.

- `@jolly-pixel/asset.pixel-art` exports `pixelArtAssetKind`, `PixelArtState`, the `PIXEL_ART_ASSET` descriptor, `ASSET_KINDS` (the kind package, taking `{ defaultSize?, snapshot? }` options), the kind and event constants for server registration, and `pixelArtContentFromPng(png)`, which turns PNG bytes into `.pixelart` content. Build a blank texture with `encodePixelArtFile(createPixelArtFile(size))` from `@jolly-pixel/pixel-draw.renderer`. Loading malformed content throws `InvalidAssetFileError`, with the renderer error as `cause`.
- `@jolly-pixel/asset.pixel-art/client` exports `SyncedPixelDocument`, `PixelSyncClient`, `pixelArtRoom`, `createPixelArtAsset`, `PixelCollaboration`, presence helpers, wire types such as `PixelArtRoom`, and the [history](./docs/history.md) a `PixelArtCanvas` or a host records undo in.
- `@jolly-pixel/asset.pixel-art/server` exports `PixelCommandArbiter`, `applyPixelCommand`, and the command, snapshot and UV layout schemas.
## License

The package root imports server dependencies. Browser code should use the client entry point. The [network API](./docs/network.md) covers commands, snapshots, notices, and presence helpers. [Architecture](./ARCHITECTURE.md) covers replay and conflict handling. The `.pixelart` file format and codec are documented by [pixel-draw.renderer](../../pixel-draw-renderer/docs/serialization/index.md).

## ✨ Contributors guide

Read the [contributing guide](../../../CONTRIBUTING.md) before submitting a change. Run `pnpm --filter @jolly-pixel/asset.pixel-art test` and `pnpm run lint` from the monorepo root.
MIT

## 📃 License
<!-- Reference-style links for DRYness -->

MIT
[contributing]: ../../../CONTRIBUTING.md
81 changes: 0 additions & 81 deletions packages/assets/pixel-art/docs/history.md

This file was deleted.

Loading
Loading