SDK ガイド
多人数で空間を共有するリアルタイムアプリを、path と entry で書きます。
SDK は作り直し中です
現在は公開していません。このページの例は、予定している公開仕様です。
コンセプト
tablebox は、多人数で空間を共有するリアルタイムアプリケーションの実装基盤です。アプリケーションが定義した entry を、その変更規則に従って参加者間で同期します。対象には、参加者の位置のように毎秒何度も更新される entry を含みます。描画と操作画面は、アプリケーションが実装します。
全員が毎秒何度も更新する空間で、全員の変化を全員へ届けると、一人が受け取る量は参加者数に比例します。tablebox では、参加者が watch で購読の範囲と頻度を選び、受け取る量はそれだけで決まります。関心の高い範囲は細かく、低い範囲は粗く同期すれば、参加者が何人いても一人の負荷は一定に収まります。
基本の概念
- World
- 共有空間です。作成時に rules を固定します。
- path・entry
- path は World の中の場所、entry は path にあるものです。class は entry.State(最新の値)、entry.Stream(追記される record の列。データか、映像・音声)、entry.Blob(バイト列)の3つです。
- rules
- path のパターンごとに、class、書ける人、読める人、schema、索引、寿命、上限を宣言します。
- watch
- prefix に一致する entry のうち、範囲・値・並びで選んだものを、件数と頻度の上限付きで受け取り続けます。
- 利用権
- Node を使う資格です。運営者から開発者、Project、参加者の鍵へと範囲を狭めて委譲され、Node が委譲の連鎖を確かめます。
使用例
World の作成は最初の1回だけで、参加は毎回同じ手順で行います。近くのアバターは 10 Hz で100人、遠くは 1 Hz で1,000人まで受け取るので、この参加者が受け取る更新は毎秒2,000件以下です。
TypeScript
import { createClient, entry, t } from "@tablebox/sdk";
const rules = {
"stage": { class: entry.State, owner: "world", ttl: "none", write: ["creator", "moderator"], export: true },
"avatars/{uid}": {
class: entry.State, write: "{uid}",
schema: { name: t.string(), image: t.blob(), team: t.string(), x: t.number(), y: t.number() },
indexes: [{ grid: ["x", "y"], cell: 20 }, { eq: "team" }],
},
"chat/{room}": { class: entry.Stream, owner: "world", ttl: 3_600_000, create: "creator", write: "any",
window: { count: 200, time: 600_000 }, limits: { rate: 1 } },
"assets/{id}": { class: entry.Blob, owner: "world", ttl: "none", write: "any", export: true, limits: { size: 16_000_000 } },
"voices/{room}/{uid}": { class: entry.Stream, kind: "media", read: "members", list: "{room}", write: "{uid}",
limits: { bitrate: 64_000 } },
};
// rulesの型。createWorld、findWorld、put、get、watchが型引数なしでこの型になる
declare global {
interface Tablebox {
rules: typeof rules;
}
}
// 作成は最初の1回だけ。参加は毎回同じ手順で行う
const client = await createClient({ endpoints, token: (subject) => myServer.token(subject) });
const worldId = await client.createWorld({ rules, idle: 600_000 });
const world = await client.findWorld(worldId).join();
// 自分のアバター。接続している間はSDKが自動で延長する
const me = world.put(`avatars/${world.me}`, new entry.State({ name: "noname", image: null, team: "red", x: 0, y: 0 }));
onMove((x, y) => me.set({ x, y }));
// 近くは細かく、遠くは粗く受け取る
const near = world.watch("avatars/", { area: { follow: `avatars/${world.me}`, range: 20 }, limit: 100, maxHz: 10 }, render);
const far = world.watch("avatars/", { area: { follow: `avatars/${world.me}`, range: 200 }, limit: 1000, maxHz: 1 }, render);
// チャット
world.put("chat/lobby", new entry.Stream()); // 作成者が部屋を作る
(await world.get("chat/lobby"))?.append({ text: "hello" });
// 抜ける
near.close();
far.close();
await world.leave();Console
Console は、開発者の token をブラウザーの鍵でプロジェクトへ委譲します。サーバーのないアプリは、公開 token の入った tablebox.json を組み込み、SDK の publicToken が参加者ごとに15分の token を手元で作ります。会員制のアプリは、サーバーの公開鍵へ委譲したサーバー用設定をアプリのサーバーに置き、ログインした本人の鍵へ token を委譲します。