diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 2b7b004..c9cb8a1 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -11,12 +11,14 @@ module.exports = { root: true, env: { browser: true, node: true }, rules: { - 'camelcase': 'off', - 'no-console': 'off', - 'no-unused-vars': ['error', { + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/no-unused-vars': ['error', { 'argsIgnorePattern': '^_', 'varsIgnorePattern': '^_' }], + 'camelcase': 'off', + 'no-cond-assign': 'off', + 'no-console': 'off', 'quotes': ['error', 'single', { 'avoidEscape': true }], diff --git a/.gitignore b/.gitignore index b293725..0b2a147 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /coverage/ +/dist/ /node_modules/ /public/* !/public/Shaders diff --git a/package.json b/package.json index fe870df..422fd8d 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "start:preview": "vite preview", "test": "jest", "test:coverage": "jest --coverage", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "typecheck": "tsc" }, "keywords": [ "world of warcraft", diff --git a/src/Client.ts b/src/Client.ts index 3f6154a..b447b1e 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -1,3 +1,4 @@ +import Device from './gfx/Device'; import WebGL2Device from './gfx/apis/webgl2/WebGL2Device'; import WebGPUDevice from './gfx/apis/webgpu/WebGPUDevice'; import Screen from './gfx/Screen'; @@ -5,9 +6,21 @@ import TextureRegistry from './gfx/TextureRegistry'; import UIContext from './ui/UIContext'; import { fetch } from './utils'; +type ClientOptions = { + api: 'webgl2' | 'webgpu' | string +} + class Client { - constructor(canvas, { api }) { - this.constructor.instance = this; + static instance: Client; + + fetch: typeof fetch; + device: Device; + screen: Screen; + textures: TextureRegistry; + ui: UIContext; + + constructor(canvas: HTMLCanvasElement, { api }: ClientOptions) { + Client.instance = this; this.fetch = fetch; @@ -17,7 +30,7 @@ class Client { this.device = new WebGL2Device(canvas); break; case 'webgpu': - this.device = new WebGPUDevice(canvas); + this.device = new WebGPUDevice(); break; } diff --git a/src/gfx/Color.ts b/src/gfx/Color.ts index be316f7..594f3a6 100644 --- a/src/gfx/Color.ts +++ b/src/gfx/Color.ts @@ -1,4 +1,6 @@ class Color { + value: number; + constructor(value = 0x00000000) { this.value = value; } diff --git a/src/gfx/Device.ts b/src/gfx/Device.ts index 990e458..24f8c9f 100644 --- a/src/gfx/Device.ts +++ b/src/gfx/Device.ts @@ -1,13 +1,13 @@ -import ShaderRegistry from './ShaderRegistry'; +import { ShaderType } from './Shader'; + +abstract class Device { + static instance: Device; -class Device { constructor() { Device.instance = this; - - this.shaders = new ShaderRegistry(); } - createShader() { + createShader(_type: ShaderType, _source: string): WebGLShader { throw new Error(`${this.constructor.name} must implement 'createShader'`); } diff --git a/src/gfx/Screen.ts b/src/gfx/Screen.ts index a737542..4720fe0 100644 --- a/src/gfx/Screen.ts +++ b/src/gfx/Screen.ts @@ -2,13 +2,20 @@ import Device from './Device'; import { LinkedList, LinkStrategy } from '../utils'; import ScreenLayer from './ScreenLayer'; +import WebGL2Device from './apis/webgl2/WebGL2Device'; class Screen { - constructor(canvas) { - this.constructor.instance = this; + static instance: Screen; + + canvas: HTMLCanvasElement; + layers: LinkedList; + debugProgram?: WebGLProgram; + + constructor(canvas: HTMLCanvasElement) { + Screen.instance = this; this.canvas = canvas; - this.layers = LinkedList.of(ScreenLayer, 'zorderLink'); + this.layers = LinkedList.using('zorderLink'); this.render = this.render.bind(this); @@ -23,12 +30,13 @@ class Screen { return this; } - setViewport(..._viewport) { + setViewport(_minX: number, _maxX: number, _minY: number, _maxY: number, _minZ: number, _maxZ: number) { // TODO: Set viewport } render() { - const { gl } = Device.instance; + // TODO: Generic device interface + const { gl } = Device.instance as WebGL2Device; console.group('render'); @@ -57,8 +65,8 @@ class Screen { // TODO: Combinatory magic with base rect } - const _viewport = this.viewport; - // TODO: Save viewport + // TODO: Viewport + // const _viewport = this.viewport; gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); gl.clearColor(0, 0, 0, 1); @@ -76,7 +84,10 @@ class Screen { if (layer.flags & 0x4) { this.setViewport(0, 1, 0, 1, 0, 1); } else { - this.setViewport(...visibleRect.args, 0, 1); + this.setViewport( + visibleRect.left, visibleRect.right, + visibleRect.bottom, visibleRect.top, + 0, 1); } if (layer.flags & 0x2) { @@ -97,31 +108,32 @@ class Screen { this.debugLines(); - console.groupEnd('render'); + console.groupEnd(); // TODO: Render again // requestAnimationFrame(this.render); } - createLayer(...args) { + createLayer(...args: ConstructorParameters) { const layer = new ScreenLayer(...args); this.addLayer(layer); return layer; } - addLayer(layer) { + addLayer(layer: ScreenLayer) { const { zorder } = layer; let target = this.layers.head; while (target && zorder < target.zorder) { - target = target.zorderLink.next.entity; + target = target.zorderLink.next?.entity; } this.layers.link(layer, LinkStrategy.BEFORE, target); } debugLines() { - const device = Device.instance; + // TODO: Generic device interface + const device = Device.instance as WebGL2Device; const { gl } = device; if (!this.debugProgram) { @@ -131,9 +143,9 @@ class Screen { return; } - this.debugProgram = gl.createProgram(); - gl.attachShader(this.debugProgram, vertexShader.apiShader); - gl.attachShader(this.debugProgram, pixelShader.apiShader); + this.debugProgram = gl.createProgram()!; + gl.attachShader(this.debugProgram, vertexShader.apiShader!); + gl.attachShader(this.debugProgram, pixelShader.apiShader!); gl.linkProgram(this.debugProgram); const success = gl.getProgramParameter(this.debugProgram, gl.LINK_STATUS); @@ -148,8 +160,8 @@ class Screen { const positionPtr = gl.getAttribLocation(this.debugProgram, 'position'); - const vertical = (x) => [x, -1, x, 1]; - const horizontal = (y) => [-1, y, 1, y]; + const vertical = (x: number) => [x, -1, x, 1]; + const horizontal = (y: number) => [-1, y, 1, y]; const dataBuffer = gl.createBuffer(); const data = new Float32Array([ diff --git a/src/gfx/ScreenLayer.ts b/src/gfx/ScreenLayer.ts index 999a7a4..0304735 100644 --- a/src/gfx/ScreenLayer.ts +++ b/src/gfx/ScreenLayer.ts @@ -1,8 +1,18 @@ import { LinkedListLink } from '../utils'; import { EdgeRect } from '../math'; +type ScreenRenderFn = (_param: null, _rect: EdgeRect, _visible: EdgeRect, _elapsedSecs: number) => void; + class ScreenLayer { - constructor(rect, zorder, flags, param, render) { + rect: EdgeRect; + zorder: number; + flags: number; + param: null; + render: ScreenRenderFn; + zorderLink: LinkedListLink; + visibleRect: EdgeRect; + + constructor(rect: EdgeRect | null, zorder: number, flags: number, param: null, render: ScreenRenderFn) { this.rect = rect || new EdgeRect({ right: 1, top: 1 }); this.zorder = zorder || 0.0; this.flags = flags || 0; diff --git a/src/gfx/Shader.ts b/src/gfx/Shader.ts index 7ccf203..621ea05 100644 --- a/src/gfx/Shader.ts +++ b/src/gfx/Shader.ts @@ -1,11 +1,19 @@ import Client from '../Client'; import Device from './Device'; +type ShaderType = 'vertex' | 'pixel' + class Shader { - constructor(type, path) { + type: ShaderType; + path: string; + isLoaded: boolean; + + source?: string; + apiShader?: WebGLShader; + + constructor(type: ShaderType, path: string) { this.type = type; this.path = path; - this.data = null; this.isLoaded = false; this.onSourceLoaded = this.onSourceLoaded.bind(this); @@ -17,7 +25,7 @@ class Shader { return this.isLoaded; } - onSourceLoaded(source) { + onSourceLoaded(source: string) { this.source = source; const device = Device.instance; @@ -28,3 +36,4 @@ class Shader { } export default Shader; +export type { ShaderType }; diff --git a/src/gfx/ShaderRegistry.ts b/src/gfx/ShaderRegistry.ts index e3b6aac..3ada6eb 100644 --- a/src/gfx/ShaderRegistry.ts +++ b/src/gfx/ShaderRegistry.ts @@ -1,15 +1,19 @@ import { HashMap, HashStrategy } from '../utils'; -import Shader from './Shader'; +import Shader, { ShaderType } from './Shader'; -class ShaderRegistry extends HashMap { - constructor(pathFor) { +type ShaderPathFn = (_type: ShaderType, _name: string) => string; + +class ShaderRegistry extends HashMap { + pathFor: ShaderPathFn; + + constructor(pathFor: ShaderPathFn) { super(HashStrategy.UPPERCASE); this.pathFor = pathFor; } - shaderFor(type, name) { + shaderFor(type: ShaderType, name: string) { const path = this.pathFor(type, name); let shader = this.get(path); if (!shader) { @@ -19,11 +23,11 @@ class ShaderRegistry extends HashMap { return shader; } - pixelShaderFor(name) { + pixelShaderFor(name: string) { return this.shaderFor('pixel', name); } - vertexShaderFor(name) { + vertexShaderFor(name: string) { return this.shaderFor('vertex', name); } } diff --git a/src/gfx/Texture.ts b/src/gfx/Texture.ts index b1feef5..1720fbf 100644 --- a/src/gfx/Texture.ts +++ b/src/gfx/Texture.ts @@ -1,5 +1,9 @@ class Texture { - constructor(path) { + path: string; + isLoaded: boolean; + image: HTMLImageElement; + + constructor(path: string) { this.path = path; this.isLoaded = false; diff --git a/src/gfx/TextureFlags.ts b/src/gfx/TextureFlags.ts index bdee077..5f110e8 100644 --- a/src/gfx/TextureFlags.ts +++ b/src/gfx/TextureFlags.ts @@ -1,8 +1,19 @@ +import { TextureFilter } from './types'; + class TextureFlags { - constructor({ - filter, wrapU, wrapV, - forceMipTracking, generateMipMaps, - } = {}) { + filter: number; + wrapU: boolean; + wrapV: boolean; + forceMipTracking: boolean; + generateMipMaps: boolean; + + constructor( + filter = TextureFilter.Linear, + wrapU = false, + wrapV = false, + forceMipTracking = false, + generateMipMaps = false + ) { this.filter = filter; this.wrapU = wrapU; this.wrapV = wrapV; diff --git a/src/gfx/TextureRegistry.ts b/src/gfx/TextureRegistry.ts index 7ff4372..60f2c24 100644 --- a/src/gfx/TextureRegistry.ts +++ b/src/gfx/TextureRegistry.ts @@ -2,12 +2,12 @@ import { HashMap, HashStrategy } from '../utils'; import Texture from './Texture'; -class TextureRegistry extends HashMap { +class TextureRegistry extends HashMap { constructor() { super(HashStrategy.UPPERCASE); } - lookup(path) { + lookup(path: string) { // TODO: BLP/TGA support instead of PNG path = `${path.replace(/\.blp|\.tga/i, '')}.png`; let texture = this.get(path); diff --git a/src/gfx/apis/webgl2/WebGL2Device.ts b/src/gfx/apis/webgl2/WebGL2Device.ts index 2aa7c6a..0fe38e4 100644 --- a/src/gfx/apis/webgl2/WebGL2Device.ts +++ b/src/gfx/apis/webgl2/WebGL2Device.ts @@ -1,14 +1,21 @@ import Device from '../../Device'; import ShaderRegistry from '../../ShaderRegistry'; -import constantsFor from './constants'; +import { ShaderType } from '../../Shader'; + +// import constantsFor from './constants'; + class WebGL2Device extends Device { - constructor(canvas) { - super(canvas); + gl: WebGL2RenderingContext; + shaders: ShaderRegistry; + + constructor(canvas: HTMLCanvasElement) { + super(); // TODO: Handle context loss - this.gl = canvas.getContext('webgl2'); - this.constants = constantsFor(this.gl); + this.gl = canvas.getContext('webgl2')!; + // TODO: Constants + // this.constants = constantsFor(this.gl); this.shaders = new ShaderRegistry((type, name) => { const ext = type === 'pixel' ? 'frag' : 'vert'; @@ -16,10 +23,10 @@ class WebGL2Device extends Device { }); } - createShader(type, source) { + createShader(type: ShaderType, source: string): WebGLShader { const { gl } = this; const shaderType = type === 'pixel' ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER; - const shader = this.gl.createShader(shaderType); + const shader = this.gl.createShader(shaderType)!; this.gl.shaderSource(shader, source); this.gl.compileShader(shader); diff --git a/src/gfx/apis/webgl2/constants.ts b/src/gfx/apis/webgl2/constants.ts index d57594e..e99b565 100644 --- a/src/gfx/apis/webgl2/constants.ts +++ b/src/gfx/apis/webgl2/constants.ts @@ -12,7 +12,7 @@ const cubeMapFaces = { 3: 'TEXTURE_CUBE_MAP_NEGATIVE_Y', 4: 'TEXTURE_CUBE_MAP_POSITIVE_Z', 5: 'TEXTURE_CUBE_MAP_NEGATIVE_Z', -}; +} as const; const blendDestinations = { [BlendMode.Opaque]: 'ZERO', @@ -27,7 +27,7 @@ const blendDestinations = { [BlendMode.SrcAlphaOpaque]: 'ZERO', [BlendMode.NoAlphaAdd]: 'ONE', [BlendMode.ConstantAlpha]: 'ONE_MINUS_CONSTANT_ALPHA', -}; +} as const; const blendSources = { [BlendMode.Opaque]: 'ONE', @@ -42,25 +42,25 @@ const blendSources = { [BlendMode.SrcAlphaOpaque]: 'SRC_ALPHA', [BlendMode.NoAlphaAdd]: 'ONE', [BlendMode.ConstantAlpha]: 'CONSTANT_ALPHA', -}; +} as const; // TODO: Texture format const bufferFormatByPoolTarget = { [PoolTarget.Vertex]: 'ZERO', [PoolTarget.Index]: 'UNSIGNED_SHORT', -}; +} as const; const bufferTypeByPooltarget = { [PoolTarget.Vertex]: 'ARRAY_BUFFER', [PoolTarget.Index]: 'ELEMENT_ARRAY_BUFFER', -}; +} as const; const bufferUsageByPoolTarget = { [PoolUsage.Static]: 'STATIC_DRAW', [PoolUsage.Dynamic]: 'DYNAMIC_DRAW', [PoolUsage.Stream]: 'DYNAMIC_DRAW', -}; +} as const; const primitiveTypes = { [PrimitiveType.Points]: 'POINTS', @@ -69,10 +69,9 @@ const primitiveTypes = { [PrimitiveType.Triangles]: 'TRIANGLES', [PrimitiveType.TriangleStrip]: 'TRIANGLE_STRIP', [PrimitiveType.TriangleFan]: 'TRIANGLE_FAN', - [PrimitiveType.Last]: 'ZERO', -}; +} as const; -export default (gl) => { +export default (gl: WebGL2RenderingContext) => { const constants = {}; const categories = { @@ -83,7 +82,7 @@ export default (gl) => { bufferTypeByPooltarget, bufferUsageByPoolTarget, primitiveTypes, - }; + } as const; for (const [name, category] of Object.entries(categories)) { const entry = {}; @@ -92,8 +91,10 @@ export default (gl) => { if (constant === undefined) { throw new Error(`Could not find WebGL2 constant: ${prop}`); } + // @ts-expect-error: currently unused (and untyped) entry[index] = constant; } + // @ts-expect-error: currently unused (and untyped) constants[name] = entry; } diff --git a/src/gfx/apis/webgpu/WebGPUDevice.ts b/src/gfx/apis/webgpu/WebGPUDevice.ts index f5802cf..c7ea649 100644 --- a/src/gfx/apis/webgpu/WebGPUDevice.ts +++ b/src/gfx/apis/webgpu/WebGPUDevice.ts @@ -2,6 +2,7 @@ import Device from '../../Device'; class WebGPUDevice extends Device { constructor() { + super(); throw new Error('WebGPU not yet supported'); } } diff --git a/src/gfx/types.ts b/src/gfx/types.ts index 4e8e8bb..354d2b3 100644 --- a/src/gfx/types.ts +++ b/src/gfx/types.ts @@ -1,187 +1,196 @@ // TODO: Can we capitalize these enums? -export const BlendMode = { - Opaque: 0, - AlphaKey: 1, - Alpha: 2, - Add: 3, - Mod: 4, - Mod2x: 5, - ModAdd: 6, - InvSrcAlphaAdd: 7, - InvSrcAlphaOpaque: 8, - SrcAlphaOpaque: 9, - NoAlphaAdd: 10, - ConstantAlpha: 11, - Last: 12, -}; +export enum BlendMode { + Opaque = 0, + AlphaKey = 1, + Alpha = 2, + Add = 3, + Mod = 4, + Mod2x = 5, + ModAdd = 6, + InvSrcAlphaAdd = 7, + InvSrcAlphaOpaque = 8, + SrcAlphaOpaque = 9, + NoAlphaAdd = 10, + ConstantAlpha = 11, + Last = 12, +} -export const PoolHintBit = { - Unk0: 0, - Unk1: 1, - Unk2: 2, - Unk3: 3, -}; +export enum PoolHintBit { + Unk0 = 0, + Unk1 = 1, + Unk2 = 2, + Unk3 = 3, +} -export const PoolTarget = { - Vertex: 0, - Index: 1, - Last: 2, -}; +export enum PoolTarget { + Vertex = 0, + Index = 1, + Last = 2, +} -export const PoolUsage = { - Static: 0, - Dynamic: 1, - Stream: 2, - Last: 3, -}; +export enum PoolUsage { + Static = 0, + Dynamic = 1, + Stream = 2, + Last = 3, +} -export const PrimitiveType = { - Points: 0, - Lines: 1, - LineStrip: 2, - Triangles: 3, - TriangleStrip: 4, - TriangleFan: 5, - Last: 6, -}; +export enum PrimitiveType { + Points = 0, + Lines = 1, + LineStrip = 2, + Triangles = 3, + TriangleStrip = 4, + TriangleFan = 5, + Last = 6, +} -export const RenderStateType = { - Polygon_Offset: 0, - MatDiffuse: 1, - MatEmissive: 2, - MatSpecular: 3, - MatSpecularExp: 4, - NormalizeNormals: 5, - BlendingMode: 6, - AlphaRef: 7, - FogStart: 8, - FogEnd: 9, - FogColor: 10, - Lighting: 11, - Fog: 12, - DepthTest: 13, - DepthFunc: 14, - DepthWrite: 15, - ColorWrite: 16, - Culling: 17, - ClipPlaneMask: 18, - Multisample: 19, - ScissorTest: 20, - Texture0: 21, - Texture1: 22, - Texture2: 23, - Texture3: 24, - Texture4: 25, - Texture5: 26, - Texture6: 27, - Texture7: 28, - Texture8: 29, - Texture9: 30, - Texture10: 31, - Texture11: 32, - Texture12: 33, - Texture13: 34, - Texture14: 35, - Texture15: 36, - ColorOp0: 37, - ColorOp1: 38, - ColorOp2: 39, - ColorOp3: 40, - ColorOp4: 41, - ColorOp5: 42, - ColorOp6: 43, - ColorOp7: 44, - AlphaOp0: 45, - AlphaOp1: 46, - AlphaOp2: 47, - AlphaOp3: 48, - AlphaOp4: 49, - AlphaOp5: 50, - AlphaOp6: 51, - AlphaOp7: 52, - TexGen0: 53, - TexGen1: 54, - TexGen2: 55, - TexGen3: 56, - TexGen4: 57, - TexGen5: 58, - TexGen6: 59, - TexGen7: 60, - Unk61: 61, - Unk62: 62, - Unk63: 63, - Unk64: 64, - Unk65: 65, - Unk66: 66, - Unk67: 67, - Unk68: 68, - Unk69: 69, - Unk70: 70, - Unk71: 71, - Unk72: 72, - Unk73: 73, - Unk74: 74, - Unk75: 75, - Unk76: 76, - VertexShader: 77, - PixelShader: 78, - PointScale: 79, - PointScaleAttenuation: 80, - PointScaleMin: 81, - PointScaleMax: 82, - PointSprite: 83, - Unk84: 84, - ColorMaterial: 85, - Last: 86, -}; +export enum RenderStateType { + Polygon_Offset = 0, + MatDiffuse = 1, + MatEmissive = 2, + MatSpecular = 3, + MatSpecularExp = 4, + NormalizeNormals = 5, + BlendingMode = 6, + AlphaRef = 7, + FogStart = 8, + FogEnd = 9, + FogColor = 10, + Lighting = 11, + Fog = 12, + DepthTest = 13, + DepthFunc = 14, + DepthWrite = 15, + ColorWrite = 16, + Culling = 17, + ClipPlaneMask = 18, + Multisample = 19, + ScissorTest = 20, + Texture0 = 21, + Texture1 = 22, + Texture2 = 23, + Texture3 = 24, + Texture4 = 25, + Texture5 = 26, + Texture6 = 27, + Texture7 = 28, + Texture8 = 29, + Texture9 = 30, + Texture10 = 31, + Texture11 = 32, + Texture12 = 33, + Texture13 = 34, + Texture14 = 35, + Texture15 = 36, + ColorOp0 = 37, + ColorOp1 = 38, + ColorOp2 = 39, + ColorOp3 = 40, + ColorOp4 = 41, + ColorOp5 = 42, + ColorOp6 = 43, + ColorOp7 = 44, + AlphaOp0 = 45, + AlphaOp1 = 46, + AlphaOp2 = 47, + AlphaOp3 = 48, + AlphaOp4 = 49, + AlphaOp5 = 50, + AlphaOp6 = 51, + AlphaOp7 = 52, + TexGen0 = 53, + TexGen1 = 54, + TexGen2 = 55, + TexGen3 = 56, + TexGen4 = 57, + TexGen5 = 58, + TexGen6 = 59, + TexGen7 = 60, + Unk61 = 61, + Unk62 = 62, + Unk63 = 63, + Unk64 = 64, + Unk65 = 65, + Unk66 = 66, + Unk67 = 67, + Unk68 = 68, + Unk69 = 69, + Unk70 = 70, + Unk71 = 71, + Unk72 = 72, + Unk73 = 73, + Unk74 = 74, + Unk75 = 75, + Unk76 = 76, + VertexShader = 77, + PixelShader = 78, + PointScale = 79, + PointScaleAttenuation = 80, + PointScaleMin = 81, + PointScaleMax = 82, + PointSprite = 83, + Unk84 = 84, + ColorMaterial = 85, + Last = 86, +} -export const TextureFormat = { - INVALID: 0, - ARGB8888: 1, - XRGB8888: 2, - RGBA8888: 3, - ABGR8888: 4, - ARGB0888: 5, - RGB888: 6, - BGR888: 7, - RGBA32F: 8, - RGBA16F: 9, - RG16F: 10, - D32: 11, - D24: 12, - D16: 13, - DF: 14, - D24S8: 15, - S8: 16, - ARGB4444: 17, - ARGB1555: 18, - ARGB0555: 19, - RGB565: 20, - A2RGB10: 21, - RGB16: 22, - L8: 23, - A8: 24, - A8L8: 25, - DXT1: 26, - DXT3: 27, - DXT5: 28, -}; +export enum TextureFilter { + Nearest = 0x0, + Linear = 0x1, + NearestMipNearest = 0x2, + LinearMipNearest = 0x3, + LinearMipLinear = 0x4, + Anisotropic = 0x5, +} -export const VertexBufferFormat = { - P: 0, - PN: 1, - PNC: 2, - PNT: 3, - PNCT: 4, - PNT2: 5, - PNCT2: 6, - PC: 7, - PCT: 8, - PCT2: 9, - PT: 10, - PT2: 11, - PBNT2: 12, - PNC2T2: 13, - Last: 14, -}; +export enum TextureFormat { + INVALID = 0, + ARGB8888 = 1, + XRGB8888 = 2, + RGBA8888 = 3, + ABGR8888 = 4, + ARGB0888 = 5, + RGB888 = 6, + BGR888 = 7, + RGBA32F = 8, + RGBA16F = 9, + RG16F = 10, + D32 = 11, + D24 = 12, + D16 = 13, + DF = 14, + D24S8 = 15, + S8 = 16, + ARGB4444 = 17, + ARGB1555 = 18, + ARGB0555 = 19, + RGB565 = 20, + A2RGB10 = 21, + RGB16 = 22, + L8 = 23, + A8 = 24, + A8L8 = 25, + DXT1 = 26, + DXT3 = 27, + DXT5 = 28, +} + +export enum VertexBufferFormat { + P = 0, + PN = 1, + PNC = 2, + PNT = 3, + PNCT = 4, + PNT2 = 5, + PNCT2 = 6, + PC = 7, + PCT = 8, + PCT2 = 9, + PT = 10, + PT2 = 11, + PBNT2 = 12, + PNC2T2 = 13, + Last = 14, +} diff --git a/src/index.ts b/src/index.ts index 9764d91..23efecf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,10 +2,10 @@ import Client from './Client'; import { ModelFFX } from './ui/components'; import * as glueScriptFunctions from './ui/scripting/globals/glue'; -const params = (new URL(document.location)).searchParams; +const params = new URLSearchParams(document.location.search); const api = params.get('api') || 'webgl2'; -const canvas = document.querySelector('canvas'); +const canvas = document.querySelector('canvas')!; const client = new Client(canvas, { api }); // TODO: Part of GlueMgr diff --git a/src/math/EdgeRect.ts b/src/math/EdgeRect.ts index 5be5a4f..966155d 100644 --- a/src/math/EdgeRect.ts +++ b/src/math/EdgeRect.ts @@ -1,4 +1,9 @@ class EdgeRect { + top: number; + left: number; + bottom: number; + right: number; + constructor({ top = 0, left = 0, diff --git a/src/math/Rect.ts b/src/math/Rect.ts index 922d348..a4d44b3 100644 --- a/src/math/Rect.ts +++ b/src/math/Rect.ts @@ -1,4 +1,9 @@ class Rect { + minY: number; + minX: number; + maxY: number; + maxX: number; + constructor({ minY = 0, minX = 0, @@ -11,7 +16,7 @@ class Rect { this.maxX = maxX; } - set(other) { + set(other: Rect) { this.minY = other.minY; this.minX = other.minX; this.maxY = other.maxY; diff --git a/src/math/index.ts b/src/math/index.ts index f99a794..4e270a9 100644 --- a/src/math/index.ts +++ b/src/math/index.ts @@ -6,6 +6,6 @@ export { default as Rect } from './Rect'; export const EPSILON1 = 0.00000023841858; export const EPSILON2 = 0.00000999999970; -export const areClose = (a, b, epsilon = EPSILON1) => ( +export const areClose = (a: number, b: number, epsilon = EPSILON1) => ( Math.abs(a - b) < epsilon ); diff --git a/src/ui/DrawLayerType.ts b/src/ui/DrawLayerType.ts index ab8dbf5..d38311c 100644 --- a/src/ui/DrawLayerType.ts +++ b/src/ui/DrawLayerType.ts @@ -1,7 +1,9 @@ -export default { - BACKGROUND: 0, - BORDER: 1, - ARTWORK: 2, - OVERLAY: 3, - HIGHLIGHT: 4, -}; +enum DrawLayerType { + BACKGROUND = 0, + BORDER = 1, + ARTWORK = 2, + OVERLAY = 3, + HIGHLIGHT = 4, +} + +export default DrawLayerType; diff --git a/src/ui/TemplateRegistry.ts b/src/ui/TemplateRegistry.ts index 7a4a8e5..da7bb77 100644 --- a/src/ui/TemplateRegistry.ts +++ b/src/ui/TemplateRegistry.ts @@ -1,7 +1,14 @@ -import { HashMap, HashStrategy } from '../utils'; +import { HashMap, HashStrategy, Status } from '../utils'; + +import XMLNode from './XMLNode'; class TemplateNode { - constructor(name, node) { + name: string; + node: XMLNode; + tainted: boolean; + locked: boolean; + + constructor(name: string, node: XMLNode) { this.name = name; this.node = node; this.tainted = false; @@ -17,18 +24,19 @@ class TemplateNode { } } -class TemplateRegistry extends HashMap { +class TemplateRegistry extends HashMap { constructor() { super(HashStrategy.UPPERCASE); } - filterByList(list) { - return list.split(',').map(name => ( - this.get(name.trim()) - )); + filterByList(list: string) { + return list.split(',').map((name: string) => ({ + name, + template: this.get(name.trim()) + })); } - register(node, name, tainted, status) { + register(node: XMLNode, name: string, tainted: boolean, status: Status) { let entry = this.get(name); if (entry) { if (!entry.tainted || tainted) { diff --git a/src/ui/UIContext.ts b/src/ui/UIContext.ts index 26a48b4..80ead2d 100644 --- a/src/ui/UIContext.ts +++ b/src/ui/UIContext.ts @@ -13,8 +13,16 @@ import Texture from './components/simple/Texture'; import XMLNode from './XMLNode'; class UIContext { + static instance: UIContext; + + scripting: ScriptingContext; + factories: FactoryRegistry; + renderer: Renderer; + templates: TemplateRegistry; + root: UIRoot; + constructor() { - this.constructor.instance = this; + UIContext.instance = this; this.scripting = new ScriptingContext(); this.factories = new FactoryRegistry(); @@ -24,7 +32,7 @@ class UIContext { this.root = new UIRoot(); } - getParentNameFor(node) { + getParentNameFor(node: XMLNode) { let parentName = node.attributes.get('parent'); if (parentName) { return parentName; @@ -33,7 +41,7 @@ class UIContext { const inherits = node.attributes.get('inherits'); if (inherits) { const templates = this.templates.filterByList(inherits); - for (const template of templates) { + for (const { template } of templates) { // TODO: Does this bit require lock/release of templates? if (template && !template.locked) { parentName = node.attributes.get('parent'); @@ -44,7 +52,7 @@ class UIContext { return parentName; } - createFrame(node, parent, status = new Status()) { + createFrame(node: XMLNode, parent: Frame | null, status = new Status()) { const name = node.attributes.get('name'); if (name) { status.info(`creating ${node.name} named ${name}`); @@ -75,12 +83,12 @@ class UIContext { // TODO: Handle unique factories frame.preLoadXML(node); frame.loadXML(node); - frame.postLoadXML(node); + frame.postLoadXML(node, status); return frame; } - createFontString(node, frame) { + createFontString(node: XMLNode, frame: Frame) { const fontString = new FontString(frame, DrawLayerType.ARTWORK, true); fontString.preLoadXML(node); fontString.loadXML(node); @@ -88,7 +96,7 @@ class UIContext { return fontString; } - createTexture(node, frame) { + createTexture(node: XMLNode, frame: Frame) { const texture = new Texture(frame, DrawLayerType.ARTWORK, true); texture.preLoadXML(node); texture.loadXML(node); @@ -96,7 +104,7 @@ class UIContext { return texture; } - async load(tocPath, status = new Status()) { + async load(tocPath: string, status = new Status()) { status.info('loading toc', tocPath); const dirPath = path.dirname(tocPath); @@ -119,7 +127,7 @@ class UIContext { } } - async loadFile(filePath, status = new Status()) { + async loadFile(filePath: string, status = new Status()) { status.info('loading file', filePath); const source = await Client.instance.fetch(filePath); @@ -173,7 +181,7 @@ class UIContext { const virtual = attributes.get('virtual'); if (stringToBoolean(virtual)) { if (name) { - this.templates.register(child, name, null, status); + this.templates.register(child, name, false, status); } else { status.warning('unnamed virtual node at top level'); } diff --git a/src/ui/XMLNode.ts b/src/ui/XMLNode.ts index db92a4f..553b016 100644 --- a/src/ui/XMLNode.ts +++ b/src/ui/XMLNode.ts @@ -1,30 +1,39 @@ import { HashMap, HashStrategy } from '../utils'; +import { lua_Ref } from '../ui/scripting/lua'; class XMLNode { - constructor(parent, name) { + parent: XMLNode | null; + name: string; + attributes: HashMap; + children: XMLNode[]; + body: string | null; + scriptLuaRef: lua_Ref | null; + + constructor(parent: XMLNode | null, name: string) { this.parent = parent; this.name = name; this.attributes = new HashMap(HashStrategy.UPPERCASE); this.children = []; this.body = null; + this.scriptLuaRef = null; } get firstChild() { return this.children[0]; } - getChildByName(name) { + getChildByName(name: string) { const iname = name.toLowerCase(); return this.children.find(child => ( child.name.toLowerCase() === iname )); } - static parse(source) { + static parse(source: string) { const parser = new DOMParser(); const document = parser.parseFromString(source, 'application/xml'); - const transform = (element, parent = null) => { + const transform = (element: Element, parent: XMLNode | null = null) => { const node = new this(parent, element.tagName); const { attributes } = node; @@ -36,7 +45,7 @@ class XMLNode { for (const child of element.children) { node.children.push(transform(child, node)); } - } else { + } else if (element.textContent) { const trimmed = element.textContent.trim(); if (trimmed) { node.body = trimmed; diff --git a/src/ui/components/Backdrop.ts b/src/ui/components/Backdrop.ts index d9577a3..a149b02 100644 --- a/src/ui/components/Backdrop.ts +++ b/src/ui/components/Backdrop.ts @@ -2,13 +2,46 @@ import Color from '../../gfx/Color'; import DrawLayerType from '../DrawLayerType'; -import { Vector2 } from '../../math'; +import XMLNode from '../XMLNode'; +import { Rect, Vector2 } from '../../math'; import { extractInsetsFrom, extractValueFrom, stringToBoolean } from '../../utils'; +import Frame from './simple/Frame'; import FramePointType from './abstract/FramePointType'; -import Texture, { TextureImageMode } from './simple/Texture'; +import Texture from './simple/Texture'; +import TextureImageMode from './simple/TextureImageMode'; class Backdrop { + backgroundFile: string | null; + backgroundSize: number; + + edgeFile: string | null; + edgeSize: number; + + tileBackground: boolean; + + color: Color; + borderColor: Color; + + // TODO: Default value + blendMode: null; + pieces: number; + + backgroundTexture: Texture | null; + leftTexture: Texture | null; + rightTexture: Texture | null; + topTexture: Texture | null; + bottomTexture: Texture | null; + topLeftTexture: Texture | null; + topRightTexture: Texture | null; + bottomLeftTexture: Texture | null; + bottomRightTexture: Texture | null; + + topInset: number; + bottomInset: number; + leftInset: number; + rightInset: number; + constructor() { this.backgroundFile = null; this.backgroundSize = 0.0; @@ -41,9 +74,9 @@ class Backdrop { this.rightInset = 0.0; } - loadXML(node) { - this.backgroundFile = node.attributes.get('bgFile'); - this.edgeFile = node.attributes.get('edgeFile'); + loadXML(node: XMLNode) { + this.backgroundFile = node.attributes.get('bgFile') ?? null; + this.edgeFile = node.attributes.get('edgeFile') ?? null; const tile = node.attributes.get('tile'); this.tileBackground = stringToBoolean(tile, false); @@ -90,18 +123,18 @@ class Backdrop { } } - setOutput(frame) { + setOutput(frame: Frame) { const texCoords = [ new Vector2(), new Vector2(), new Vector2(), new Vector2(), - ]; + ] as const; const { backgroundFile, edgeFile } = this; if (backgroundFile) { - const bgTexture = new Texture(frame, DrawLayerType.BACKGROUND, 1); + const bgTexture = new Texture(frame, DrawLayerType.BACKGROUND, true); this.backgroundTexture = bgTexture; bgTexture.setPoint(FramePointType.TOPLEFT, frame, FramePointType.TOPLEFT, this.leftInset, -this.topInset, false); @@ -124,7 +157,7 @@ class Backdrop { leftTexture.resize(false); - leftTexture.setTexture(edgeFile, true, true, null, TextureImageMode.UI); + leftTexture.setTexture(edgeFile!, true, true, null, TextureImageMode.UI); // TODO: leftTexture.setBlendMode(this.blendMode); } @@ -138,7 +171,7 @@ class Backdrop { rightTexture.resize(false); - rightTexture.setTexture(edgeFile, true, true, null, TextureImageMode.UI); + rightTexture.setTexture(edgeFile!, true, true, null, TextureImageMode.UI); // TODO: rightTexture.setBlendMode(this.blendMode); } @@ -152,7 +185,7 @@ class Backdrop { topTexture.resize(false); - topTexture.setTexture(edgeFile, true, true, null, TextureImageMode.UI); + topTexture.setTexture(edgeFile!, true, true, null, TextureImageMode.UI); // TODO: topTexture.setBlendMode(this.blendMode); } @@ -166,7 +199,7 @@ class Backdrop { bottomTexture.resize(false); - bottomTexture.setTexture(edgeFile, true, true, null, TextureImageMode.UI); + bottomTexture.setTexture(edgeFile!, true, true, null, TextureImageMode.UI); // TODO: bottomTexture.setBlendMode(this.blendMode); } @@ -183,7 +216,7 @@ class Backdrop { texCoords[2].setElements(0.6171875, 0.0625); texCoords[3].setElements(0.6171875, 0.9375); - topLeftTexture.setTexture(edgeFile, false, false, null, TextureImageMode.UI); + topLeftTexture.setTexture(edgeFile!, false, false, null, TextureImageMode.UI); topLeftTexture.setTextureCoords(texCoords); // TODO: topLeftTexture.setBlendMode(this.blendMode); } @@ -201,7 +234,7 @@ class Backdrop { texCoords[2].setElements(0.7421875, 0.0625); texCoords[3].setElements(0.7421875, 0.9375); - topRightTexture.setTexture(edgeFile, false, false, null, TextureImageMode.UI); + topRightTexture.setTexture(edgeFile!, false, false, null, TextureImageMode.UI); topRightTexture.setTextureCoords(texCoords); // TODO: topRightTexture.setBlendMode(this.blendMode); } @@ -219,7 +252,7 @@ class Backdrop { texCoords[2].setElements(0.8671875, 0.0625); texCoords[3].setElements(0.8671875, 0.9375); - bottomLeftTexture.setTexture(edgeFile, false, false, null, TextureImageMode.UI); + bottomLeftTexture.setTexture(edgeFile!, false, false, null, TextureImageMode.UI); bottomLeftTexture.setTextureCoords(texCoords); // TODO: bottomLeftTexture.setBlendMode(this.blendMode); } @@ -237,19 +270,19 @@ class Backdrop { texCoords[2].setElements(0.9921875, 0.0625); texCoords[3].setElements(0.9921875, 0.9375); - bottomRightTexture.setTexture(edgeFile, false, false, null, TextureImageMode.UI); + bottomRightTexture.setTexture(edgeFile!, false, false, null, TextureImageMode.UI); bottomRightTexture.setTextureCoords(texCoords); // TODO: bottomRightTexture.setBlendMode(this.blendMode); } } - update(rect) { + update(rect: Rect) { const texCoords = [ new Vector2(), new Vector2(), new Vector2(), new Vector2(), - ]; + ] as const; const width = rect.maxX - rect.minX; const height = rect.maxY - rect.minY; @@ -270,7 +303,7 @@ class Backdrop { texCoords[2].setElements(width / size, 0.0); texCoords[3].setElements(width / size, height / size); - this.backgroundTexture.setTextureCoords(texCoords); + this.backgroundTexture!.setTextureCoords(texCoords); } if (this.pieces & 0x1) { @@ -279,7 +312,7 @@ class Backdrop { texCoords[2].setElements(0.1171875, 0.0625); texCoords[3].setElements(0.1171875, v17 - 0.0625); - this.leftTexture.setTextureCoords(texCoords); + this.leftTexture!.setTextureCoords(texCoords); } if (this.pieces & 0x2) { @@ -288,7 +321,7 @@ class Backdrop { texCoords[2].setElements(0.2421875, 0.0625); texCoords[3].setElements(0.2421875, v17 - 0.0625); - this.rightTexture.setTextureCoords(texCoords); + this.rightTexture!.setTextureCoords(texCoords); } if (this.pieces & 0x4) { @@ -297,7 +330,7 @@ class Backdrop { texCoords[2].setElements(0.2578125, 0.0625); texCoords[3].setElements(0.3671875, 0.0625); - this.topTexture.setTextureCoords(texCoords); + this.topTexture!.setTextureCoords(texCoords); } if (this.pieces & 0x8) { @@ -306,7 +339,7 @@ class Backdrop { texCoords[2].setElements(0.3828125, 0.0625); texCoords[3].setElements(0.4921875, 0.0625); - this.bottomTexture.setTextureCoords(texCoords); + this.bottomTexture!.setTextureCoords(texCoords); } // TODO: Set vertex colors diff --git a/src/ui/components/FactoryRegistry.ts b/src/ui/components/FactoryRegistry.ts index d67f9f4..da15e1f 100644 --- a/src/ui/components/FactoryRegistry.ts +++ b/src/ui/components/FactoryRegistry.ts @@ -1,40 +1,41 @@ import { HashMap, HashStrategy } from '../../utils'; -import * as frameTypes from '.'; - -const DEFAULT_FACTORIES = [ - 'Button', - 'CheckButton', - 'EditBox', - 'Frame', - 'Model', - 'ScrollFrame', - 'SimpleHTML', - 'Slider', -]; +import { + Button, CheckButton, EditBox, Frame, Model, ScrollFrame, SimpleHTML, Slider +} from '.'; + +const DEFAULT_FACTORIES = { + Button, CheckButton, EditBox, Frame, Model, ScrollFrame, SimpleHTML, Slider +}; + +type FactoryConstructor = new (parent: Frame | null) => Frame; class FactoryNode { - constructor(name, ctor, unique = false) { + name: string; + ctor: FactoryConstructor; + unique: boolean; + + constructor(name: string, ctor: FactoryConstructor, unique = false) { this.name = name; this.ctor = ctor; this.unique = unique; } - build(parent) { + build(parent: Frame | null) { return new this.ctor(parent); } } -class FactoryRegistry extends HashMap { +class FactoryRegistry extends HashMap { constructor() { super(HashStrategy.UPPERCASE); - for (const name of DEFAULT_FACTORIES) { - this.register(name, frameTypes[name]); + for (const [name, ctor] of Object.entries(DEFAULT_FACTORIES)) { + this.register(name, ctor); } } - register(name, ctor, unique = false) { + register(name: string, ctor: FactoryConstructor, unique = false) { const node = new FactoryNode(name, ctor, unique); this.set(name, node); } diff --git a/src/ui/components/UIRoot.ts b/src/ui/components/UIRoot.ts index 547ea8a..4cc23a0 100644 --- a/src/ui/components/UIRoot.ts +++ b/src/ui/components/UIRoot.ts @@ -1,28 +1,50 @@ +import DrawLayerType from '../DrawLayerType'; import Screen from '../../gfx/Screen'; +import ScreenLayer from '../../gfx/ScreenLayer'; +import { EdgeRect } from '../../math'; import { LinkedList, NDCtoDDCWidth, NDCtoDDCHeight } from '../../utils'; import Frame, { FrameFlag } from './simple/Frame'; -import FramePointType from './abstract/FramePoint'; +import FramePointType from './abstract/FramePointType'; import FrameStrata, { FrameStrataType } from './abstract/FrameStrata'; import LayoutFrame from './abstract/LayoutFrame'; class UIRoot extends LayoutFrame { + static instance: UIRoot; + + layout: { + frame: Frame | null, + anchor: FramePointType + }; + strata: Record & Iterable; + frames: LinkedList; + destroyedFrames: LinkedList; + screenLayer: ScreenLayer; + constructor() { super(); - this.constructor.instance = this; + UIRoot.instance = this; this.layout = { frame: null, anchor: FramePointType.TOPLEFT, }; - this.strata = Object.values(FrameStrataType).map(type => ( - new FrameStrata(type) - )); - - this.frames = LinkedList.of(Frame, 'framesLink'); - this.destroyedFrames = LinkedList.of(Frame, 'destroyedLink'); + this.strata = [ + new FrameStrata(FrameStrataType.WORLD), + new FrameStrata(FrameStrataType.BACKGROUND), + new FrameStrata(FrameStrataType.LOW), + new FrameStrata(FrameStrataType.MEDIUM), + new FrameStrata(FrameStrataType.HIGH), + new FrameStrata(FrameStrataType.DIALOG), + new FrameStrata(FrameStrataType.FULLSCREEN), + new FrameStrata(FrameStrataType.FULLSCREEN_DIALOG), + new FrameStrata(FrameStrataType.TOOLTIP), + ]; + + this.frames = LinkedList.using('framesLink'); + this.destroyedFrames = LinkedList.using('destroyedLink'); this.rect.maxX = NDCtoDDCWidth(1); this.rect.maxY = NDCtoDDCHeight(1); @@ -37,16 +59,16 @@ class UIRoot extends LayoutFrame { ); } - register(frame) { + register(frame: Frame) { this.frames.add(frame); } - showFrame(frame) { + showFrame(frame: Frame, _unknown: boolean) { this.strata[frame.strataType].addFrame(frame); // TODO: Register for events } - hideFrame(frame, _unknownBool) { + hideFrame(frame: Frame, _unknown: boolean) { if (this.layout.frame === frame) { // Unflatten (?) current layout frame @@ -58,7 +80,8 @@ class UIRoot extends LayoutFrame { this.strata[frame.strataType].removeFrame(frame); } - raiseFrame(frame, _checkOcclusion) { + raiseFrame(source: Frame, _checkOcclusion: boolean) { + let frame: Frame | null = source; while (frame && frame.flags & FrameFlag.TOPLEVEL) { frame = frame.parent; } @@ -79,14 +102,14 @@ class UIRoot extends LayoutFrame { return true; } - notifyFrameLayerChanged(frame, drawLayerType) { + notifyFrameLayerChanged(frame: Frame, drawLayerType: DrawLayerType) { const strata = this.strata[frame.strataType]; const level = strata.levels[frame.level]; level.batchDirty |= 1 << drawLayerType; strata.batchDirty = 1; } - onLayerUpdate(elapsedSecs) { + onLayerUpdate(elapsedSecs: number) { // TODO: Clean-up destroyed frames console.log('root pre-render', this); @@ -111,7 +134,7 @@ class UIRoot extends LayoutFrame { } } - onPaintScreen(param, rect, visibleRect, elapsedSecs) { + onPaintScreen(_param: null, _rect: EdgeRect, _visibleRect: EdgeRect, elapsedSecs: number) { this.onLayerUpdate(elapsedSecs); this.onLayerRender(); } diff --git a/src/ui/components/abstract/FramePoint.ts b/src/ui/components/abstract/FramePoint.ts index 80443fd..f1bc47b 100644 --- a/src/ui/components/abstract/FramePoint.ts +++ b/src/ui/components/abstract/FramePoint.ts @@ -1,9 +1,17 @@ import { Vector2 } from '../../../math'; +import LayoutFrame from './LayoutFrame'; import Type from './FramePointType'; class FramePoint { - constructor(relative, type, offsetX, offsetY) { + static UNDEFINED = Infinity; + + relative: LayoutFrame; + type: Type; + offset: Vector2; + flags: number; + + constructor(relative: LayoutFrame, type: Type, offsetX: number, offsetY: number) { this.relative = relative; this.type = type; this.offset = new Vector2([offsetX, offsetY]); @@ -33,7 +41,7 @@ class FramePoint { } } - if (relative.flags & 0x2) { + if (relative.layoutFlags & 0x2) { relative.deferredResize = false; if (this.flags & 0x4) { if (!initial) { @@ -64,8 +72,12 @@ class FramePoint { return rect; } - x(scale) { - const { UNDEFINED } = this.constructor; + setRelative(_relative: LayoutFrame, _relativePoint: Type, _offsetX: number, _offsetY: number) { + // TODO: Implement + } + + x(scale: number) { + const { UNDEFINED } = FramePoint; const rect = this.relativeRect; if (!rect) { @@ -93,8 +105,8 @@ class FramePoint { } } - y(scale) { - const { UNDEFINED } = this.constructor; + y(scale: number) { + const { UNDEFINED } = FramePoint; const rect = this.relativeRect; if (!rect) { @@ -122,7 +134,7 @@ class FramePoint { } } - static synthesizeSide(center, opposite, size) { + static synthesizeSide(center: number, opposite: number, size: number) { if (center !== this.UNDEFINED && opposite !== this.UNDEFINED) { return center + center - opposite; } else if (opposite !== this.UNDEFINED && size !== 0.0) { @@ -133,7 +145,7 @@ class FramePoint { return this.UNDEFINED; } - static synthesizeCenter(side1, side2, size) { + static synthesizeCenter(side1: number, side2: number, size: number) { if (side1 !== this.UNDEFINED && side2 !== this.UNDEFINED) { return (side1 + side2) * 0.5; } else if (side1 !== this.UNDEFINED && size !== 0.0) { @@ -145,6 +157,4 @@ class FramePoint { } } -FramePoint.UNDEFINED = Infinity; - export default FramePoint; diff --git a/src/ui/components/abstract/FramePointType.ts b/src/ui/components/abstract/FramePointType.ts index ae4fbdf..4c06cdb 100644 --- a/src/ui/components/abstract/FramePointType.ts +++ b/src/ui/components/abstract/FramePointType.ts @@ -1,16 +1,19 @@ -const FramePointType = { - TOPLEFT: 0, - TOP: 1, - TOPRIGHT: 2, - LEFT: 3, - CENTER: 4, - RIGHT: 5, - BOTTOMLEFT: 6, - BOTTOM: 7, - BOTTOMRIGHT: 8, -}; +enum FramePointType { + TOPLEFT = 0, + TOP = 1, + TOPRIGHT = 2, + LEFT = 3, + CENTER = 4, + RIGHT = 5, + BOTTOMLEFT = 6, + BOTTOM = 7, + BOTTOMRIGHT = 8, +} -export const stringToPointType = (str) => FramePointType[str.toUpperCase()]; +export const stringToPointType = (string?: string) => { + if (!string) return undefined; + return FramePointType[string.toUpperCase() as keyof typeof FramePointType]; +}; export const FramePointTypeSide = { CENTERX: [ diff --git a/src/ui/components/abstract/FrameStrata.ts b/src/ui/components/abstract/FrameStrata.ts index 2edc5e3..e341299 100644 --- a/src/ui/components/abstract/FrameStrata.ts +++ b/src/ui/components/abstract/FrameStrata.ts @@ -5,7 +5,12 @@ import FrameStrataType from './FrameStrataType'; import FrameStrataLevel from './FrameStrataLevel'; class FrameStrata { - constructor(type) { + type: FrameStrataType; + levels: FrameStrataLevel[]; + levelsDirty: number; + batchDirty: number; + + constructor(type: FrameStrataType) { this.type = type; this.levels = []; @@ -14,16 +19,14 @@ class FrameStrata { } get name() { - const lookup = Object.keys(FrameStrataType); - const name = lookup[this.type]; - return name; + return FrameStrataType[this.type]; } get topLevel() { return this.levels.length; } - addFrame(frame) { + addFrame(frame: Frame) { const { topLevel } = this; if (frame.level >= topLevel) { for (let index = topLevel; index < frame.level + 1; ++index) { @@ -47,14 +50,14 @@ class FrameStrata { } this.levelsDirty = 1; - this.batchDirty |= (level.batchDirty !== 0); + this.batchDirty |= +(level.batchDirty !== 0); } - removeFrame(frame) { + removeFrame(frame: Frame) { if (frame.level < this.topLevel) { const level = this.levels[frame.level]; const batchDirty = level.removeFrame(frame); - this.batchDirty |= batchDirty; + this.batchDirty |= +batchDirty; this.levelsDirty = 1; } } @@ -84,7 +87,7 @@ class FrameStrata { return this.batchDirty; } - onLayerUpdate(elapsedSecs) { + onLayerUpdate(elapsedSecs: number) { for (const level of this.levels) { level.onLayerUpdate(elapsedSecs); } @@ -110,10 +113,10 @@ class FrameStrata { renderer.draw(batch); } - console.groupEnd(`level ${level.index}`); + console.groupEnd(); } - console.groupEnd(`strata ${this.type} (${this.name})`); + console.groupEnd(); } } diff --git a/src/ui/components/abstract/FrameStrataLevel.ts b/src/ui/components/abstract/FrameStrataLevel.ts index 191fad2..9b7d08d 100644 --- a/src/ui/components/abstract/FrameStrataLevel.ts +++ b/src/ui/components/abstract/FrameStrataLevel.ts @@ -4,30 +4,41 @@ import RenderBatch from '../../rendering/RenderBatch'; import { LinkedList } from '../../../utils'; class FrameStrataLevel { - constructor(index) { + index: number; + pendingFrames: LinkedList; + frames: LinkedList; + pendingFrame?: Frame; + batches: Record & Iterable; + batchDirty: number; + renderList: LinkedList; + + constructor(index: number) { this.index = index; - this.pendingFrames = LinkedList.of(Frame, 'strataLink'); - this.frames = LinkedList.of(Frame, 'strataLink'); + // TODO: Can these two linked lists use the same property safely? + this.pendingFrames = LinkedList.using('strataLink'); + this.frames = LinkedList.using('strataLink'); - this.pendingFrame = null; - - this.batches = Object.values(DrawLayerType).map(type => ( - new RenderBatch(type) - )); + this.batches = [ + new RenderBatch(DrawLayerType.BACKGROUND), + new RenderBatch(DrawLayerType.BORDER), + new RenderBatch(DrawLayerType.ARTWORK), + new RenderBatch(DrawLayerType.OVERLAY), + new RenderBatch(DrawLayerType.HIGHLIGHT), + ]; this.batchDirty = 0; - this.renderList = LinkedList.of(RenderBatch, 'renderLink'); + this.renderList = LinkedList.using('renderLink'); } - removeFrame(frame) { + removeFrame(frame: Frame) { if (!this.frames.isLinked(frame)) { - return 0; + return false; } if (frame === this.pendingFrame) { - this.pendingFrame = this.frames.linkFor(frame).next; + this.pendingFrame = this.frames.linkFor(frame).next?.entity; } this.frames.unlink(frame); @@ -80,10 +91,10 @@ class FrameStrataLevel { return 0; } - onLayerUpdate(elapsedSecs) { + onLayerUpdate(elapsedSecs: number) { let frame = this.frames.head; while (frame) { - const next = frame.strataLink.next.entity; + const next = frame.strataLink.next?.entity; this.pendingFrame = next; frame.onLayerUpdate(elapsedSecs); frame = this.pendingFrame; diff --git a/src/ui/components/abstract/FrameStrataType.ts b/src/ui/components/abstract/FrameStrataType.ts index ecda3ee..bcd24c4 100644 --- a/src/ui/components/abstract/FrameStrataType.ts +++ b/src/ui/components/abstract/FrameStrataType.ts @@ -1,11 +1,13 @@ -export default { - WORLD: 0, - BACKGROUND: 1, - LOW: 2, - MEDIUM: 3, - HIGH: 4, - DIALOG: 5, - FULLSCREEN: 6, - FULLSCREEN_DIALOG: 7, - TOOLTIP: 8, -}; +enum FrameStrataType { + WORLD = 0, + BACKGROUND = 1, + LOW = 2, + MEDIUM = 3, + HIGH = 4, + DIALOG = 5, + FULLSCREEN = 6, + FULLSCREEN_DIALOG = 7, + TOOLTIP = 8, +} + +export default FrameStrataType; diff --git a/src/ui/components/abstract/LayoutFrame.ts b/src/ui/components/abstract/LayoutFrame.ts index f78526d..ca42c2a 100644 --- a/src/ui/components/abstract/LayoutFrame.ts +++ b/src/ui/components/abstract/LayoutFrame.ts @@ -1,4 +1,5 @@ import ScriptRegion from './ScriptRegion'; +import XMLNode from '../../XMLNode'; import { EPSILON1, EPSILON2, @@ -22,7 +23,10 @@ import FramePointType, { } from './FramePointType'; class FrameNode extends LinkedListNode { - constructor(frame, changed = 0) { + frame: LayoutFrame; + changed: number; + + constructor(frame: LayoutFrame, changed: number = 0) { super(); this.frame = frame; @@ -31,6 +35,32 @@ class FrameNode extends LinkedListNode { } class LayoutFrame { + static resizePendingList: LinkedList = LinkedList.using('resizePendingLink'); + + layoutFlags: number; + + rect: Rect; + _width: number; + _height: number; + + layoutScale: number; + layoutDepth: number; + + guard: { + left: boolean, + top: boolean, + right: boolean, + bottom: boolean, + centerX: boolean, + centerY: boolean, + }; + + resizeList: LinkedList; + resizeCounter: number; + _resizePendingLink?: LinkedListLink; + + points: Record & Iterable; + constructor() { this.layoutFlags = 0; @@ -50,15 +80,18 @@ class LayoutFrame { centerY: false, }; - this.resizeList = LinkedList.of(FrameNode); + this.resizeList = LinkedList.using('link'); this.resizeCounter = NaN; - this.points = []; + this.points = [ + null, null, null, + null, null, null, + null, null, null, + ]; } - // Note: Required as LayoutFrame is used as an auxiliary baseclass using - // the multipleClasses utility; creating this link in the constructor would - // incorrectly hold a reference to a throw-away LayoutFrame instance + // Note: LayoutFrame is used as an auxiliary baseclass using the `multipleClasses` utility, so creating this + // link in the constructor would hold an invalid reference to a throw-away LayoutFrame instance get resizePendingLink() { if (!this._resizePendingLink) { this._resizePendingLink = LinkedListLink.for(this); @@ -203,11 +236,11 @@ class LayoutFrame { return this.layoutFlags & 0x4; } - get layoutParent() { + get layoutParent(): LayoutFrame | null { return null; } - set deferredResize(enable) { + set deferredResize(enable: boolean) { if (enable) { this.layoutFlags |= 0x2; @@ -234,7 +267,7 @@ class LayoutFrame { // TODO: Implementation } - calculateRect(rect) { + calculateRect(rect: Rect) { rect.minX = this.left; if (rect.minX === FramePoint.UNDEFINED) { return false; @@ -291,7 +324,7 @@ class LayoutFrame { return true; } - canBeAnchorFor(_other) { + canBeAnchorFor(_other: LayoutFrame) { // TODO: Implementation return true; } @@ -303,7 +336,11 @@ class LayoutFrame { } } - getFirstPoint(pointTypes, prop = 'x') { + fullyQualifyName(_name: string): string | null { + return null; + } + + getFirstPoint(pointTypes: FramePointType[], prop: 'x' | 'y') { let value; for (const pointType of pointTypes) { const point = this.points[pointType]; @@ -330,7 +367,7 @@ class LayoutFrame { return rect; } - loadXML(node) { + loadXML(node: XMLNode) { const size = node.getChildByName('Size'); if (size) { const { x, y } = extractDimensionsFrom(size); @@ -360,12 +397,12 @@ class LayoutFrame { const relativeValue = child.attributes.get('relativeTo'); const pointType = stringToPointType(pointValue); + let relativePointType = pointType; if (!pointType) { // TODO: Error handling continue; } - let relativePointType = pointType; if (relativePointValue) { relativePointType = stringToPointType(relativePointValue); if (!relativePointType) { @@ -376,7 +413,7 @@ class LayoutFrame { let relative = layoutParent; if (relativeValue) { - const fqname = this.fullyQualifyName(relativeValue); + const fqname = this.fullyQualifyName(relativeValue)!; relative = ScriptRegion.getObjectByName(fqname); if (!relative) { // TODO: Error handling @@ -396,7 +433,7 @@ class LayoutFrame { y: offsetY, } = extractDimensionsFrom(offsetNode || child); - this.setPoint(pointType, relative, relativePointType, offsetX, offsetY, false); + this.setPoint(pointType, relative, relativePointType!, offsetX, offsetY, false); } this.resize(false); @@ -440,13 +477,13 @@ class LayoutFrame { return this.layoutFlags & 0x1; } - onFrameSizeChanged(_prevRect) { + onFrameSizeChanged(_prevRect: Rect) { for (const node of this.resizeList) { node.frame.resize(false); } } - registerResize(frame, changed) { + registerResize(frame: LayoutFrame, changed: number) { for (const node of this.resizeList) { if (node.frame === frame) { node.changed |= changed; @@ -485,8 +522,8 @@ class LayoutFrame { } } - setAllPoints(relative, resize = false) { - if (!relative.canBeAnchorFor(this)) { + setAllPoints(relative: LayoutFrame | null, resize = false) { + if (!relative || !relative.canBeAnchorFor(this)) { return; } @@ -518,8 +555,8 @@ class LayoutFrame { } } - setPoint(pointType, relative, relativePointType, offsetX = 0, offsetY = 0, resize = false) { - if (!relative.canBeAnchorFor(this)) { + setPoint(pointType: FramePointType, relative: LayoutFrame | null, relativePointType: FramePointType, offsetX = 0, offsetY = 0, resize = false) { + if (!relative || !relative.canBeAnchorFor(this)) { return; } @@ -551,7 +588,7 @@ class LayoutFrame { this.points[pointType] = framePoint; } - this.layoutflags &= ~0x8; + this.layoutFlags &= ~0x8; relative.registerResize(this, 1 << pointType); if (resize) { @@ -559,7 +596,7 @@ class LayoutFrame { } } - unregisterResize() { + unregisterResize(_frame: LayoutFrame, _dep: number) { // TODO: Implementation } @@ -569,13 +606,11 @@ class LayoutFrame { for (const frame of resizePendingList) { // TODO: Is object loaded check if (frame.onFrameResize() || --frame.resizeCounter === 0) { - frame.layoutflags &= ~0x4; + frame.layoutFlags &= ~0x4; resizePendingList.unlink(frame); } } } } -LayoutFrame.resizePendingList = LinkedList.of(LayoutFrame, 'resizePendingLink'); - export default LayoutFrame; diff --git a/src/ui/components/abstract/ScriptObject.script.ts b/src/ui/components/abstract/ScriptObject.script.ts index 2c52a3a..8154b99 100644 --- a/src/ui/components/abstract/ScriptObject.script.ts +++ b/src/ui/components/abstract/ScriptObject.script.ts @@ -1,11 +1,13 @@ import { LUA_REGISTRYINDEX, + lua_State, lua_pushnil, lua_pushstring, lua_rawgeti, } from '../../scripting/lua'; import ScriptObject from './ScriptObject'; +import ScriptRegion from './ScriptRegion'; export const GetObjectType = () => { return 0; @@ -15,7 +17,7 @@ export const IsObjectType = () => { return 0; }; -export const GetName = (L) => { +export const GetName = (L: lua_State) => { const object = ScriptObject.getObjectFromStack(L); if (object && object.name) { lua_pushstring(L, object.name); @@ -26,15 +28,16 @@ export const GetName = (L) => { return 1; }; -export const GetParent = (L) => { - const object = ScriptObject.getObjectFromStack(L); +export const GetParent = (L: lua_State) => { + // TODO: Is this cast correct? + const object = ScriptObject.getObjectFromStack(L) as ScriptRegion; const { parent } = object; if (parent) { if (!parent.isLuaRegistered) { parent.register(); } - lua_rawgeti(L, LUA_REGISTRYINDEX, parent.luaRef); + lua_rawgeti(L, LUA_REGISTRYINDEX, parent.luaRef!); } else { lua_pushnil(L); } diff --git a/src/ui/components/abstract/ScriptObject.ts b/src/ui/components/abstract/ScriptObject.ts index 04da300..29e07fd 100644 --- a/src/ui/components/abstract/ScriptObject.ts +++ b/src/ui/components/abstract/ScriptObject.ts @@ -1,4 +1,6 @@ +import Frame from '../simple/Frame'; import FrameScriptObject from '../../scripting/FrameScriptObject'; +import XMLNode from '../../XMLNode'; import * as scriptFunctions from './ScriptObject.script'; @@ -7,8 +9,10 @@ class ScriptObject extends FrameScriptObject { return scriptFunctions; } - constructor() { - super(); + _name: string | null; + + constructor(_dummy: unknown) { + super(_dummy); this._name = null; } @@ -33,14 +37,18 @@ class ScriptObject extends FrameScriptObject { } } + get parent(): Frame | null { + return null; + } + // Fully qualifies given name, replacing $parent (if any) with parent's name // Example: - fullyQualifyName(name) { + fullyQualifyName(name: string) { const keyword = '$parent'; if (name.includes(keyword)) { - let parentName = ''; + let parentName: string | null = ''; - let node = this; + let node: ScriptObject | null = this; while (node = node.parent) { parentName = node.name; if (parentName) { @@ -48,13 +56,13 @@ class ScriptObject extends FrameScriptObject { } } - const fqname = name.replace(keyword, parentName); + const fqname: string = name.replace(keyword, parentName || ''); return fqname; } return name; } - preLoadXML(node) { + preLoadXML(node: XMLNode) { const name = node.attributes.get('name'); if (name) { diff --git a/src/ui/components/abstract/ScriptRegion.script.ts b/src/ui/components/abstract/ScriptRegion.script.ts index 683bd5e..207b6ac 100644 --- a/src/ui/components/abstract/ScriptRegion.script.ts +++ b/src/ui/components/abstract/ScriptRegion.script.ts @@ -6,6 +6,7 @@ import { } from '../../../utils'; import { luaL_error, + lua_State, lua_isnumber, lua_pushnumber, lua_tonumber, @@ -49,11 +50,11 @@ export const GetBottom = () => { return 0; }; -export const GetWidth = (L) => { +export const GetWidth = (L: lua_State) => { const region = ScriptRegion.getObjectFromStack(L); let { width } = region; - if (width === 0.0 && !luaValueToBoolean(L, 2, 0)) { + if (width === 0.0 && !luaValueToBoolean(L, 2, false)) { if (region.isResizePending) { region.resize(true); } @@ -70,7 +71,7 @@ export const GetWidth = (L) => { return 1; }; -export const SetWidth = (L) => { +export const SetWidth = (L: lua_State) => { const region = ScriptRegion.getObjectFromStack(L); // TODO: Protected logic @@ -88,11 +89,11 @@ export const SetWidth = (L) => { return 0; }; -export const GetHeight = (L) => { +export const GetHeight = (L: lua_State) => { const region = ScriptRegion.getObjectFromStack(L); let { height } = region; - if (height === 0.0 && !luaValueToBoolean(L, 2, 0)) { + if (height === 0.0 && !luaValueToBoolean(L, 2, false)) { if (region.isResizePending) { region.resize(true); } @@ -109,7 +110,7 @@ export const GetHeight = (L) => { return 1; }; -export const SetHeight = (L) => { +export const SetHeight = (L: lua_State) => { const region = ScriptRegion.getObjectFromStack(L); // TOOD: Protected logic diff --git a/src/ui/components/abstract/ScriptRegion.ts b/src/ui/components/abstract/ScriptRegion.ts index e44416f..bc9bdfa 100644 --- a/src/ui/components/abstract/ScriptRegion.ts +++ b/src/ui/components/abstract/ScriptRegion.ts @@ -1,8 +1,11 @@ -import LayoutFrame from './LayoutFrame'; -import ScriptObject from './ScriptObject'; +import Frame from '../simple/Frame'; import UIRoot from '../UIRoot'; +import XMLNode from '../../XMLNode'; import { multipleClasses } from '../../../utils'; +import LayoutFrame from './LayoutFrame'; +import ScriptObject from './ScriptObject'; + import * as scriptFunctions from './ScriptRegion.script'; class ScriptRegion extends multipleClasses(ScriptObject, LayoutFrame) { @@ -13,8 +16,10 @@ class ScriptRegion extends multipleClasses(ScriptObject, LayoutFrame) { }; } - constructor(...args) { - super(...args); + _parent: Frame | null; + + constructor(_dummy: unknown) { + super(_dummy); this._parent = null; } @@ -28,14 +33,14 @@ class ScriptRegion extends multipleClasses(ScriptObject, LayoutFrame) { return this._parent; } - get layoutParent() { + get layoutParent(): LayoutFrame { if (this.width === 0.0 || !this.parent) { return UIRoot.instance; } return this.parent; } - loadXML(node) { + loadXML(node: XMLNode) { LayoutFrame.prototype.loadXML.call(this, node); const parentKey = node.attributes.get('parentKey'); diff --git a/src/ui/components/simple/Button.script.ts b/src/ui/components/simple/Button.script.ts index 39bff52..7db0385 100644 --- a/src/ui/components/simple/Button.script.ts +++ b/src/ui/components/simple/Button.script.ts @@ -4,6 +4,7 @@ import { } from '../../../utils'; import { LUA_REGISTRYINDEX, + lua_State, lua_pushnil, lua_pushnumber, lua_rawgeti, @@ -103,7 +104,7 @@ export const SetHighlightTexture = () => { return 0; }; -export const GetHighlightTexture = (L) => { +export const GetHighlightTexture = (L: lua_State) => { const button = Button.getObjectFromStack(L); const texture = button.highlightTexture; @@ -112,7 +113,7 @@ export const GetHighlightTexture = (L) => { texture.register(); } - lua_rawgeti(L, LUA_REGISTRYINDEX, texture.luaRef); + lua_rawgeti(L, LUA_REGISTRYINDEX, texture.luaRef!); } else { lua_pushnil(L); } @@ -128,7 +129,7 @@ export const GetPushedTextOffset = () => { return 0; }; -export const GetTextWidth = (L) => { +export const GetTextWidth = (L: lua_State) => { const button = Button.getObjectFromStack(L); const { fontString } = button; diff --git a/src/ui/components/simple/Button.ts b/src/ui/components/simple/Button.ts index acf4108..67791a4 100644 --- a/src/ui/components/simple/Button.ts +++ b/src/ui/components/simple/Button.ts @@ -1,9 +1,14 @@ import DrawLayerType from '../../DrawLayerType'; -import Frame from './Frame'; import Script from '../../scripting/Script'; import UIContext from '../../UIContext'; +import XMLNode from '../../XMLNode'; +import { BlendMode } from '../../../gfx/types'; import ButtonState from './ButtonState'; +import FontString from './FontString'; +import Frame from './Frame'; +import Texture from './Texture'; + import * as scriptFunctions from './Button.script'; class Button extends Frame { @@ -14,8 +19,17 @@ class Button extends Frame { }; } - constructor(...args) { - super(...args); + textures: Record; + + activeTexture: Texture | null; + highlightTexture: Texture | null; + + fontString: FontString | null; + + state: ButtonState; + + constructor(parent: Frame | null) { + super(parent); this.scripts.register( new Script('PreClick', ['button', 'down']), @@ -38,7 +52,7 @@ class Button extends Frame { this.state = ButtonState.DISABLED; } - loadXML(node) { + loadXML(node: XMLNode) { super.loadXML(node); const ui = UIContext.instance; @@ -78,7 +92,7 @@ class Button extends Frame { // TODO: Text, click registration and motion scripts } - setHighlight(texture, _blendMode) { + setHighlight(texture: Texture, _blendMode: BlendMode | null) { if (this.highlightTexture === texture) { return; } @@ -95,7 +109,7 @@ class Button extends Frame { this.highlightTexture = texture; } - setStateTexture(state, texture) { + setStateTexture(state: ButtonState, texture: Texture) { const stored = this.textures[state]; if (stored === texture) { return; @@ -117,7 +131,7 @@ class Button extends Frame { if (texture && state === this.state) { this.activeTexture = texture; - // TODO: Show texture + texture.show(); } } } diff --git a/src/ui/components/simple/ButtonState.ts b/src/ui/components/simple/ButtonState.ts index e1bfdc6..9a0c559 100644 --- a/src/ui/components/simple/ButtonState.ts +++ b/src/ui/components/simple/ButtonState.ts @@ -1,5 +1,7 @@ -export default { - DISABLED: 0x0, - NORMAL: 0x1, - PUSHED: 0x2, -}; +enum ButtonState { + DISABLED = 0x0, + NORMAL = 0x1, + PUSHED = 0x2, +} + +export default ButtonState; diff --git a/src/ui/components/simple/EditBox.ts b/src/ui/components/simple/EditBox.ts index 66bea8b..51f46a7 100644 --- a/src/ui/components/simple/EditBox.ts +++ b/src/ui/components/simple/EditBox.ts @@ -11,8 +11,8 @@ class EditBox extends Frame { }; } - constructor(...args) { - super(...args); + constructor(parent: Frame | null) { + super(parent); this.scripts.register( new Script('OnEnterPressed'), diff --git a/src/ui/components/simple/FontString.ts b/src/ui/components/simple/FontString.ts index 3ff759d..5bd9ff0 100644 --- a/src/ui/components/simple/FontString.ts +++ b/src/ui/components/simple/FontString.ts @@ -1,3 +1,6 @@ +import RenderBatch from '../../rendering/RenderBatch'; +import XMLNode from '../../XMLNode'; + import Region from './Region'; import * as scriptFunctions from './FontString.script'; @@ -11,11 +14,11 @@ class FontString extends Region { }; } - postLoadXML(_node) { + postLoadXML(_node: XMLNode) { // TODO } - draw(_batch) { + draw(_batch: RenderBatch) { // TODO } } diff --git a/src/ui/components/simple/Frame.script.ts b/src/ui/components/simple/Frame.script.ts index f1a1d5d..9c025af 100644 --- a/src/ui/components/simple/Frame.script.ts +++ b/src/ui/components/simple/Frame.script.ts @@ -1,4 +1,5 @@ import { + lua_State, lua_pushnil, lua_pushnumber, } from '../../scripting/lua'; @@ -49,7 +50,7 @@ export const SetFrameStrata = () => { return 0; }; -export const GetFrameLevel = (L) => { +export const GetFrameLevel = (L: lua_State) => { const frame = Frame.getObjectFromStack(L); lua_pushnumber(L, frame.level); return 1; @@ -135,7 +136,7 @@ export const SetAlpha = () => { return 0; }; -export const GetID = (L) => { +export const GetID = (L: lua_State) => { const frame = Frame.getObjectFromStack(L); const { id } = frame; lua_pushnumber(L, id); @@ -162,7 +163,7 @@ export const DisableDrawLayer = () => { return 0; }; -export const Show = (L) => { +export const Show = (L: lua_State) => { const frame = Frame.getObjectFromStack(L); if (frame.protectedFunctionsAllowed) { frame.show(); @@ -172,7 +173,7 @@ export const Show = (L) => { return 0; }; -export const Hide = (L) => { +export const Hide = (L: lua_State) => { const frame = Frame.getObjectFromStack(L); if (frame.protectedFunctionsAllowed) { frame.hide(); @@ -186,7 +187,7 @@ export const IsVisible = () => { return 0; }; -export const IsShown = (L) => { +export const IsShown = (L: lua_State) => { const frame = Frame.getObjectFromStack(L); if (frame.shown) { lua_pushnumber(L, 1.0); diff --git a/src/ui/components/simple/Frame.ts b/src/ui/components/simple/Frame.ts index cb55aa5..cefd9b5 100644 --- a/src/ui/components/simple/Frame.ts +++ b/src/ui/components/simple/Frame.ts @@ -2,26 +2,33 @@ import Backdrop from '../Backdrop'; import DrawLayerType from '../../DrawLayerType'; import FrameStrataType from '../abstract/FrameStrataType'; import Region from './Region'; +import RenderBatch from '../../rendering/RenderBatch'; import UIRoot from '../UIRoot'; import Script from '../../scripting/Script'; import ScriptRegion from '../abstract/ScriptRegion'; import UIContext from '../../UIContext'; +import XMLNode from '../../XMLNode'; import { LinkedList, LinkedListLink, LinkedListNode, + Status, stringToBoolean, } from '../../../utils'; +import { Rect } from '../../../math'; import { stringToDrawLayerType, stringToStrataType, } from '../../utils'; import FrameFlag from './FrameFlag'; +import TitleRegion from './TitleRegion'; import * as scriptFunctions from './Frame.script'; class FrameNode extends LinkedListNode { - constructor(frame) { + frame: Frame; + + constructor(frame: Frame) { super(); this.frame = frame; @@ -36,11 +43,33 @@ class Frame extends ScriptRegion { }; } - constructor(parent) { - super(); + id: number; + flags: number; + titleRegion: TitleRegion | null; + + loading: boolean; + shown: boolean; + visible: boolean; + strataType: FrameStrataType; + level: number; + + layersEnabled: Record & Iterable; + backdrop: Backdrop | null; + + regions: LinkedList; + layers: Record>; + children: LinkedList; + + framesLink: LinkedListLink; + destroyedLink: LinkedListLink; + strataLink: LinkedListLink; + + constructor(parent: Frame | null) { + super(null); this.id = 0; this.flags = 0; + this.titleRegion = null; this.loading = false; this.shown = false; @@ -48,18 +77,24 @@ class Frame extends ScriptRegion { this.strataType = FrameStrataType.MEDIUM; this.level = 0; - this.layersEnabled = []; - this.layersEnabled[DrawLayerType.BACKGROUND] = true; - this.layersEnabled[DrawLayerType.BORDER] = true; - this.layersEnabled[DrawLayerType.ARTWORK] = true; - this.layersEnabled[DrawLayerType.OVERLAY] = true; - this.layersEnabled[DrawLayerType.HIGHLIGHT] = false; - - this.regions = LinkedList.of(Region, 'regionLink'); - this.layers = Object.values(DrawLayerType).map(() => ( - LinkedList.of(Region, 'layerLink') - )); - this.children = LinkedList.of(FrameNode); + this.layersEnabled = [ + true, + true, + true, + true, + false, + ]; + this.backdrop = null; + + this.regions = LinkedList.using('regionLink'); + this.layers = [ + LinkedList.using('layerLink'), + LinkedList.using('layerLink'), + LinkedList.using('layerLink'), + LinkedList.using('layerLink'), + LinkedList.using('layerLink'), + ]; + this.children = LinkedList.using('link'); this.framesLink = LinkedListLink.for(this); this.destroyedLink = LinkedListLink.for(this); @@ -95,7 +130,7 @@ class Frame extends ScriptRegion { this.show(); } - setFrameFlag(flag, on) { + setFrameFlag(flag: number, on: boolean) { if (on) { this.flags |= flag; } else { @@ -103,7 +138,7 @@ class Frame extends ScriptRegion { } } - setFrameLevel(level, shiftChildren = false) { + setFrameLevel(level: number, shiftChildren = false) { level = Math.max(level, 0); if (this.level === level) { @@ -131,7 +166,7 @@ class Frame extends ScriptRegion { } } - setFrameStrataType(strataType) { + setFrameStrataType(strataType: FrameStrataType) { if (this.strataType === strataType) { return; } @@ -196,7 +231,7 @@ class Frame extends ScriptRegion { } } - preLoadXML(node) { + preLoadXML(node: XMLNode) { super.preLoadXML(node); const id = node.attributes.get('id'); @@ -218,7 +253,7 @@ class Frame extends ScriptRegion { } } - loadXML(node) { + loadXML(node: XMLNode) { const dontSavePosition = node.attributes.get('dontSavePosition'); const frameLevel = node.attributes.get('frameLevel'); const frameStrata = node.attributes.get('frameStrata'); @@ -230,7 +265,7 @@ class Frame extends ScriptRegion { if (inherits) { const templates = UIContext.instance.templates.filterByList(inherits); - for (const template of templates) { + for (const { template } of templates) { if (template) { if (template.locked) { // TODO: Error handling @@ -306,11 +341,11 @@ class Frame extends ScriptRegion { const iname = child.name.toLowerCase(); switch (iname) { // TODO: TitleRegion, ResizeBounds & HitRectInsects - case 'backdrop': + case 'backdrop': { const backdrop = new Backdrop(); backdrop.loadXML(child); this.setBackdrop(backdrop); - break; + } break; case 'layers': this.loadXMLLayers(child); break; @@ -324,7 +359,7 @@ class Frame extends ScriptRegion { } } - loadXMLLayers(node) { + loadXMLLayers(node: XMLNode) { const ui = UIContext.instance; for (const layer of node.children) { @@ -341,14 +376,14 @@ class Frame extends ScriptRegion { for (const layerChild of layer.children) { const iname = layerChild.name.toLowerCase(); switch (iname) { - case 'texture': + case 'texture': { const texture = ui.createTexture(layerChild, this); texture.setFrame(this, drawLayerType, texture.shown); - break; - case 'fontstring': + } break; + case 'fontstring': { const fontstring = ui.createFontString(layerChild, this); fontstring.setFrame(this, drawLayerType, fontstring.shown); - break; + } break; default: // TODO: Error handling } @@ -356,7 +391,7 @@ class Frame extends ScriptRegion { } } - loadXMLScripts(node) { + loadXMLScripts(node: XMLNode) { for (const child of node.children) { const script = this.scripts.get(child.name); if (script) { @@ -370,7 +405,7 @@ class Frame extends ScriptRegion { } } - postLoadXML(node, status) { + postLoadXML(node: XMLNode, status: Status) { this.loading = false; // TODO: More stuff @@ -398,16 +433,16 @@ class Frame extends ScriptRegion { } } - postLoadXMLFrames(node) { + postLoadXMLFrames(node: XMLNode, status: Status) { const ui = UIContext.instance; const inherits = node.attributes.get('inherits'); if (inherits) { const templates = ui.templates.filterByList(inherits); - for (const template of templates) { + for (const { template } of templates) { if (template && !template.locked) { template.lock(); - this.postLoadXMLFrames(template.node); + this.postLoadXMLFrames(template.node, status); template.release(); } } @@ -416,12 +451,12 @@ class Frame extends ScriptRegion { const frames = node.getChildByName('Frames'); if (frames) { for (const frame of frames.children) { - ui.createFrame(frame, this); + ui.createFrame(frame, this, status); } } } - setBackdrop(backdrop) { + setBackdrop(backdrop: Backdrop) { if (this.backdrop) { // TODO: Destructor } @@ -524,7 +559,7 @@ class Frame extends ScriptRegion { return true; } - addRegion(region, drawLayerType) { + addRegion(region: Region, drawLayerType: DrawLayerType) { // TODO: Layout scaling console.debug(`adding ${region.name} as frame region to ${this.name} on layer ${drawLayerType}`); @@ -533,12 +568,12 @@ class Frame extends ScriptRegion { this.notifyDrawLayerChanged(drawLayerType); } - removeRegion(region, drawLayerType) { + removeRegion(region: Region, drawLayerType: DrawLayerType) { this.layers[drawLayerType].unlink(region); this.notifyDrawLayerChanged(drawLayerType); } - notifyDrawLayerChanged(drawLayerType) { + notifyDrawLayerChanged(drawLayerType: DrawLayerType) { const root = UIRoot.instance; // TODO: Constantize frame flag @@ -551,7 +586,7 @@ class Frame extends ScriptRegion { // TODO: Notify scroll parent } - onFrameRender(batch) { + onFrameRender(batch: RenderBatch) { const { drawLayerType } = batch; if (!this.layersEnabled[drawLayerType]) { @@ -563,7 +598,7 @@ class Frame extends ScriptRegion { } } - onFrameSizeChanged(rect) { + onFrameSizeChanged(rect: Rect) { super.onFrameSizeChanged(rect); // TODO: Set hit rect @@ -583,7 +618,7 @@ class Frame extends ScriptRegion { this.runOnHideScript(); } - onLayerUpdate(_elapsedSecs) { + onLayerUpdate(_elapsedSecs: number) { // TODO: Run update script // TODO: Run PreOnAnimUpdate hooks diff --git a/src/ui/components/simple/FrameFlag.ts b/src/ui/components/simple/FrameFlag.ts index 2bda77a..6852fa3 100644 --- a/src/ui/components/simple/FrameFlag.ts +++ b/src/ui/components/simple/FrameFlag.ts @@ -1,6 +1,7 @@ // TODO: Are these shared between Frame and LayoutFrame flags? export default { TOPLEVEL: 0x1, + OCCLUDED: 0x10, MOVABLE: 0x100, RESIZABLE: 0x200, DONT_SAVE_POSITION: 0x80000, diff --git a/src/ui/components/simple/Region.ts b/src/ui/components/simple/Region.ts index c06c5f8..b35977f 100644 --- a/src/ui/components/simple/Region.ts +++ b/src/ui/components/simple/Region.ts @@ -1,9 +1,23 @@ +import DrawLayerType from '../../DrawLayerType'; +import RenderBatch from '../../rendering/RenderBatch'; import ScriptRegion from '../abstract/ScriptRegion'; import { LinkedListLink } from '../../../utils'; -class Region extends ScriptRegion { - constructor(frame, drawLayerType, show) { - super(); +import Frame from './Frame'; + +abstract class Region extends ScriptRegion { + drawLayerType: DrawLayerType | null; + shown: boolean; + visible: boolean; + + colors: never[]; + alphas: never[]; + + layerLink: LinkedListLink; + regionLink: LinkedListLink; + + constructor(frame: Frame, drawLayerType: DrawLayerType, show: boolean) { + super(null); this.drawLayerType = null; this.shown = false; @@ -20,11 +34,13 @@ class Region extends ScriptRegion { } } + abstract draw(_batch: RenderBatch): void; + onRegionChanged() { // TODO: Implementation } - setFrame(frame, drawLayerType, show) { + setFrame(frame: Frame, drawLayerType: DrawLayerType, show: boolean) { if (this._parent === frame) { if (this.drawLayerType === drawLayerType) { if (show !== this.shown) { @@ -56,7 +72,7 @@ class Region extends ScriptRegion { if (frame) { frame.regions.add(this); - this.deferredResize = (this._parent.layoutFlags & 0x2); + this.deferredResize = !!(this._parent.layoutFlags & 0x2); // TODO: Color changed @@ -79,7 +95,7 @@ class Region extends ScriptRegion { if (!this._parent.loading) { this.deferredResize = true; } - this._parent.removeRegion(this, this.drawLayerType); + this._parent.removeRegion(this, this.drawLayerType!); this.visible = false; } } @@ -95,8 +111,8 @@ class Region extends ScriptRegion { this.deferredResize = false; } - this._parent.addRegion(this, this.drawLayerType); - this.visible = 1; + this._parent.addRegion(this, this.drawLayerType!); + this.visible = true; } } } diff --git a/src/ui/components/simple/ScrollFrame.script.ts b/src/ui/components/simple/ScrollFrame.script.ts index 3202993..7d691f3 100644 --- a/src/ui/components/simple/ScrollFrame.script.ts +++ b/src/ui/components/simple/ScrollFrame.script.ts @@ -1,4 +1,4 @@ -import { lua_pushnumber } from '../../scripting/lua'; +import { lua_State, lua_pushnumber } from '../../scripting/lua'; export const SetScrollChild = () => { return 0; @@ -28,7 +28,7 @@ export const GetHorizontalScrollRange = () => { return 0; }; -export const GetVerticalScrollRange = (L) => { +export const GetVerticalScrollRange = (L: lua_State) => { // TODO: Implementation lua_pushnumber(L, 0); return 1; diff --git a/src/ui/components/simple/ScrollFrame.ts b/src/ui/components/simple/ScrollFrame.ts index 76406de..3d7eb89 100644 --- a/src/ui/components/simple/ScrollFrame.ts +++ b/src/ui/components/simple/ScrollFrame.ts @@ -1,6 +1,8 @@ -import Frame from './Frame'; import Script from '../../scripting/Script'; import UIContext from '../../UIContext'; +import XMLNode from '../../XMLNode'; + +import Frame from './Frame'; import * as scriptFunctions from './ScrollFrame.script'; @@ -12,8 +14,10 @@ class ScrollFrame extends Frame { }; } - constructor(...args) { - super(...args); + scrollChild: Frame | null; + + constructor(parent: Frame | null) { + super(parent); this.scrollChild = null; @@ -24,7 +28,7 @@ class ScrollFrame extends Frame { ); } - loadXML(node) { + loadXML(node: XMLNode) { super.loadXML(node); const scrollChild = node.getChildByName('ScrollChild'); diff --git a/src/ui/components/simple/Slider.script.ts b/src/ui/components/simple/Slider.script.ts index 5f696ac..b0ff4e9 100644 --- a/src/ui/components/simple/Slider.script.ts +++ b/src/ui/components/simple/Slider.script.ts @@ -1,4 +1,4 @@ -import { lua_pushnumber } from '../../scripting/lua'; +import { lua_State, lua_pushnumber } from '../../scripting/lua'; import Slider from './Slider'; @@ -26,7 +26,7 @@ export const SetMinMaxValues = () => { return 0; }; -export const GetValue = (L) => { +export const GetValue = (L: lua_State) => { const slider = Slider.getObjectFromStack(L); const { value } = slider; lua_pushnumber(L, value); diff --git a/src/ui/components/simple/Slider.ts b/src/ui/components/simple/Slider.ts index 1c96365..2f6a6d4 100644 --- a/src/ui/components/simple/Slider.ts +++ b/src/ui/components/simple/Slider.ts @@ -11,8 +11,10 @@ class Slider extends Frame { }; } - constructor(...args) { - super(...args); + value: number; + + constructor(parent: Frame | null) { + super(parent); this.value = 0.0; diff --git a/src/ui/components/simple/Texture.ts b/src/ui/components/simple/Texture.ts index 853630d..6e4fa3c 100644 --- a/src/ui/components/simple/Texture.ts +++ b/src/ui/components/simple/Texture.ts @@ -1,10 +1,15 @@ import Client from '../../../Client'; import Device from '../../../gfx/Device'; +import DrawLayerType from '../../DrawLayerType'; +import GfxTexture from '../../../gfx/Texture'; import Region from './Region'; -import TextureFlags from '../../../gfx/TextureFlags'; +import RenderBatch from '../../rendering/RenderBatch'; +import Shader from '../../../gfx/Shader'; import UIContext from '../../UIContext'; +import WebGL2Device from '../../../gfx/apis/webgl2/WebGL2Device'; +import XMLNode from '../../XMLNode'; import { BlendMode } from '../../../gfx/types'; -import { Vector2, Vector3 } from '../../../math'; +import { Rect, Vector2, Vector3 } from '../../../math'; import { stringToBlendMode } from '../../utils'; import { NDCtoDDCHeight, @@ -14,10 +19,19 @@ import { stringToFloat, } from '../../../utils'; -import ImageMode from './TextureImageMode'; +import Frame from './Frame'; +import TextureImageMode from './TextureImageMode'; import * as scriptFunctions from './Texture.script'; +type TexturePosition = readonly [Vector3, Vector3, Vector3, Vector3]; +type TextureCoords = readonly [Vector2, Vector2, Vector2, Vector2]; + class Texture extends Region { + static indices = [ + 0, 1, 2, + 2, 1, 3, + ]; + static get scriptFunctions() { return { ...super.scriptFunctions, @@ -25,7 +39,16 @@ class Texture extends Region { }; } - constructor(frame, drawLayerType, show) { + blendMode: BlendMode; + position: TexturePosition; + shader: Shader; + texture: GfxTexture | null; + textureCoords: TextureCoords; + tileHorizontally: boolean; + tileVertically: boolean; + updateTextureCoords: boolean; + + constructor(frame: Frame, drawLayerType: DrawLayerType, show: boolean) { super(frame, drawLayerType, show); this.blendMode = BlendMode.Alpha; @@ -35,7 +58,7 @@ class Texture extends Region { new Vector3(), new Vector3(), ]; - this.shader = Device.instance.shaders.pixelShaderFor(ImageMode.UI); + this.shader = (Device.instance as WebGL2Device).shaders.pixelShaderFor(TextureImageMode.UI); this.texture = null; this.textureCoords = [ new Vector2([0, 0]), @@ -88,7 +111,7 @@ class Texture extends Region { super.height = height; } - loadXML(node) { + loadXML(node: XMLNode) { const alphaMode = node.attributes.get('alphaMode'); const file = node.attributes.get('file'); const hidden = node.attributes.get('hidden'); @@ -137,7 +160,7 @@ class Texture extends Region { for (const child of node.children) { const iname = child.name.toLowerCase(); switch (iname) { - case 'texcoords': + case 'texcoords': { let valid = true; const rect = { @@ -155,7 +178,7 @@ class Texture extends Region { continue; } - for (const side of Object.keys(rect)) { + for (const side of Object.keys(rect) as (keyof typeof rect)[]) { const attr = child.attributes.get(side); if (attr) { if ( @@ -181,7 +204,7 @@ class Texture extends Region { new Vector2([rect.left, rect.bottom]), new Vector2([rect.right, rect.top]), new Vector2([rect.right, rect.bottom]), - ]; + ] as const; this.setTextureCoords(coords); @@ -195,7 +218,7 @@ class Texture extends Region { || rect.bottom < 0 || rect.bottom > 1 ); } - break; + } break; case 'color': // TODO: Color break; @@ -207,7 +230,7 @@ class Texture extends Region { if (file) { // TODO: Handle all arguments correctly - const success = this.setTexture(file, wrapU, wrapV, null, ImageMode.UI); + const success = this.setTexture(file, wrapU, wrapV, null, TextureImageMode.UI); if (success) { // TODO: Set colors } else { @@ -225,11 +248,11 @@ class Texture extends Region { // TODO: Non-blocking } - postLoadXML(_node) { + postLoadXML(_node: XMLNode) { // TODO } - onFrameSizeChanged(rect) { + onFrameSizeChanged(rect: Rect) { super.onFrameSizeChanged(rect); this.setPosition(this.rect); @@ -242,7 +265,7 @@ class Texture extends Region { // TODO: Notify scroll parent } - setBlendMode(blendMode) { + setBlendMode(blendMode: BlendMode) { if (this.blendMode === blendMode) { return; } @@ -251,7 +274,7 @@ class Texture extends Region { this.onRegionChanged(); } - setPosition(rect) { + setPosition(rect: Rect) { this.position[0].setElements(rect.minX, rect.maxY, this.layoutDepth); this.position[1].setElements(rect.minX, rect.minY, this.layoutDepth); this.position[2].setElements(rect.maxX, rect.maxY, this.layoutDepth); @@ -261,17 +284,18 @@ class Texture extends Region { } // TODO: Create flags - setTexture(filename, wrapU, wrapV, filter, imageMode) { + setTexture(filename: string, _wrapU: boolean, _wrapV: boolean, _filter: unknown, imageMode: TextureImageMode) { let texture = null; if (filename) { // TODO: Remaining texture flags - const flags = new TextureFlags({ wrapU, wrapV }); - texture = Client.instance.textures.lookup(filename, flags); + // TODO: No longer accurate, needs a refactor + // TODO: const _flags = new TextureFlags(TextureFilter.Linear, wrapU, wrapV); + texture = Client.instance.textures.lookup(filename); // TODO: Verify texture - this.shader = Device.instance.shaders.pixelShaderFor(imageMode); + this.shader = (Device.instance as WebGL2Device).shaders.pixelShaderFor(imageMode); } // TODO: Texture cleanup @@ -281,24 +305,19 @@ class Texture extends Region { return true; } - setTextureCoords(coords) { + setTextureCoords(coords: TextureCoords) { this.textureCoords[0].set(coords[0]); this.textureCoords[1].set(coords[1]); this.textureCoords[2].set(coords[2]); this.textureCoords[3].set(coords[3]); } - draw(batch) { + draw(batch: RenderBatch) { if (this.texture) { batch.queueTexture(this); } } } -Texture.indices = [ - 0, 1, 2, - 2, 1, 3, -]; - export default Texture; -export { ImageMode as TextureImageMode }; +export { type TextureCoords, type TexturePosition }; diff --git a/src/ui/components/simple/TextureImageMode.ts b/src/ui/components/simple/TextureImageMode.ts index b8c2197..10a2238 100644 --- a/src/ui/components/simple/TextureImageMode.ts +++ b/src/ui/components/simple/TextureImageMode.ts @@ -1,4 +1,6 @@ -export default { - UI: 'UI', - DESATURATE: 'Desaturate', -}; +enum TextureImageMode { + UI = 'UI', + DESATURATE = 'Desaturate', +} + +export default TextureImageMode; diff --git a/src/ui/components/simple/TitleRegion.ts b/src/ui/components/simple/TitleRegion.ts new file mode 100644 index 0000000..88ea335 --- /dev/null +++ b/src/ui/components/simple/TitleRegion.ts @@ -0,0 +1,6 @@ +import ScriptRegion from '../abstract/ScriptRegion'; + +class TitleRegion extends ScriptRegion { +} + +export default TitleRegion; diff --git a/src/ui/rendering/RenderBatch.ts b/src/ui/rendering/RenderBatch.ts index 62cfc6a..193e0e4 100644 --- a/src/ui/rendering/RenderBatch.ts +++ b/src/ui/rendering/RenderBatch.ts @@ -1,4 +1,8 @@ -import Texture from '../../ui/components/simple/Texture'; +import DrawLayerType from '../DrawLayerType'; +import GfxTexture from '../../gfx/Texture'; +import Shader from '../../gfx/Shader'; +import Texture, { TextureCoords, TexturePosition } from '../../ui/components/simple/Texture'; +import { BlendMode } from '../../gfx/types'; import { DDCtoNDCHeight, DDCtoNDCWidth, @@ -10,7 +14,14 @@ import { Rect } from '../../math'; import RenderMesh from './RenderMesh'; class RenderBatch { - constructor(drawLayerType) { + drawLayerType: DrawLayerType; + + meshes: RenderMesh[]; + count: number; + + renderLink: LinkedListLink; + + constructor(drawLayerType: DrawLayerType) { this.drawLayerType = drawLayerType; this.meshes = []; @@ -34,15 +45,12 @@ class RenderBatch { // TODO: Too many arguments, potentially use options (may impact performance) queue( - texture, blendMode, position, textureCoords, - colors, indices, shader, + texture: GfxTexture, blendMode: BlendMode, position: TexturePosition, textureCoords: TextureCoords, + colors: never[], indices: number[], shader: Shader, ) { - const mesh = new RenderMesh(); - mesh.texture = texture; + const mesh = new RenderMesh(texture, position, textureCoords); mesh.blendMode = blendMode; mesh.shader = shader; - mesh.position = position; - mesh.textureCoords = textureCoords; mesh.colors = colors; mesh.indices = indices; // TODO: Atlas implementation @@ -51,8 +59,8 @@ class RenderBatch { ++this.count; } - queueTexture(texture) { - if (!texture.texture.isLoaded) { + queueTexture(texture: Texture) { + if (!texture.texture?.isLoaded) { return; } @@ -62,7 +70,7 @@ class RenderBatch { // TODO: Check whether texture coords need updating if (texture.updateTextureCoords) { - const { texCoords } = texture; + const { textureCoords } = texture; const rect = new Rect({ minY: texture.position[1].y, @@ -77,20 +85,20 @@ class RenderBatch { const ndcWidth = DDCtoNDCWidth(ddcWidth); if (width && ndcWidth > 0) { - if (texCoords[0].x !== 0) { - texCoords[0].x = ndcWidth / width; + if (textureCoords[0].x !== 0) { + textureCoords[0].x = ndcWidth / width; } - if (texCoords[1].x !== 0) { - texCoords[1].x = ndcWidth / width; + if (textureCoords[1].x !== 0) { + textureCoords[1].x = ndcWidth / width; } - if (texCoords[2].x !== 0) { - texCoords[2].x = ndcWidth / width; + if (textureCoords[2].x !== 0) { + textureCoords[2].x = ndcWidth / width; } - if (texCoords[3].x !== 0) { - texCoords[3].x = ndcWidth / width; + if (textureCoords[3].x !== 0) { + textureCoords[3].x = ndcWidth / width; } } } @@ -101,26 +109,26 @@ class RenderBatch { const ndcHeight = DDCtoNDCHeight(ddcHeight); if (height && ndcHeight > 0.0) { - if (texCoords[0].y !== 0.0) { - texCoords[0].y = ndcHeight / height; + if (textureCoords[0].y !== 0.0) { + textureCoords[0].y = ndcHeight / height; } - if (texCoords[1].y !== 0.0) { - texCoords[1].y = ndcHeight / height; + if (textureCoords[1].y !== 0.0) { + textureCoords[1].y = ndcHeight / height; } - if (texCoords[2].y !== 0.0) { - texCoords[2].y = ndcHeight / height; + if (textureCoords[2].y !== 0.0) { + textureCoords[2].y = ndcHeight / height; } - if (texCoords[3].y !== 0.0) { - texCoords[3].y = ndcHeight / height; + if (textureCoords[3].y !== 0.0) { + textureCoords[3].y = ndcHeight / height; } } } texture.updateTextureCoords = false; - texture.setTextureCoords(texCoords); + texture.setTextureCoords(textureCoords); } this.queue( diff --git a/src/ui/rendering/RenderMesh.ts b/src/ui/rendering/RenderMesh.ts index 09f460d..b624739 100644 --- a/src/ui/rendering/RenderMesh.ts +++ b/src/ui/rendering/RenderMesh.ts @@ -1,18 +1,30 @@ +import Shader from '../../gfx/Shader'; +import Texture from '../../gfx/Texture'; import { BlendMode } from '../../gfx/types'; +import { TextureCoords, TexturePosition } from '../components/simple/Texture'; class RenderMesh { - constructor() { - this.texture = null; + texture: Texture; + _blendMode: BlendMode | null; + shader: Shader | null; + position: TexturePosition; + textureCoords: TextureCoords; + colors: never[]; + indices: number[]; + + constructor(texture: Texture, position: TexturePosition, textureCoords: TextureCoords) { + this.texture = texture; this._blendMode = null; this.shader = null; - this.position = null; - this.textureCoords = []; + this.position = position; + this.textureCoords = textureCoords; this.colors = []; this.indices = []; } get blendMode() { - if (this.colors.length && this.blendMode < BlendMode.Alpha) { + // TODO: This comparison might be incorrect + if (this.colors.length && this.blendMode! < BlendMode.Alpha) { return BlendMode.Alpha; } else { return this._blendMode; diff --git a/src/ui/rendering/Renderer.ts b/src/ui/rendering/Renderer.ts index 8cbfd6c..ecea507 100644 --- a/src/ui/rendering/Renderer.ts +++ b/src/ui/rendering/Renderer.ts @@ -1,21 +1,28 @@ import Device from '../../gfx/Device'; +import Shader from '../../gfx/Shader'; import UIRoot from '../components/UIRoot'; +import WebGL2Device from '../../gfx/apis/webgl2/WebGL2Device'; import { Matrix4 } from '../../math'; +import RenderBatch from './RenderBatch'; + +// TODO: Device agnostic (not bound to WebGL2Device) class Renderer { + vertexShader: Shader; + pixelShader: Shader; + program: WebGLProgram | null; + constructor() { // TODO: Does not support stereo vertex shader yet - this.vertexShader = Device.instance.shaders.shaderFor('vertex', 'UI'); - this.pixelShader = Device.instance.shaders.shaderFor('pixel', 'UI'); + this.vertexShader = (Device.instance as WebGL2Device).shaders.shaderFor('vertex', 'UI'); + this.pixelShader = (Device.instance as WebGL2Device).shaders.shaderFor('pixel', 'UI'); this.program = null; } - draw(batch) { - // TODO: Drawing routine should use WebGL2Device - + draw(batch: RenderBatch) { const root = UIRoot.instance; - const { gl } = Device.instance; + const { gl } = (Device.instance as WebGL2Device); const { pixelShader, vertexShader } = this; if (!this.program) { @@ -23,9 +30,9 @@ class Renderer { return; } - this.program = gl.createProgram(); - gl.attachShader(this.program, vertexShader.apiShader); - gl.attachShader(this.program, pixelShader.apiShader); + this.program = gl.createProgram()!; + gl.attachShader(this.program, vertexShader.apiShader!); + gl.attachShader(this.program, pixelShader.apiShader!); gl.linkProgram(this.program); const success = gl.getProgramParameter(this.program, gl.LINK_STATUS); diff --git a/src/ui/scripting/FrameScriptObject.ts b/src/ui/scripting/FrameScriptObject.ts index 1b717bc..439bcfd 100644 --- a/src/ui/scripting/FrameScriptObject.ts +++ b/src/ui/scripting/FrameScriptObject.ts @@ -1,9 +1,11 @@ -import ScriptingContext from './ScriptingContext'; +import ScriptingContext, { ScriptFunction } from './ScriptingContext'; import Script from './Script'; import ScriptRegistry from './ScriptRegistry'; import { LUA_REGISTRYINDEX, LUA_TTABLE, + lua_Ref, + lua_State, lua_createtable, lua_getglobal, lua_pushcclosure, @@ -21,12 +23,16 @@ import { luaL_error, luaL_ref, } from './lua'; +import { This, ThisConstructor } from '../../utils'; -const scriptMetaTables = new Map(); -const objectTypes = new Map(); +const scriptMetaTables = new Map(); +const objectTypes = new Map(); class FrameScriptObject { - constructor() { + luaRef: lua_Ref | null; + scripts: ScriptRegistry; + + constructor(_dummy: unknown) { this.luaRef = null; this.scripts = new ScriptRegistry(); @@ -39,7 +45,7 @@ class FrameScriptObject { return this.luaRef !== null; } - register(name = null) { + register(name: string | null = null) { const L = ScriptingContext.instance.state; if (!this.isLuaRegistered) { @@ -50,7 +56,7 @@ class FrameScriptObject { lua_pushlightuserdata(L, this); lua_rawset(L, -3); - const ref = this.constructor.scriptMetaTable; + const ref = (this.constructor as typeof FrameScriptObject).scriptMetaTable; lua_rawgeti(L, LUA_REGISTRYINDEX, ref); lua_setmetatable(L, -2); @@ -64,7 +70,7 @@ class FrameScriptObject { lua_settop(L, -2); if (!found) { - lua_rawgeti(L, LUA_REGISTRYINDEX, this.luaRef); + lua_rawgeti(L, LUA_REGISTRYINDEX, this.luaRef!); lua_setglobal(L, name); } } @@ -74,16 +80,20 @@ class FrameScriptObject { // TODO: Unregister } - runScript(name, argsCount = 0) { + runScript(name: string, argsCount = 0) { // TODO: This needs to be moved to the caller const script = this.scripts.get(name); if (script && script.luaRef) { // TODO: Pass in remaining arguments - ScriptingContext.instance.executeFunction(script.luaRef, this, argsCount); + ScriptingContext.instance.executeFunction(script.luaRef, this, argsCount, undefined, undefined); } } - static getObjectByName(name, type = this) { + static getObjectByName>( + this: T, + name: string, + type = this + ): This | null { const object = ScriptingContext.instance.getObjectByName(name); if (object && object instanceof type) { return object; @@ -91,10 +101,13 @@ class FrameScriptObject { return null; } - static getObjectFromStack(L) { + // Note: Throw (as `luaL_error` throws) otherwise TypeScript infers incorrect return type + static getObjectFromStack>( + this: T, + L: lua_State + ): This { if (lua_type(L, 1) !== LUA_TTABLE) { - luaL_error(L, "Attempt to find 'this' in non-table object (used '.' instead of ':' ?)"); - return null; + throw luaL_error(L, "Attempt to find 'this' in non-table object (used '.' instead of ':' ?)"); } lua_rawgeti(L, 1, 0); @@ -102,20 +115,18 @@ class FrameScriptObject { lua_settop(L, -2); if (!object) { - luaL_error(L, "Attempt to find 'this' in non-framescript object"); - return null; + throw luaL_error(L, "Attempt to find 'this' in non-framescript object"); } // TODO: Will this work in all scenarios? if (!(object instanceof this)) { - luaL_error(L, 'Wrong object type for member function'); - return null; + throw luaL_error(L, 'Wrong object type for member function'); } return object; } - static get scriptFunctions() { + static get scriptFunctions(): Record { return {}; } @@ -146,7 +157,7 @@ class FrameScriptObject { let type = objectTypes.get(this); if (!type) { type = objectTypes.size; - objectTypes.set(type, this); + objectTypes.set(this, type); } return type; } diff --git a/src/ui/scripting/Script.ts b/src/ui/scripting/Script.ts index a3ac9b9..9775c6c 100644 --- a/src/ui/scripting/Script.ts +++ b/src/ui/scripting/Script.ts @@ -1,6 +1,9 @@ +import XMLNode from '../../ui/XMLNode'; + import ScriptingContext from './ScriptingContext'; import { LUA_REGISTRYINDEX, + lua_Ref, lua_getglobal, lua_rawgeti, luaL_ref, @@ -8,10 +11,16 @@ import { } from './lua'; class Script { - constructor(name, args = []) { + name: string; + args: string[]; + luaRef: lua_Ref | null; + source: string | null; + + constructor(name: string, args: string[] = []) { this.name = name; this.args = ['self', ...args]; this.luaRef = null; + this.source = null; } get wrapper() { @@ -20,7 +29,7 @@ class Script { // Activates script using given node // TODO: Handle status - loadXML(node) { + loadXML(node: XMLNode) { const { body } = node; const scripting = ScriptingContext.instance; diff --git a/src/ui/scripting/ScriptRegistry.ts b/src/ui/scripting/ScriptRegistry.ts index a04016a..1daceb2 100644 --- a/src/ui/scripting/ScriptRegistry.ts +++ b/src/ui/scripting/ScriptRegistry.ts @@ -1,11 +1,14 @@ import { HashMap, HashStrategy } from '../../utils'; -class ScriptRegistry extends HashMap { +import Script from './Script'; + +// TODO: Add generic constraint to allow only relevant scripts +class ScriptRegistry extends HashMap { constructor() { super(HashStrategy.UPPERCASE); } - register(...scripts) { + register(...scripts: Script[]) { for (const script of scripts) { this.set(script.name, script); } diff --git a/src/ui/scripting/ScriptingContext.ts b/src/ui/scripting/ScriptingContext.ts index c58ada0..0cc218c 100644 --- a/src/ui/scripting/ScriptingContext.ts +++ b/src/ui/scripting/ScriptingContext.ts @@ -2,6 +2,8 @@ import { LUA_REGISTRYINDEX, LUA_TTABLE, lua_Debug, + lua_Ref, + lua_State, lua_atnativeerror, lua_call, lua_createtable, @@ -34,15 +36,27 @@ import { import bitLua from './vendor/bit.lua'; import compatLua from './vendor/compat.lua'; +import FrameScriptObject from './FrameScriptObject'; + import * as extraScriptFunctions from './globals/extra'; import * as frameScriptFunctions from './globals/frame'; import * as sharedScriptFunctions from './globals/shared'; import * as soundScriptFunctions from './globals/sound'; import * as systemScriptFunctions from './globals/system'; +type ScriptFunction = (L: lua_State) => number; + class ScriptingContext { + static instance: ScriptingContext; + + errorHandlerFunc: lua_Ref | null; + handlingError: boolean; + state: lua_State; + errorHandlerRef: lua_Ref; + recursiveTableHash: lua_Ref; + constructor() { - this.constructor.instance = this; + ScriptingContext.instance = this; this.errorHandlerFunc = null; this.handlingError = false; @@ -73,7 +87,7 @@ class ScriptingContext { this.execute(compatLua, 'compat.lua'); } - compileFunction(source, name = '') { + compileFunction(source: string, name = '') { const L = this.state; lua_rawgeti(L, LUA_REGISTRYINDEX, this.errorHandlerRef); @@ -99,7 +113,7 @@ class ScriptingContext { return luaRef; } - execute(source, filename = '') { + execute(source: string, filename = '') { console.groupCollapsed('executing', filename); console.log(source.slice(0, 500)); @@ -121,12 +135,12 @@ class ScriptingContext { lua_settop(L, -2); } - console.groupEnd('executing', filename); + console.groupEnd(); return true; } - executeFunction(functionRef, thisArg, givenArgsCount, _unk, _event) { + executeFunction(functionRef: lua_Ref, thisArg: FrameScriptObject, givenArgsCount: number, _unk: unknown, _event: unknown) { const L = this.state; let argsCount = givenArgsCount; @@ -137,11 +151,11 @@ class ScriptingContext { lua_rawgeti(L, LUA_REGISTRYINDEX, functionRef); if (thisArg) { - if (!thisArg.luaRegistered) { + if (!thisArg.isLuaRegistered) { thisArg.register(); } - lua_rawgeti(L, LUA_REGISTRYINDEX, thisArg.luaRef); + lua_rawgeti(L, LUA_REGISTRYINDEX, thisArg.luaRef!); argsCount++; } @@ -158,7 +172,7 @@ class ScriptingContext { lua_settop(L, -1 - givenArgsCount); } - getObjectByName(name) { + getObjectByName(name: string) { const L = this.state; lua_getglobal(L, name); @@ -174,7 +188,7 @@ class ScriptingContext { return null; } - getObjectAt(index) { + getObjectAt(index: number) { const L = this.state; const info = new lua_Debug(); @@ -212,7 +226,7 @@ class ScriptingContext { return 1; } - onError(L) { + onError(L: lua_State) { if (lua_isuserdata(L, -1)) { const e = lua_touserdata(L, -1); console.error(e); @@ -251,13 +265,13 @@ class ScriptingContext { return 1; } - registerFunctions(object) { + registerFunctions(object: Record) { for (const [name, func] of Object.entries(object)) { this.registerFunction(name, func); } } - registerFunction(name, func) { + registerFunction(name: string, func: ScriptFunction) { const L = this.state; lua_pushcclosure(L, func, 0); lua_setglobal(L, name); @@ -265,3 +279,4 @@ class ScriptingContext { } export default ScriptingContext; +export { type ScriptFunction }; diff --git a/src/ui/scripting/globals/extra.ts b/src/ui/scripting/globals/extra.ts index 129971f..f534005 100644 --- a/src/ui/scripting/globals/extra.ts +++ b/src/ui/scripting/globals/extra.ts @@ -2,6 +2,7 @@ import ScriptingContext from '../ScriptingContext'; import { LUA_REGISTRYINDEX, LUA_TFUNCTION, + lua_State, lua_type, luaL_error, luaL_ref, @@ -96,17 +97,17 @@ export const debugprofilestop = () => { return 0; }; -export const seterrorhandler = (L) => { +export const seterrorhandler = (L: lua_State) => { if (lua_type(L, 1) !== LUA_TFUNCTION) { luaL_error(L, 'Usage: seterrorhandler(errfunc)'); return 0; } const scripting = ScriptingContext.instance; - if (scripting.errorHandlerRef) { - luaL_unref(L, LUA_REGISTRYINDEX, scripting.errorHandlerRef); + if (scripting.errorHandlerFunc) { + luaL_unref(L, LUA_REGISTRYINDEX, scripting.errorHandlerFunc); } - scripting.errorHandlerRef = luaL_ref(L, LUA_REGISTRYINDEX); + scripting.errorHandlerFunc = luaL_ref(L, LUA_REGISTRYINDEX); return 0; }; diff --git a/src/ui/scripting/globals/frame.ts b/src/ui/scripting/globals/frame.ts index d12990c..0294922 100644 --- a/src/ui/scripting/globals/frame.ts +++ b/src/ui/scripting/globals/frame.ts @@ -4,6 +4,7 @@ import XMLNode from '../../XMLNode'; import { LUA_REGISTRYINDEX, LUA_TTABLE, + lua_State, lua_isnumber, lua_isstring, lua_rawgeti, @@ -32,16 +33,16 @@ export const CreateFont = () => { return 0; }; -export const CreateFrame = (L) => { +export const CreateFrame = (L: lua_State) => { const type = lua_type(L, 3); if (!lua_isstring(L, 1) || (type !== -1 && type && type !== LUA_TTABLE)) { luaL_error(L, 'Usage: CreateFrame("frameType" [, "name"] [, parent] [, "template"] [, id])'); return 0; } - const frameType = lua_tojsstring(L, 1, 0); - const name = lua_tojsstring(L, 2, 0); - const inherits = lua_tojsstring(L, 4, 0); + const frameType = lua_tojsstring(L, 1); + const name = lua_tojsstring(L, 2); + const inherits = lua_tojsstring(L, 4); let parent = null; @@ -65,13 +66,13 @@ export const CreateFrame = (L) => { // Verify all inherited templates exist if (inherits) { const templates = UIContext.instance.templates.filterByList(inherits); - for (const template of templates) { + for (const { name, template } of templates) { if (!template) { - luaL_error(L, `CreateFrame: Could not find inherited node '${template.name}'`); + return luaL_error(L, `CreateFrame: Could not find inherited node '${name}'`); } if (template.locked) { - luaL_error(L, `CreateFrame: Recursively inherited node '${template.name}'`); + return luaL_error(L, `CreateFrame: Recursively inherited node '${template.name}'`); } } } @@ -92,11 +93,11 @@ export const CreateFrame = (L) => { } if (lua_isstring(L, 5)) { - const id = lua_tojsstring(L, 5, 0); + const id = lua_tojsstring(L, 5); attributes.set('id', id); } else if (lua_isnumber(L, 5)) { const id = lua_tonumber(L, 5); - attributes.set('id', id); + attributes.set('id', `${id}`); } const status = new Status(); @@ -114,7 +115,7 @@ export const CreateFrame = (L) => { } // Return a reference to the instance - lua_rawgeti(L, LUA_REGISTRYINDEX, frame.luaRef); + lua_rawgeti(L, LUA_REGISTRYINDEX, frame.luaRef!); return 1; }; diff --git a/src/ui/scripting/globals/glue/character-select.ts b/src/ui/scripting/globals/glue/character-select.ts index 1e72779..c19df03 100644 --- a/src/ui/scripting/globals/glue/character-select.ts +++ b/src/ui/scripting/globals/glue/character-select.ts @@ -1,4 +1,4 @@ -import { lua_pushnumber } from '../../../scripting/lua'; +import { lua_State, lua_pushnumber } from '../../../scripting/lua'; export const SetCharSelectModelFrame = () => { return 0; @@ -12,7 +12,7 @@ export const GetCharacterListUpdate = () => { return 0; }; -export const GetNumCharacters = (L) => { +export const GetNumCharacters = (L: lua_State) => { // TODO: Actually calculate number of characters lua_pushnumber(L, 0); return 1; diff --git a/src/ui/scripting/globals/glue/shared.ts b/src/ui/scripting/globals/glue/shared.ts index 534c921..f4b53e5 100644 --- a/src/ui/scripting/globals/glue/shared.ts +++ b/src/ui/scripting/globals/glue/shared.ts @@ -5,6 +5,7 @@ import { maxAspectCompensation, } from '../../../../utils'; import { + lua_State, lua_pushboolean, lua_pushnumber, lua_pushstring, @@ -38,7 +39,7 @@ export const SetUsesToken = () => { return 0; }; -export const GetSavedAccountList = (L) => { +export const GetSavedAccountList = (L: lua_State) => { // TODO: Implementation lua_pushstring(L, ''); return 1; @@ -76,14 +77,14 @@ export const GetMovieResolution = () => { return 0; }; -export const GetScreenWidth = (L) => { +export const GetScreenWidth = (L: lua_State) => { const ddcx = NDCtoDDCWidth(1.0); const ndcx = DDCtoNDCWidth(maxAspectCompensation * ddcx); lua_pushnumber(L, ndcx); return 1; }; -export const GetScreenHeight = (L) => { +export const GetScreenHeight = (L: lua_State) => { const ddcy = NDCtoDDCHeight(1.0); const ndcx = DDCtoNDCWidth(maxAspectCompensation * ddcy); lua_pushnumber(L, ndcx); @@ -98,7 +99,7 @@ export const ShowTOSNotice = () => { return 0; }; -export const TOSAccepted = (L) => { +export const TOSAccepted = (L: lua_State) => { // TODO: Implementation lua_pushboolean(L, 1); return 1; @@ -112,7 +113,7 @@ export const ShowEULANotice = () => { return 0; }; -export const EULAAccepted = (L) => { +export const EULAAccepted = (L: lua_State) => { // TODO: Implementation lua_pushboolean(L, 1); return 1; @@ -294,7 +295,7 @@ export const GetCreditsText = () => { return 0; }; -export const GetClientExpansionLevel = (L) => { +export const GetClientExpansionLevel = (L: lua_State) => { // TODO: Wrath of the Lich King lua_pushnumber(L, 3); return 1; @@ -324,7 +325,7 @@ export const ScanDLLContinueAnyway = () => { return 0; }; -export const IsScanDLLFinished = (L) => { +export const IsScanDLLFinished = (L: lua_State) => { // TODO: Implementation lua_pushboolean(L, 1); return 1; diff --git a/src/ui/scripting/lua.ts b/src/ui/scripting/lua.ts index 0d63564..1bdbb06 100644 --- a/src/ui/scripting/lua.ts +++ b/src/ui/scripting/lua.ts @@ -1,4 +1,6 @@ -import fengari from 'fengari'; +import fengari, { lua_State } from 'fengari'; + +type lua_Ref = number; const { lauxlib, @@ -72,11 +74,6 @@ const { const { luaL_openlibs, - luaopen_base, - luaopen_bit, - luaopen_math, - luaopen_string, - luaopen_table, } = lualib; export { @@ -91,6 +88,8 @@ export { LUA_TTABLE, LUA_TUSERDATA, lua_Debug, + type lua_Ref, + type lua_State, lua_atnativeerror, lua_call, lua_checkstack, @@ -137,11 +136,6 @@ export { luaL_openlibs, luaL_ref, luaL_unref, - luaopen_base, - luaopen_bit, - luaopen_math, - luaopen_string, - luaopen_table, to_jsstring, to_luastring, }; diff --git a/src/ui/utils.ts b/src/ui/utils.ts index 3bb07e3..5e84016 100644 --- a/src/ui/utils.ts +++ b/src/ui/utils.ts @@ -3,6 +3,17 @@ import { BlendMode } from '../gfx/types'; import DrawLayerType from './DrawLayerType'; import FrameStrataType from './components/abstract/FrameStrataType'; -export const stringToBlendMode = (str) => BlendMode[str.toUpperCase()]; -export const stringToDrawLayerType = (str) => DrawLayerType[str.toUpperCase()]; -export const stringToStrataType = (str) => FrameStrataType[str.toUpperCase()]; +export const stringToBlendMode = (string?: string) => { + if (!string) return undefined; + return BlendMode[string?.toUpperCase() as keyof typeof BlendMode]; +}; + +export const stringToDrawLayerType = (string?: string) => { + if (!string) return undefined; + return DrawLayerType[string?.toUpperCase() as keyof typeof DrawLayerType]; +}; + +export const stringToStrataType = (string?: string) => { + if (!string) return undefined; + return FrameStrataType[string?.toUpperCase() as keyof typeof FrameStrataType]; +}; diff --git a/src/utils/coordinates.ts b/src/utils/coordinates.ts index a3a6b69..f7c947f 100644 --- a/src/utils/coordinates.ts +++ b/src/utils/coordinates.ts @@ -1,10 +1,10 @@ -export let aspectRatio = null; -export let aspectCompensation = null; -export let maxAspectCompensation = null; -let x = null; -let y = null; +export let aspectRatio: number; +export let aspectCompensation: number; +export let maxAspectCompensation: number; +let x: number; +let y: number; -export const setAspectRatio = (newAspectRatio) => { +export const setAspectRatio = (newAspectRatio: number) => { aspectRatio = newAspectRatio; aspectCompensation = aspectRatio * 0.75; maxAspectCompensation = aspectCompensation * 1024.0; @@ -13,16 +13,16 @@ export const setAspectRatio = (newAspectRatio) => { x = aspectRatio * y; }; -export const DDCtoNDCWidth = ddcx => ddcx / x; -export const DDCtoNDCHeight = ddcy => ddcy / y; -export const DDCtoNDC = (ddcx, ddcy) => ({ +export const DDCtoNDCWidth = (ddcx: number) => ddcx / x; +export const DDCtoNDCHeight = (ddcy: number) => ddcy / y; +export const DDCtoNDC = (ddcx: number, ddcy: number) => ({ x: DDCtoNDCWidth(ddcx), y: DDCtoNDCHeight(ddcy), }); -export const NDCtoDDCWidth = ndcx => ndcx * x; -export const NDCtoDDCHeight = ndcy => ndcy * y; -export const NDCtoDDC = (ndcx, ndcy) => ({ +export const NDCtoDDCWidth = (ndcx: number) => ndcx * x; +export const NDCtoDDCHeight = (ndcy: number) => ndcy * y; +export const NDCtoDDC = (ndcx: number, ndcy: number) => ({ x: NDCtoDDCWidth(ndcx), y: NDCtoDDCHeight(ndcy), }); diff --git a/src/utils/datastructures/HashMap.ts b/src/utils/datastructures/HashMap.ts index 24f8375..9b6f824 100644 --- a/src/utils/datastructures/HashMap.ts +++ b/src/utils/datastructures/HashMap.ts @@ -1,32 +1,38 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + const HashStrategy = { - IDENTITY: key => key, - LOWERCASE: key => key.toLowerCase(), - UPPERCASE: key => key.toUpperCase(), -}; + IDENTITY: (key: any) => key, + LOWERCASE: (key: string) => key.toLowerCase(), + UPPERCASE: (key: string) => key.toUpperCase(), +} as const; + +type HashKeyStrategyFn = (_key: K) => K; + +class HashMap extends Map { + strategy: HashKeyStrategyFn; -class HashMap extends Map { constructor(strategy = HashStrategy.IDENTITY) { super(); this.strategy = strategy; } - delete(origKey) { + delete(origKey: K) { const key = this.strategy(origKey); return super.delete(key); } - get(origKey) { + get(origKey: K) { const key = this.strategy(origKey); return super.get(key); } - has(origKey) { + has(origKey: K) { const key = this.strategy(origKey); return super.has(key); } - set(origKey, value) { + set(origKey: K, value: V) { const key = this.strategy(origKey); return super.set(key, value); } diff --git a/src/utils/datastructures/LinkedList.ts b/src/utils/datastructures/LinkedList.ts index b1b477a..46a2fb3 100644 --- a/src/utils/datastructures/LinkedList.ts +++ b/src/utils/datastructures/LinkedList.ts @@ -5,9 +5,12 @@ const LinkStrategy = { BEFORE: 2, }; -class LinkedList { - constructor(type, propertyName = 'link') { - this.type = type; +class LinkedList { + propertyName: keyof T; + + sentinel: LinkedListLink; + + constructor(propertyName: keyof T) { this.propertyName = propertyName; this.sentinel = new LinkedListLink(); @@ -20,7 +23,7 @@ class LinkedList { } get head() { - return this.headLink.entity; + return this.headLink?.entity; } get tailLink() { @@ -28,7 +31,7 @@ class LinkedList { } get tail() { - return this.tailLink.entity; + return this.tailLink?.entity; } get size() { @@ -36,32 +39,32 @@ class LinkedList { let count = 0; while (link !== this.sentinel) { count++; - link = link.next; + link = link?.next ?? null; } return count; } - add(entity) { + add(entity: T) { this.linkToTail(entity); } - isLinked(entity) { + isLinked(entity: T) { return this.linkFor(entity).isLinked; } - linkToHead(entity) { + linkToHead(entity: T) { this.link(entity, LinkStrategy.AFTER); } - linkToTail(entity) { + linkToTail(entity: T) { this.link(entity, LinkStrategy.BEFORE); } - linkFor(entity) { - return entity[this.propertyName]; + linkFor(entity: T): LinkedListLink { + return entity[this.propertyName] as LinkedListLink; } - link(entity, strategy = LinkStrategy.BEFORE, other = null) { + link(entity: T, strategy = LinkStrategy.BEFORE, other?: T) { const link = this.linkFor(entity); if (link.isLinked) { link.unlink(); @@ -70,28 +73,32 @@ class LinkedList { const target = (other && this.linkFor(other)) || this.sentinel; switch (strategy) { - case LinkStrategy.BEFORE: + case LinkStrategy.BEFORE: { // From A - C, with target C, becomes A - B - C const prev = target.prev; - prev.next = link; + if (prev) { + prev.next = link; + } link.prev = prev; link.next = target; target.prev = link; - break; - case LinkStrategy.AFTER: + } break; + case LinkStrategy.AFTER: { // From A - C, with target A, becomes A - B - C const next = target.next; - next.prev = link; + if (next) { + next.prev = link; + } link.next = next; link.prev = target; target.next = link; - break; + } break; default: throw new Error(`Invalid link strategy: ${strategy}`); } } - unlink(entity) { + unlink(entity: T) { return this.linkFor(entity).unlink(); } @@ -100,14 +107,14 @@ class LinkedList { return { link: sentinel, next() { - this.link = this.link.next; - return { value: this.link.entity, done: this.link === sentinel }; + this.link = this.link?.next ?? this.link; + return { value: this.link.entity!, done: this.link === sentinel }; }, }; } - static of(type, propertyName = 'link') { - return new this(type, propertyName); + static using(propertyName: keyof T) { + return new this(propertyName); } } diff --git a/src/utils/datastructures/LinkedListLink.ts b/src/utils/datastructures/LinkedListLink.ts index e19fc77..a987b8a 100644 --- a/src/utils/datastructures/LinkedListLink.ts +++ b/src/utils/datastructures/LinkedListLink.ts @@ -1,5 +1,9 @@ -class LinkedListLink { - constructor(entity) { +class LinkedListLink { + entity?: T; + prev: LinkedListLink | null; + next: LinkedListLink | null; + + constructor(entity?: T) { this.entity = entity; this.prev = null; @@ -23,7 +27,7 @@ class LinkedListLink { this.next = null; } - static for(entity) { + static for(entity: T) { return new this(entity); } } diff --git a/src/utils/datastructures/LinkedListNode.ts b/src/utils/datastructures/LinkedListNode.ts index e5e5a54..a5f9504 100644 --- a/src/utils/datastructures/LinkedListNode.ts +++ b/src/utils/datastructures/LinkedListNode.ts @@ -1,6 +1,8 @@ import LinkedListLink from './LinkedListLink'; class LinkedListNode { + link: LinkedListLink; + constructor() { this.link = LinkedListLink.for(this); } diff --git a/src/utils/dimensions.ts b/src/utils/dimensions.ts index 334843d..a1a3c6a 100644 --- a/src/utils/dimensions.ts +++ b/src/utils/dimensions.ts @@ -1,11 +1,13 @@ /* eslint-disable import/prefer-default-export */ +import XMLNode from '../ui/XMLNode'; + import { NDCtoDDCWidth, maxAspectCompensation, } from './coordinates'; -export const extractDimensionsFrom = (node) => { +export const extractDimensionsFrom = (node: XMLNode) => { let x = undefined; let y = undefined; @@ -35,6 +37,7 @@ export const extractDimensionsFrom = (node) => { if (yValue) { y = parseFloat(yValue); } + break; case 'absdimension': xValue = child.attributes.get('x'); if (xValue != null) { @@ -47,6 +50,7 @@ export const extractDimensionsFrom = (node) => { const ndcy = parseFloat(yValue) / maxAspectCompensation; y = NDCtoDDCWidth(ndcy); } + break; default: // TODO: Error handling } @@ -55,7 +59,7 @@ export const extractDimensionsFrom = (node) => { return { x, y }; }; -export const extractInsetsFrom = (node) => { +export const extractInsetsFrom = (node: XMLNode) => { let left = undefined; let right = undefined; let top = undefined; @@ -148,7 +152,7 @@ export const extractInsetsFrom = (node) => { return { left, right, top, bottom }; }; -export const extractValueFrom = (node) => { +export const extractValueFrom = (node: XMLNode) => { let value = undefined; let val = node.attributes.get('val'); diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts index fa5f26b..4feabb2 100644 --- a/src/utils/fetch.ts +++ b/src/utils/fetch.ts @@ -1,4 +1,6 @@ -export default async (path, bodyType = 'text') => { +type ResponseBodyType = 'arrayBuffer' | 'blob' | 'formData' | 'json' | 'text' + +export default async (path: string, bodyType: ResponseBodyType = 'text' ) => { // TODO: Does this path need to be normalized? const response = await fetch(path); if (response.status === 200) { diff --git a/src/utils/index.ts b/src/utils/index.ts index a7bdf8d..654d1bd 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -7,3 +7,4 @@ export * from './inheritance'; export * from './logging'; export * from './lua'; export * from './strings'; +export * from './types'; diff --git a/src/utils/inheritance.ts b/src/utils/inheritance.ts index 1b82a4c..2d3876f 100644 --- a/src/utils/inheritance.ts +++ b/src/utils/inheritance.ts @@ -1,36 +1,51 @@ -/* eslint-disable import/prefer-default-export */ +/* eslint-disable @typescript-eslint/no-explicit-any, import/prefer-default-export */ + +type Constructor = new (...args: any[]) => T; + +// Poor man's multi inheritance mechanism (may break with complex hierarchies) +const multipleClasses = < + B extends Constructor, + O extends Constructor +>(base: B, other: O) => { + type CombinedClassType = Constructor & InstanceType> & { + // Statics for base + [K in keyof B]: B[K] + } & { + // Statics for other + [K in keyof O]: O[K] + } -// Poor man's multi inheritance mechanism -// Note: Will most likely not work with complex hierarchies -// Inspiration: https://itnext.io/multiple-inheritance-in-js-part-2-24adca2c2518 -const multipleClasses = (base, ...ctors) => { const cls = class extends base { - constructor(baseArgs = [], ...ctorsArgs) { + constructor(..._args: any[]) { // Create an instance of the base class - const instance = super(...baseArgs); - - // Construct temporary instances of all other classes and assign their - // own properties to the instance - for (const [index, ctor] of ctors.entries()) { - Object.assign(instance, new ctor(...(ctorsArgs[index] || []))); - } + super(); - return instance; + // Construct temporary throw-away instance of other class and assign own properties + // Warning: using `this` as a reference in other class' constructor will not work correctly + Object.assign(this, new other()); } - }; + } as unknown as CombinedClassType; const { prototype } = cls; - // Enhance the new class' prototype with all the methods, getters and setters - // from all other classes - for (const ctor of ctors) { - const pds = Object.getOwnPropertyDescriptors(ctor.prototype); - for (const [name, pd] of Object.entries(pds)) { - if (name === 'constructor') { - continue; - } - Object.defineProperty(prototype, name, pd); + // Handle instance methods, getters and setters from the other class + let pds = Object.getOwnPropertyDescriptors(other.prototype); + for (const [name, pd] of Object.entries(pds)) { + // Skip over existing prototype entries + if (name in prototype) { + continue; + } + Object.defineProperty(prototype, name, pd); + } + + // Handle statics from the other class + pds = Object.getOwnPropertyDescriptors(other); + for (const [name, pd] of Object.entries(pds)) { + // Skip over existing static properties / methods + if (name in cls) { + continue; } + Object.defineProperty(cls, name, pd); } return cls; diff --git a/src/utils/logging.ts b/src/utils/logging.ts index a352dba..4e77bd8 100644 --- a/src/utils/logging.ts +++ b/src/utils/logging.ts @@ -1,37 +1,47 @@ /* eslint-disable no-console, import/prefer-default-export */ -const INFO = 0x0; -const WARNING = 0x1; -const ERROR = 0x2; -const FATAL = 0x3; +enum StatusType { + INFO = 0x0, + WARNING = 0x1, + ERROR = 0x2, + FATAL = 0x3, +} + +type StatusArgs = Array; +type StatusEntry = { + type: StatusType, + args: StatusArgs, +} class Status { + entries: StatusEntry[]; + constructor() { this.entries = []; } - add(type, ...args) { - this.entries.push([type, ...args]); + add(type: StatusType, ...args: StatusArgs) { + this.entries.push({ type, args }); } - info(...args) { + info(...args: StatusArgs) { console.info(...args); - this.add(INFO, ...args); + this.add(StatusType.INFO, ...args); } - warning(...args) { + warning(...args: StatusArgs) { console.warn(...args); - this.add(WARNING, ...args); + this.add(StatusType.WARNING, ...args); } - error(...args) { + error(...args: StatusArgs) { console.error(...args); - this.add(ERROR, ...args); + this.add(StatusType.ERROR, ...args); } - fatal(...args) { + fatal(...args: StatusArgs) { console.error(...args); - this.add(FATAL, ...args); + this.add(StatusType.FATAL, ...args); } } diff --git a/src/utils/lua.ts b/src/utils/lua.ts index ad61d2f..ae25b64 100644 --- a/src/utils/lua.ts +++ b/src/utils/lua.ts @@ -10,6 +10,7 @@ import { LUA_TSTRING, LUA_TTABLE, LUA_TUSERDATA, + lua_State, lua_gettop, lua_toboolean, lua_tocfunction, @@ -21,7 +22,7 @@ import { import { stringToBoolean } from '.'; -export const luaStackToArray = (L) => { +export const luaStackToArray = (L: lua_State) => { const top = lua_gettop(L); const results = []; @@ -46,6 +47,7 @@ export const luaStackToArray = (L) => { break; case LUA_TNONE: value = undefined; + break; case LUA_TNUMBER: value = lua_tonumber(L, idx); break; @@ -66,7 +68,7 @@ export const luaStackToArray = (L) => { return results; }; -export const luaValueToBoolean = (L, index, standard) => { +export const luaValueToBoolean = (L: lua_State, index: number, standard: boolean) => { let result; switch (lua_type(L, index)) { case LUA_TNIL: @@ -78,10 +80,10 @@ export const luaValueToBoolean = (L, index, standard) => { case LUA_TNUMBER: result = lua_tonumber(L, index) !== 0; break; - case LUA_TSTRING: + case LUA_TSTRING: { const str = lua_tojsstring(L, index); result = stringToBoolean(str, standard); - break; + } break; default: result = standard; break; diff --git a/src/utils/path.ts b/src/utils/path.ts index 088c39d..48c5ec2 100644 --- a/src/utils/path.ts +++ b/src/utils/path.ts @@ -2,15 +2,15 @@ const SEPARATOR = '\\'; -const dirname = (path) => { +const dirname = (path: string) => { return path.slice(0, path.lastIndexOf(SEPARATOR)); }; -const join = (...segments) => { +const join = (...segments: string[]) => { return segments.join(SEPARATOR); }; -const normalize = (path) => { +const normalize = (path: string) => { const segments = []; const parts = path.split(SEPARATOR); diff --git a/src/utils/strings.ts b/src/utils/strings.ts index 2eabac1..75ba2d3 100644 --- a/src/utils/strings.ts +++ b/src/utils/strings.ts @@ -1,6 +1,6 @@ /* eslint-disable import/prefer-default-export */ -export const stringToBoolean = (string, standard = false) => { +export const stringToBoolean = (string?: string, standard = false) => { if (!string) { return standard; } @@ -39,4 +39,4 @@ export const stringToBoolean = (string, standard = false) => { }; // TODO: This may not cover all edge cases -export const stringToFloat = (string) => parseFloat(string); +export const stringToFloat = (string: string) => parseFloat(string); diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..0c424d5 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,6 @@ +// See: https://github.com/microsoft/TypeScript/issues/5863#issuecomment-1336204919 +export type ThisConstructor< + T extends { prototype: unknown } = { prototype: unknown }, +> = T; + +export type This = T['prototype']; diff --git a/tsconfig.json b/tsconfig.json index 75abdef..656ac48 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,5 +19,5 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src", "vendor"] } diff --git a/vendor/fengari.d.ts b/vendor/fengari.d.ts new file mode 100644 index 0000000..e6ffa4e --- /dev/null +++ b/vendor/fengari.d.ts @@ -0,0 +1,270 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +// Loosely adapted from: https://github.com/fengari-lua/fengari/pull/157 +declare module 'fengari' { + const FENGARI_AUTHORS: string; + const FENGARI_COPYRIGHT: string; + const FENGARI_RELEASE: string; + const FENGARI_VERSION: string; + const FENGARI_VERSION_NUM: number; + const FENGARI_VERSION_MAJOR: string; + const FENGARI_VERSION_RELEASE: string; + const FENGARI_VERSION_MINOR: string; + + type lua_String = Uint8Array; + type lua_InputString = Uint8Array | string; + + interface lua_State {} + + function to_luastring(s: string): lua_String; + function to_jsstring(s: lua_String): string; + function to_uristring(s: lua_String): string; + + const LUA_OPADD: number; + const LUA_OPSUB: number; + const LUA_OPMUL: number; + const LUA_OPMOD: number; + const LUA_OPPOW: number; + const LUA_OPDIV: number; + const LUA_OPIDIV: number; + const LUA_OPBAND: number; + const LUA_OPBOR: number; + const LUA_OPBXOR: number; + const LUA_OPSHL: number; + const LUA_OPSHR: number; + const LUA_OPUNM: number; + const LUA_OPBNOT: number; + + namespace lua { + class lua_Debug { + source: lua_String; + namewhat: lua_String; + } + + const LUA_TNONE: number; + const LUA_TNIL: number; + const LUA_TBOOLEAN: number; + const LUA_TLIGHTUSERDATA: number; + const LUA_TNUMBER: number; + const LUA_TSTRING: number; + const LUA_TTABLE: number; + const LUA_TFUNCTION: number; + const LUA_TUSERDATA: number; + const LUA_TTHREAD: number; + + type lua_Type = number; + type lua_ArithOp = number; + + const LUA_OK: number; + const LUA_ERRRUN: number; + + type lua_Alloc = (...a: any) => any; // TODO: define this correctly. Is alloc actually used? + type lua_Cfunction = (L: lua_State) => number; + type lua_Integer = number + type lua_KContext = any; + type lua_KFunction = (L: lua_State, status: number, ctx: lua_KContext) => number; + type lua_Number = number; + type lua_Writer = (L: lua_State, p: any, sz: number, ud: any) => number; + type lua_Reader = (L: lua_State, ud: any, size: any) => any; + + const LUA_AUTHORS: string; + const LUA_COPYRIGHT: string; + const LUA_VERSION: string; + const LUA_RELEASE: string; + + const LUA_REGISTRYINDEX: number; + const LUA_RIDX_MAINTHREAD: number; + const LUA_RIDX_GLOBALS: number; + + function lua_absindex(L: lua_State, idx: number): number; + function lua_arith(L: lua_State, op: lua_ArithOp): void; + function lua_atnativeerror(L: lua_State, errfunc: lua_Cfunction): lua_Cfunction; + function lua_atpanic(L: lua_State, panicf: lua_Cfunction): lua_Cfunction; + + function lua_call(L: lua_State, nargs: number, nresults: number): void; + function lua_callk(L: lua_State, nargs: number, nresults: number, ctx: lua_KContext, k: lua_KFunction): void; + function lua_checkstack(L: lua_State, extra: number): number; + function lua_close(L: lua_State): void; + function lua_compare(L: lua_State, index1: number, index2: number, op: number): number; + function lua_concat(L: lua_State, n: number): void; + function lua_copy(L: lua_State, fromidx: number, toidx: number): void; + function lua_createtable(L: lua_State, narr: number, nrec: number): void; + + function lua_dump(L: lua_State, writer: lua_Writer, data: any, strip: boolean): number; + + function lua_error(L: lua_State): number; + + function lua_gc(L: lua_State, what: number, data: number): number; + function lua_getallocf(L: lua_State, data: any): number; + function lua_getfield(L: lua_State, index: number, k: string): lua_Type; + function lua_getextraspace(L: lua_State): any; + function lua_getglobal(L: lua_State, name: string): lua_Type; + function lua_geti(L: lua_State, idx: number, i: lua_Integer): number; + function lua_getmetatable(L: lua_State, index: number): number; + function lua_gettable(L: lua_State, index: number): lua_Type; + function lua_gettop(L: lua_State): number; + function lua_getuservalue(L: lua_State, index: number): lua_Type; + + function lua_getinfo(L, what, ar): number; + function lua_getlocal(L, ar, n): string; + function lua_getstack(L, level, ar): number; + + function lua_insert(L: lua_State, index: number): void; + function lua_isboolean(L: lua_State, index: number): boolean; + function lua_iscfunction(L: lua_State, index: number): boolean; + function lua_isfunction(L: lua_State, index: number): boolean; + function lua_isinteger(L: lua_State, index: number): boolean; + function lua_islightuserdata(L: lua_State, index: number): boolean; + function lua_isnil(L: lua_State, index: number): boolean; + function lua_isnone(L: lua_State, index: number): boolean; + function lua_isnoneornil(L: lua_State, index: number): boolean; + function lua_isnumber(L: lua_State, index: number): boolean; + function lua_isproxy(L: lua_State, index: number): boolean; + function lua_isstring(L: lua_State, index: number): boolean; + function lua_istable(L: lua_State, index: number): boolean; + function lua_isuserdata(L: lua_State, index: number): boolean; + function lua_isthread(L: lua_State, index: number): boolean; + function lua_isyieldable(L: lua_State, index: number): boolean; + + function lua_len(L: lua_State, index: number): number; + function lua_load(L: lua_State, reader: lua_Reader, data: any, chunkname: lua_String, mode: lua_String | null): number; + + function lua_newstate(f: lua_Alloc, ud: any): lua_State; + function lua_newtable(L: lua_State): void; + function lua_newthread(L: lua_State): lua_State; + function lua_newuserdata(L: lua_State, size: number): any; + function lua_next(L: lua_State, index: number): number; + function lua_numbertointeger(n: lua_Number, p: lua_Integer): boolean; + + function lua_pcall(L: lua_State, nargs: number, nresults: number, errfunc: number): number; + function lua_pcallk(L: lua_State, nargs: number, nresults: number, errfunc: number, ctx: lua_KContext, k: lua_KFunction): number; + function lua_pop(L: lua_State, n: number): void; + function lua_pushboolean(L: lua_State, b: boolean | 0 | 1): void; + function lua_pushcclosure(L: lua_State, fn: lua_Cfunction, idx: number): void; + function lua_pushcfunction(L: lua_State, fn: lua_Cfunction): void; + function lua_pushfstring(L: lua_State, s: lua_String, ...a: any): lua_String; + + function lua_pushglobaltable(L: lua_State): void; + function lua_pushinteger(L: lua_State, n: number): void; + function lua_pushlightuserdata(L: lua_State, p: any): void; + function lua_pushliteral(L: lua_State, s: string): void; + function lua_pushlstring(L: lua_State, s: lua_String | string, size: number): void; + function lua_pushnil(L: lua_State): void; + function lua_pushnumber(L: lua_State, n: number): void; + function lua_pushstring(L: lua_State, s: lua_String | string): void; + function lua_pushthread(L: lua_State): number; + function lua_pushvalue(L: lua_State, index: number): void; + function lua_pushvstring(L: lua_State, fmt: lua_String, argp: any): lua_String; // TODO: Check if this is supported + + function lua_pushjsfunction(L: lua_State, fn: lua_Cfunction): void; + function lua_pushjsclosure(L: lua_State, fn: lua_Cfunction, idx: number): void; + + function lua_rawequal(L: lua_State, index1: number, index2: number): boolean; + function lua_rawget(L: lua_State, index: number): number; + function lua_rawgeti(L: lua_State, index: number, n: number): lua_Type; + function lua_rawgetp(L: lua_State, index: number, p: any): lua_Type; + function lua_rawset(L: lua_State, index: number): void; + function lua_rawseti(L: lua_State, index: number, n: number): void; + function lua_rawsetp(L: lua_State, index: number, p: any): void; + function lua_register(L: lua_State, name: lua_String, f: lua_Cfunction): void; + function lua_remove(L: lua_State, index: number): void; + function lua_replace(L: lua_State, index: number): void; + function lua_resume(L: lua_State, from: lua_State, nargs: number): number; + function lua_rotate(L: lua_State, idx: number, n: number): void; + + function lua_setallocf(L: lua_State, f: lua_Alloc, ud: any): void; + function lua_setfield(L: lua_State, index: number, s: lua_String): void; + function lua_setglobal(L: lua_State, name: lua_InputString): void; + function lua_seti(L: lua_State, index: number, n: lua_Integer): void; + function lua_setmetatable(L: lua_State, index: number): void; + function lua_settable(L: lua_State, index: number): void; + function lua_settop(L: lua_State, index: number): void; + function lua_setuservalue(L: lua_State, index: number): void; + function lua_status(L: lua_State): number; + function lua_stringtonumber(L: lua_State, s: lua_String): number; + + function lua_toboolean(L: lua_State, index: number): boolean; + function lua_tocfunction(L: lua_State, index: number): boolean; + function lua_tointeger(L: lua_State, index: number): lua_Integer; + function lua_tointegerx(L: lua_State, index: number, isnum: any): lua_Integer; // TODO: See how out param is implemented + function lua_tojsstring(L: lua_State, index: number): string; + function lua_tolstring(L: lua_State, index: number, len: any): lua_String; // TODO: See how out param is implemented + function lua_tonumber(L: lua_State, index: number): lua_Number; + function lua_tonumberx(L: lua_State, index: number, isnum: any): lua_Number; // TODO: See how out param is implemented + function lua_topointer(L: lua_State, index: number): any; + function lua_tostring(L: lua_State, index: number): lua_String; + function lua_tothread(L: lua_State, index: number): lua_State; + function lua_touserdata(L: lua_State, index: number): any; + function lua_tovalue(L: lua_State, index: number): string; + function lua_type(L: lua_State, index: number): lua_Type; + function lua_typename(L: lua_State, tp: lua_Type): string; + + function lua_upvalueindex(index: number): number; + + function lua_version(L: lua_State): lua_Number; + + function lua_xmove(from: lua_State, to: lua_State, n: number): void; + + function lua_yield(L: lua_State, nresults: number): lua_Number; + function lua_yieldk(L: lua_State, nresults: number, ctx: lua_KContext, k: lua_KFunction): number; + } + + namespace lualib { + function luaL_openlibs(L: lua.lua_State): void; + } + + namespace lauxlib { + interface luaL_Buffer {} + + function luaL_addchar(B: luaL_Buffer, c: number): void; + function luaL_addlstring(B: luaL_Buffer, s: string, l: number): void; + function luaL_addstring(B: luaL_Buffer, s: string): void; + function luaL_addsize(B: luaL_Buffer, n: number): void; + function luaL_addvalue(B: luaL_Buffer): void; + function luaL_argcheck(L: lua.lua_State, cond: number, narg: number, extramsg: string): void; + function luaL_argerror(L: lua.lua_State, narg: number, extramsg: string): void; + + function luaL_buffinit(B: luaL_Buffer): void; + + function luaL_callmeta(L: lua.lua_State, obj: number, e: string): number; + function luaL_checkany(L: lua.lua_State, narg: number): void; + function luaL_checkinteger(L: lua.lua_State, narg: number): lua.lua_Integer; + function luaL_checknumber(L: lua.lua_State, narg: number): lua.lua_Number; + function luaL_checklstring(L: lua.lua_State, narg: number): string; + function luaL_checkoption(L: lua.lua_State, narg: number, def: string, lst: string[]): number; + function luaL_checkstack(L: lua.lua_State, sz: number, msg: string): string; + function luaL_checkstring(L: lua.lua_State, narg: number): string; + function luaL_checktype(L: lua.lua_State, narg: number, t: lua.lua_Type): void; + function luaL_checkudata(L: lua.lua_State, narg: number, tname: string): void; + + function luaL_dofile(L: lua.lua_State, filename: string): boolean; + function luaL_dostring(L: lua.lua_State, str: lua_String): boolean; + + function luaL_error(L: lua.lua_State, fmt: lua_InputString, ...args: any[]): number; + + function luaL_getmetafield(L: lua.lua_State, obj: number, e: string): lua.lua_Type; + function luaL_getmetatable(L: lua.lua_State, tname: string): lua.lua_Type; + function luaL_gsub(L: lua.lua_State, s: string, p: string, r: string): string; + + function luaL_loadbuffer(L: lua.lua_State, buff: lua_InputString, size: number, name: lua_InputString): number; + function luaL_loadfile(L: lua.lua_State, filename: string): number; + function luaL_loadstring(L: lua.lua_State, s: lua_String): number; + + function luaL_newmetatable(L: lua.lua_State, tname: string): number; + function luaL_newstate(): lua.lua_State; + + function luaL_optinteger(L: lua.lua_State, narg: number, d: lua.lua_Integer): lua.lua_Integer; + function luaL_optlstring(L: lua.lua_State, narg: number, d: string): string; + function luaL_optnumber(L: lua.lua_State, narg: number, d: lua.lua_Number): lua.lua_Number; + function luaL_optstring(L: lua.lua_State, narg: number, d: string): string; + + function luaL_prepbuffer(B: luaL_Buffer): any; + function luaL_pushresult(B: luaL_Buffer): void; + + function luaL_ref(L: lua.lua_State, t: number): number; + + function luaL_typename(L: lua.lua_State, idx: number): lua_String; + function luaL_unref(L: lua.lua_State, t: number, ref: number): void; + function luaL_where(L: lua.lua_State, lvl: number): void; + } +} diff --git a/vendor/global.d.ts b/vendor/global.d.ts new file mode 100644 index 0000000..e4f471a --- /dev/null +++ b/vendor/global.d.ts @@ -0,0 +1,5 @@ +// Ensures `import file from './file.lua'` does not trip up TypeScript compiler +declare module '*.lua' { + const format: string; + export default format; +} diff --git a/vendor/wowserhq-math.d.ts b/vendor/wowserhq-math.d.ts new file mode 100644 index 0000000..f72ed7f --- /dev/null +++ b/vendor/wowserhq-math.d.ts @@ -0,0 +1,26 @@ +// TODO: Types should come from @wowserhq/math package itself + +declare module '@wowserhq/math' { + type InputVector3 = Vector3 | [number, number, number]; + + class Matrix4 extends Float32Array { + multiply(r: Matrix4): this; + translate(move: InputVector3): this; + transpose(): this; + } + + class Vector2 extends Float32Array { + x: number; + y: number; + + setElements(x: number, y: number): this; + } + + class Vector3 extends Float32Array { + x: number; + y: number; + z: number; + + setElements(x: number, y: number, z: number): this; + } +} diff --git a/vite.config.js b/vite.config.js index 551f313..604965a 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,7 +1,16 @@ import { defineConfig } from 'vite'; -export default defineConfig({ +export default defineConfig(({ command }) => ({ + // Ensure non-existent files produce 404s + appType: 'mpa', assetsInclude: ['**/*.lua'], + build: { + rollupOptions: { + output: { + entryFileNames: 'assets/wowser-client-[hash].js', + }, + }, + }, define: { // Configure Fengari to not suffix Lua integers with `.0` when string formatted // See: https://github.com/fengari-lua/fengari/issues/113 @@ -20,4 +29,6 @@ export default defineConfig({ }, }, ], -}); + // Do not include local game files into a production build + publicDir: command === 'build' ? false : 'public' +}));