wip: separate package

This commit is contained in:
syuilo 2026-05-25 22:17:58 +09:00
commit 71bb388b8b
263 changed files with 731 additions and 204 deletions

View file

@ -16,6 +16,8 @@ on:
- packages/misskey-js/**
- packages/misskey-bubble-game/**
- packages/misskey-reversi/**
- packages/misskey-world/**
- packages/frontend-misskey-world-engine/**
- packages/shared/eslint.config.js
- .github/workflows/lint.yml
pull_request:
@ -30,6 +32,8 @@ on:
- packages/misskey-js/**
- packages/misskey-bubble-game/**
- packages/misskey-reversi/**
- packages/misskey-world/**
- packages/frontend-misskey-world-engine/**
- packages/shared/eslint.config.js
- .github/workflows/lint.yml
jobs:
@ -65,6 +69,8 @@ jobs:
- misskey-js
- misskey-bubble-game
- misskey-reversi
- misskey-world
- frontend-misskey-world-engine
env:
eslint-cache-version: v1
eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }}

View file

@ -30,6 +30,8 @@ COPY --link ["packages/sw/package.json", "./packages/sw/"]
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"]
COPY --link ["packages/misskey-bubble-game/package.json", "./packages/misskey-bubble-game/"]
COPY --link ["packages/misskey-world/package.json", "./packages/misskey-world/"]
COPY --link ["packages/frontend-misskey-world-engine/package.json", "./packages/frontend-misskey-world-engine/"]
ARG NODE_ENV=production
@ -61,6 +63,8 @@ COPY --link ["packages/backend/package.json", "./packages/backend/"]
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"]
COPY --link ["packages/misskey-bubble-game/package.json", "./packages/misskey-bubble-game/"]
COPY --link ["packages/misskey-world/package.json", "./packages/misskey-world/"]
COPY --link ["packages/frontend-misskey-world-engine/package.json", "./packages/frontend-misskey-world-engine/"]
ARG NODE_ENV=production
@ -99,12 +103,18 @@ COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/nod
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-js/node_modules ./packages/misskey-js/node_modules
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-reversi/node_modules ./packages/misskey-reversi/node_modules
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-bubble-game/node_modules ./packages/misskey-bubble-game/node_modules
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-world/node_modules ./packages/misskey-world/node_modules
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/frontend-misskey-world-engine/node_modules ./packages/frontend-misskey-world-engine/node_modules
COPY --chown=misskey:misskey --from=native-builder /misskey/built ./built
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-js/built ./packages/misskey-js/built
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-reversi/built ./packages/misskey-reversi/built
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-bubble-game/built ./packages/misskey-bubble-game/built
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/i18n/built ./packages/i18n/built
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-world/built ./packages/misskey-world/built
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/frontend-misskey-world-engine/built ./packages/frontend-misskey-world-engine/built
COPY --chown=misskey:misskey . ./
ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so

View file

@ -12,7 +12,9 @@
"packages/i18n",
"packages/misskey-reversi",
"packages/misskey-bubble-game",
"packages/misskey-world",
"packages/icons-subsetter",
"packages/frontend-misskey-world-engine",
"packages/frontend-shared",
"packages/frontend-builder",
"packages/sw",

View file

@ -0,0 +1,5 @@
# frontend用Misskey Worldエンジン
エンジンはWeb Worker内で動作し、ほぼすべてのMisskey Webの機能は使えないため、意図しないそれらへの参照/依存が原理的に発生しないように別パッケージとする
ただしヘッドレス動作することは(今のところ)意図していない

View file

@ -0,0 +1,28 @@
import tsParser from '@typescript-eslint/parser';
import sharedConfig from '../shared/eslint.config.js';
// eslint-disable-next-line import/no-default-export
export default [
...sharedConfig,
{
ignores: [
'**/node_modules',
'built',
'coverage',
'jest.config.ts',
'test',
'test-d',
],
},
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parserOptions: {
parser: tsParser,
project: ['./tsconfig.json'],
sourceType: 'module',
tsconfigRootDir: import.meta.dirname,
},
},
},
];

