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
5 changes: 5 additions & 0 deletions .changeset/cyan-singers-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@jolly-pixel/voxel.renderer": minor
---

Refactor hooks to have type-safe metadata
66 changes: 26 additions & 40 deletions packages/voxel-renderer/docs/Hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,50 +13,36 @@ import {
function onLayerUpdated(
event: VoxelLayerHookEvent
): void {
// Send information over the network
console.log(event);
// Narrow on `action` to get a fully-typed `metadata`.
if (event.action === "voxel-set") {
console.log(event.metadata.position, event.metadata.blockId);
}
}

const vr = new VoxelRenderer({
onLayerUpdated,
});
```

## Types

```ts
export type VoxelLayerHookAction =
// Voxel-layer actions
| "added" // a voxel layer was created
| "removed" // a voxel layer was deleted
| "updated" // layer properties (visibility, …) changed
| "offset-updated" // layer world offset changed
| "voxel-set" // a voxel was placed in a layer
| "voxel-removed" // a voxel was removed from a layer
| "reordered" // layer render order changed
// Object-layer actions
| "object-layer-added" // a new object layer was created
| "object-layer-removed" // an object layer was deleted
| "object-layer-updated" // object layer properties (e.g. visibility) changed
| "object-added" // an object was added to an object layer
| "object-removed" // an object was removed from an object layer
| "object-updated"; // an object's properties were patched

// Describes a change related to a layer.
export interface VoxelLayerHookEvent {
// The name of the affected layer.
layerName: string;

// The action that occurred on the layer.
action: VoxelLayerHookAction;

// Additional data related to the action.
// Use `unknown` to encourage callers
// to validate the payload before use.
metadata: Record<string, unknown>;
}

export type VoxelLayerHookListener = (
event: VoxelLayerHookEvent
) => void;
```
## Event reference

`VoxelLayerHookEvent` is a discriminated union keyed on `action`. Narrowing on `action`
gives you a precise `metadata` type with no casting required.

| `action` | `metadata` shape |
|---|---|
| `"added"` | `{ options: VoxelLayerConfigurableOptions }` |
| `"removed"` | `{}` |
| `"updated"` | `{ options: Partial<VoxelLayerConfigurableOptions> }` |
| `"offset-updated"` | `{ offset: VoxelCoord }` or `{ delta: VoxelCoord }` |
| `"voxel-set"` | `{ position, blockId, rotation, flipX, flipZ, flipY }` |
| `"voxel-removed"` | `{ position: Vector3Like }` |
| `"reordered"` | `{ direction: "up" \| "down" }` |
| `"object-layer-added"` | `{}` |
| `"object-layer-removed"` | `{}` |
| `"object-layer-updated"` | `{ patch: { visible?: boolean } }` |
| `"object-added"` | `{ objectId: string }` |
| `"object-removed"` | `{ objectId: string }` |
| `"object-updated"` | `{ objectId: string; patch: Partial<VoxelObjectJSON> }` |

`VoxelLayerHookAction` is a convenience alias for `VoxelLayerHookEvent["action"]`.
122 changes: 103 additions & 19 deletions packages/voxel-renderer/src/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,107 @@
export type VoxelLayerHookAction =
| "added"
| "removed"
| "updated"
| "offset-updated"
| "voxel-set"
| "voxel-removed"
| "reordered"
| "object-layer-added"
| "object-layer-removed"
| "object-layer-updated"
| "object-added"
| "object-removed"
| "object-updated";
// Import Third-party Dependencies
import type { Vector3Like } from "three";

// Import Internal Dependencies
import type { VoxelLayerConfigurableOptions } from "./world/VoxelLayer.ts";
import type { VoxelCoord } from "./world/types.ts";
import type {
VoxelObjectLayerJSON,
VoxelObjectJSON
} from "./serialization/VoxelSerializer.ts";

export type VoxelLayerHookEvent =
| {
action: "added";
layerName: string;
metadata: {
options: VoxelLayerConfigurableOptions;
};
}
| {
action: "removed";
layerName: string;
metadata: Record<string, never>;
}
| {
action: "updated";
layerName: string;
metadata: {
options: Partial<VoxelLayerConfigurableOptions>;
};
}
| {
action: "offset-updated";
layerName: string;
metadata: { offset: VoxelCoord; } | { delta: VoxelCoord; };
}
| {
action: "voxel-set";
layerName: string;
metadata: {
position: Vector3Like;
blockId: number;
rotation: number;
flipX: boolean;
flipZ: boolean;
flipY: boolean;
};
}
| {
action: "voxel-removed";
layerName: string;
metadata: {
position: Vector3Like;
};
}
| {
action: "reordered";
layerName: string;
metadata: {
direction: "up" | "down";
};
}
| {
action: "object-layer-added";
layerName: string;
metadata: Record<string, never>;
}
| {
action: "object-layer-removed";
layerName: string;
metadata: Record<string, never>;
}
| {
action: "object-layer-updated";
layerName: string;
metadata: {
patch: Partial<Pick<VoxelObjectLayerJSON, "visible">>;
};
}
| {
action: "object-added";
layerName: string;
metadata: {
objectId: string;
};
}
| {
action: "object-removed";
layerName: string;
metadata: {
objectId: string;
};
}
| {
action: "object-updated";
layerName: string;
metadata: {
objectId: string;
patch: Partial<VoxelObjectJSON>;
};
};

export type VoxelLayerHookAction = VoxelLayerHookEvent["action"];

export interface VoxelLayerHookEvent {
layerName: string;
action: VoxelLayerHookAction;
metadata: Record<string, any>;
}
export type VoxelLayerHookListener = (
event: VoxelLayerHookEvent
) => void;
Loading