View file

@ -0,0 +1,29 @@
{
"type": "module",
"name": "frontend-misskey-world-engine",
"private": true,
"scripts": {
"eslint": "eslint './**/*.{js,jsx,ts,tsx}'",
"typecheck": "tsgo --noEmit",
"lint": "pnpm typecheck && pnpm eslint"
},
"devDependencies": {
"@types/seedrandom": "3.0.8",
"@typescript-eslint/eslint-plugin": "8.59.2",
"@typescript-eslint/parser": "8.59.2",
"esbuild": "0.28.0",
"execa": "9.6.1",
"nodemon": "3.1.14"
},
"files": [
"built"
],
"dependencies": {
"@babylonjs/core": "9.9.0",
"@babylonjs/inspector": "9.9.0",
"@babylonjs/loaders": "9.9.0",
"@babylonjs/materials": "9.9.0",
"eventemitter3": "5.0.4",
"seedrandom": "3.0.5"
}
}

View file

@ -8,6 +8,7 @@ import EventEmitter from 'eventemitter3';
export type EngineBaseEvents = {
'loadingProgress': (ctx: { progress: number }) => void;
'contextlost': (ctx: { reason: string; message: string; }) => void;
};
export abstract class EngineBase<EVs extends EngineBaseEvents> extends EventEmitter<{
@ -38,6 +39,18 @@ export abstract class EngineBase<EVs extends EngineBaseEvents> extends EventEmit
this.fps = options.fps;
this.engine = options.engine;
// doNotHandleContextLostがtrueだとそもそも呼ばれない
//babylonEngine.onContextLostObservable.add(() => {
// os.alert({
// type: 'error',
// title: i18n.ts.somethingHappened,
// text: i18n.ts._miWorld.crushed_description,
// });
//});
this.engine._device.lost.then((info) => { // TODO: babylonEngineの内部プロパティに依存しない方法をforumで聞く
this.ev('contextlost', { reason: info.reason, message: info.message }); // transferableじゃないデータが含まれている可能性も考慮してinfoそのままは送らない
});
this.scene = new BABYLON.Scene(this.engine);
}

View file

@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
// structredCloneが遅いため
// SEE: http://var.blog.jp/archives/86038606.html
// あと、Vue RefをIndexedDBに保存しようとしてstructredCloneを使ったらエラーになった
// https://github.com/misskey-dev/misskey/pull/8098#issuecomment-1114144045
export type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | { [key: number]: Cloneable } | { [key: symbol]: Cloneable } | Cloneable[];
export function deepClone<T extends Cloneable>(x: T): T {
if (typeof x === 'object') {
if (x === null) return x;
if (Array.isArray(x)) return x.map(deepClone) as T;
const obj = {} as Record<string | number | symbol, Cloneable>;
for (const [k, v] of Object.entries(x)) {
obj[k] = v === undefined ? undefined : deepClone(v);
}
return obj as T;
} else {
return x;
}
}

View file

@ -12,8 +12,6 @@ import Hls from 'hls.js';
import { RecyvlingTextGrid, Timer, WORLD_SCALE, camelToKebab, cm, createPlaneUvMapper, normalizeUvToSquare, randomRange } from './utility.js';
import { TIME_MAP } from './utility.js';
import { EngineBase } from './EngineBase.js';
import { genId } from '@/utility/id.js';
import { deepClone } from '@/utility/clone.js';
const SNAPSHOT_RENDERING = false; // 実験的
const USE_GLOW = true; // ドローコールが増えて重い

View file

@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
// ランダムな文字列が生成できればなんでも良い(時系列でソートできるなら尚良)が、とりあえずaidの実装を拝借
const TIME2000 = 946684800000;
let counter = Math.floor(Math.random() * 10000);
function getTime(time: number): string {
time = time - TIME2000;
if (time < 0) time = 0;
return time.toString(36).padStart(8, '0');
}
function getNoise(): string {
return counter.toString(36).padStart(2, '0').slice(-2);
}
export function genId(): string {
counter++;
return getTime(Date.now()) + getNoise();
}

View file

@ -389,7 +389,7 @@ export class RoomEngine extends EngineBase<{
if (_DEV_) { // SR状態確認用
const box = BABYLON.MeshBuilder.CreateBox('', { size: cm(10) }, this.scene);
// eslint-disable-next-line no-restricted-globals
setInterval(() => {
box.position = new BABYLON.Vector3(0, Math.random() * cm(10), 0);
}, 10);
@ -1157,7 +1157,7 @@ export class RoomEngine extends EngineBase<{
this.envManager.turnOnRoomLight();
if (!forInit) {
// workerで実行される可能性がある
// eslint-disable-next-line no-restricted-globals
setTimeout(() => {
this.sr.enableSnapshotRendering(); // このメソッドは参照カウント方式な点に留意
}, 10);
@ -1168,7 +1168,7 @@ export class RoomEngine extends EngineBase<{
this.sr.disableSnapshotRendering(); // このメソッドは参照カウント方式な点に留意
this.envManager.turnOffRoomLight();
// workerで実行される可能性がある
// eslint-disable-next-line no-restricted-globals
setTimeout(() => {
this.sr.enableSnapshotRendering(); // このメソッドは参照カウント方式な点に留意
}, 10);
@ -1453,7 +1453,7 @@ export class RoomEngine extends EngineBase<{
this.sr.disableSnapshotRendering();
this.engine.resize(true);
// workerで実行される可能性がある
// eslint-disable-next-line no-restricted-globals
setTimeout(() => {
this.sr.enableSnapshotRendering();
}, 1);

View file

@ -1,4 +1,4 @@
/* eslint-disable id-denylist */
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only

View file

@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { RoomEngine } from './engine.js';
import type { RoomState } from './engine.js';
import type { RoomAttachments } from './utility.js';
//BABYLON.RegisterStandardEngineExtensions();
//BABYLON.RegisterEnginesExtensionsEngineRawTexture();
//BABYLON.RegisterCollisionCoordinator();
export async function createRoomEngine(params: {
roomState: RoomState; roomAttachments: RoomAttachments; canvas: HTMLCanvasElement; options: { antialias: boolean; resolution: number };
}) {
const babylonEngine = new BABYLON.WebGPUEngine(params.canvas, { doNotHandleContextLost: true, powerPreference: 'high-performance', antialias: params.options.antialias });
babylonEngine.compatibilityMode = false;
babylonEngine.enableOfflineSupport = false;
await babylonEngine.initAsync();
if (params.options.resolution === 2) babylonEngine.setHardwareScalingLevel(0.5);
if (params.options.resolution === 0.5) babylonEngine.setHardwareScalingLevel(2);
const engine = new RoomEngine(params.roomState, params.roomAttachments, {
engine: babylonEngine,
...params.options,
});
return engine;
}

View file

@ -114,7 +114,6 @@ import { wireNet } from './objects/wireNet.js';
import { woodRingFloorLamp } from './objects/woodRingFloorLamp.js';
import { woodRingsPendantLight } from './objects/woodRingsPendantLight.js';
import { woodSoundAbsorbingPanel } from './objects/woodSoundAbsorbingPanel.js';
import type { ObjectDef } from './object.js';
export const OBJECT_DEFS = [
a4Case,

View file

@ -8,20 +8,6 @@ import { createPlaneUvMapper } from '../utility.js';
import type { Timer } from '../utility.js';
import type { ModelManager, RoomAttachments } from './utility.js';
// babylonのドメイン知識は持たない
export type RoomStateObject = {
id: string;
type: string;
position: [number, number, number];
rotation: [number, number, number];
options: RawOptions;
/**
* ID
*/
sticky?: string | null;
};
export type RoomObjectInstance<Options = any> = {
onInited?: () => void;
onOptionsUpdated?: <K extends keyof Options, V extends Options[K]>(kv: [K, V]) => void;
@ -33,60 +19,6 @@ export type RoomObjectInstance<Options = any> = {
dispose: () => void;
};
type NumberOptionSchema = {
type: 'number';
min?: number;
max?: number;
step?: number;
};
type BooleanOptionSchema = {
type: 'boolean';
};
type StringOptionSchema = {
type: 'string';
};
type ColorOptionSchema = {
type: 'color';
};
type MaterialOptionSchema = {
type: 'material';
};
type LightOptionSchema = {
type: 'light';
};
type EnumOptionSchema = {
type: 'enum';
enum: {
value: string | number;
}[];
};
type RangeOptionSchema = {
type: 'range';
min: number;
max: number;
step?: number;
};
type ImageOptionSchema = {
type: 'image';
presets: {
value: string;
}[];
};
type SeedOptionSchema = {
type: 'seed';
};
type OptionsSchema = Record<string, NumberOptionSchema | BooleanOptionSchema | StringOptionSchema | ColorOptionSchema | MaterialOptionSchema | LightOptionSchema | EnumOptionSchema | RangeOptionSchema | ImageOptionSchema | SeedOptionSchema>;
export type RawOptions = Record<string, unknown> & {
readonly __brand: unique symbol;
};
@ -96,20 +28,7 @@ export type ConvertedOptions = Record<string, unknown> & {
};
type RawImageValue<Presets extends string = string> = { type: Presets | null | '_custom_'; driveFileId?: string | null; fit?: 'cover' | 'contain' | 'stretch'; };
type GetRawOptionsSchemaValues<T extends OptionsSchema> = {
[K in keyof T]:
T[K] extends NumberOptionSchema ? number :
T[K] extends BooleanOptionSchema ? boolean :
T[K] extends StringOptionSchema ? string :
T[K] extends ColorOptionSchema ? [number, number, number] :
T[K] extends MaterialOptionSchema ? { color: [number, number, number]; metallic: number; roughness: number; } :
T[K] extends LightOptionSchema ? { color: [number, number, number]; brightness: number; } :
T[K] extends EnumOptionSchema ? T[K]['enum'][number]['value'] :
T[K] extends RangeOptionSchema ? number :
T[K] extends ImageOptionSchema ? RawImageValue<T[K]['presets'][number]['value']> :
T[K] extends SeedOptionSchema ? number :
never;
};
type ConvertedImageValue<Presets extends string = string> = { type: Presets | null | '_custom_'; custom?: { url: string; } | null; fit?: 'cover' | 'contain' | 'stretch'; };
type GetConvertedOptionsSchemaValues<T extends OptionsSchema> = {
[K in keyof T]:
@ -146,19 +65,6 @@ export type SnapshotRenderingHelperWrapper = {
fixParticleSystem: (ps: BABYLON.ParticleSystem) => void;
};
export type ObjectSchemaDef<OpSc extends OptionsSchema = OptionsSchema> = {
id: string;
options: {
schema: string extends keyof OpSc ? OptionsSchema : OpSc;
default: string extends keyof OpSc ? RawOptions : GetRawOptionsSchemaValues<OpSc>; // 関数にした方が使用側でdeepCloneの必要がなくて綺麗かもしれない
};
placement: 'top' | 'side' | 'bottom' | 'wall' | 'ceiling' | 'floor';
hasCollisions?: boolean;
hasTexture?: boolean;
canPreMeshesMerging?: boolean; // TODO: 除外するメッシュ名を指定できるようにする
isChair?: boolean;
};
export type ObjectDef<Schema extends ObjectSchemaDef = ObjectSchemaDef> = Schema & {
path?: (options: string extends keyof Schema['options']['schema'] ? ConvertedOptions : Readonly<GetConvertedOptionsSchemaValues<Schema['options']['schema']>>) => string;
createInstance: (args: {
@ -187,14 +93,6 @@ export function defineObject<const Schema extends ObjectSchemaDef<any>>(schema:
return { ...schema, ...def };
}
export function defineObjectClass<const Schema extends ObjectSchemaDef<any>>(baseDef: Partial<ObjectDef<Schema>>): {
extend: (childDef: Partial<ObjectDef<Schema>>) => ObjectDef<Schema>;
} {
return {
extend: (childDef) => ({ ...baseDef, ...childDef }) as ObjectDef<Schema>,
};
}
export function convertRawOptions<OpSc extends OptionsSchema>(schema: OpSc, raw: RawOptions, attachments: RoomAttachments): ConvertedOptions {
const converted = {} as ConvertedOptions;
for (const record of Object.entries(schema)) {

View file

@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { createTextureManager, defineObject } from '../object.js';
import { cm, WORLD_SCALE } from '../../utility.js';
import { cm, WORLD_SCALE } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { getLightRangeFactorByGraphicsQuality } from '../utility.js';
import { allInOnePc_schema } from './allInOnePc.schema.js';

View file

@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../object.js';
import { cm } from '../../utility.js';
import { cm } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { aquarium_schema } from './aquarium.schema.js';
export const aquarium = defineObject(aquarium_schema, {

View file

@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../object.js';
import { cm, WORLD_SCALE } from '../../utility.js';
import { cm, WORLD_SCALE } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { getLightRangeFactorByGraphicsQuality } from '../utility.js';
import { beamLamp_schema } from './beamLamp.schema.js';

View file

@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../object.js';
import { cm, remap } from '../../utility.js';
import { cm, remap } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { createOverridedStates } from '../utility.js';
import { blind_schema } from './blind.schema.js';

View file

@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../object.js';
import { cm } from '../../utility.js';
import { cm } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { books_schema } from './books.schema.js';
export const books = defineObject(books_schema, {

View file

@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { createTextureManager, defineObject } from '../object.js';
import { remap } from '../../utility.js';
import { remap } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { clippedPicture_schema } from './clippedPicture.schema.js';
export const clippedPicture = defineObject(clippedPicture_schema, {

View file

@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../object.js';
import { cm } from '../../utility.js';
import { cm } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { yuge } from '../utility.js';
import { cupNoodle_schema } from './cupNoodle.schema.js';

View file

@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../object.js';
import { cm, WORLD_SCALE } from '../../utility.js';
import { cm, WORLD_SCALE } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { getLightRangeFactorByGraphicsQuality } from '../utility.js';
import { desktopPc_schema } from './desktopPc.schema.js';

View file

@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { createTextureManager, defineObject } from '../object.js';
import { normalizeUvToSquare } from '../../utility.js';
import { normalizeUvToSquare } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { djPlayer_schema } from './djPlayer.schema.js';
export const djPlayer = defineObject(djPlayer_schema, {

View file

@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../object.js';
import { RecyvlingTextGrid } from '../../utility.js';
import { RecyvlingTextGrid } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { electronicDisplayBoard_schema } from './electronicDisplayBoard.schema.js';
export const electronicDisplayBoard = defineObject(electronicDisplayBoard_schema, {

View file

@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { createTextureManager, defineObject } from '../object.js';
import { cm, WORLD_SCALE } from '../../utility.js';
import { cm, WORLD_SCALE } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { getLightRangeFactorByGraphicsQuality } from '../utility.js';
import { laptopPc_schema } from './laptopPc.schema.js';

View file

@ -4,7 +4,7 @@
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../object.js';
import { cm, WORLD_SCALE } from '../../utility.js';
import { cm, WORLD_SCALE } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { getLightRangeFactorByGraphicsQuality } from '../utility.js';
import { lavaLamp_schema } from './lavaLamp.schema.js';

View file

@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { createTextureManager, defineObject } from '../object.js';
import { cm, WORLD_SCALE, normalizeUvToSquare } from '../../utility.js';
import { cm, WORLD_SCALE, normalizeUvToSquare } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { getLightRangeFactorByGraphicsQuality } from '../utility.js';
import { monitor_schema } from './monitor.schema.js';

View file

@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../object.js';
import { cm } from '../../utility.js';
import { cm } from '../../../../../frontend-misskey-world-engine/src/utility.js';
import { yuge } from '../utility.js';
import { mug_schema } from './mug.schema.js';

Some files were not shown because too many files have changed in this diff Show more