diff --git a/CHANGELOG.md b/CHANGELOG.md index 582b212..7f45865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Since Last Release ------------------ * Adds support for games that use `Task` instead of `Thread`. +* Removes the background-loading support for assets and the corresponding APIs + added in the previous release. v0.5.0 - January 5, 2022 diff --git a/package-lock.json b/package-lock.json index fef4525..ef0773a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "oozaru", - "version": "0.6-WiP", + "version": "0.5.0+", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "oozaru", - "version": "0.6-WiP", + "version": "0.5.0+", "license": "MIT", "devDependencies": { "typescript": "^4.5.4" diff --git a/package.json b/package.json index 42de6f4..81b29a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oozaru", - "version": "0.6-WiP", + "version": "0.5.0+", "description": "JavaScript game engine for the Web", "main": "index.js", "scripts": { diff --git a/src/audialis.ts b/src/audialis.ts index 77271ed..163362b 100644 --- a/src/audialis.ts +++ b/src/audialis.ts @@ -68,28 +68,50 @@ class Mixer this.panner.connect(this.context.destination); } - get pan() { return this.panner.pan.value; } - set pan(value) { this.panner.pan.value = value; } + get pan() + { + return this.panner.pan.value; + } + set pan(value) + { + this.panner.pan.value = value; + } - get volume() { return this.gainer.gain.value; } - set volume(value) { this.gainer.gain.value = value; } + get volume() + { + return this.gainer.gain.value; + } + set volume(value) + { + this.gainer.gain.value = value; + } } export class Sound { - static fromFile(fileName: string) + static async fromFile(fileName: string) { - return new Sound(fileName).whenReady(); + const url = Game.urlOf(fileName); + const audioElement = new Audio(); + await new Promise((resolve, reject) => { + audioElement.onloadedmetadata = () => { + resolve(); + } + audioElement.onerror = () => { + reject(Error(`Couldn't load audio file '${url}'.`)); + }; + audioElement.src = url; + }); + const sound = new Sound(audioElement); + sound.fileName = Game.fullPath(fileName); + return sound; } audioNode: MediaElementAudioSourceNode | null = null; - complete = false; currentMixer: Mixer | null = null; element: HTMLAudioElement; - exception: unknown; fileName: string | undefined; - promise: Promise | null = null; constructor(source: HTMLAudioElement | string) { @@ -98,21 +120,7 @@ class Sound this.element.loop = true; } else if (typeof source === 'string') { - const url = Game.urlOf(source); - this.fileName = source; - this.element = new Audio(); - this.element.loop = true; - this.promise = new Promise((resolve) => { - this.element.onloadedmetadata = () => { - this.complete = true; - resolve(); - } - this.element.onerror = () => { - this.exception = Error(`Couldn't load audio file '${url}'.`); - resolve(); - }; - this.element.src = url; - }); + throw Error("'new Sound' with filename is not supported under Oozaru."); } else { throw TypeError(`Invalid value '${source}' passed for 'Sound' source`); @@ -121,31 +129,19 @@ class Sound get length() { - this.checkIfReady(); return this.element.duration; } get playing() { - this.checkIfReady(); return !this.element.paused; } get position() { - this.checkIfReady(); return this.element.currentTime; } - get ready() - { - if (this.exception !== undefined) - throw this.exception; - if (this.complete) - this.promise = null; - return this.complete; - } - get repeat() { return this.element.loop; @@ -163,7 +159,6 @@ class Sound set position(value) { - this.checkIfReady(); this.element.currentTime = value; } @@ -182,21 +177,13 @@ class Sound this.element.volume = value; } - checkIfReady() - { - if (this.promise !== null) - throw Error(`Sound loaded from file '${this.fileName}' was used without a ready check.`); - } - pause() { - this.checkIfReady(); this.element.pause(); } play(mixer = Mixer.Default) { - this.checkIfReady(); if (mixer !== this.currentMixer) { this.currentMixer = mixer; if (this.audioNode !== null) @@ -210,23 +197,9 @@ class Sound stop() { - this.checkIfReady(); this.element.pause(); this.element.currentTime = 0.0; } - - async whenReady() - { - if (this.exception !== undefined) - throw this.exception; - if (this.promise !== null) { - await this.promise; - if (this.exception !== undefined) - throw this.exception; - this.promise = null; - } - return this; - } } export diff --git a/src/fontso.ts b/src/fontso.ts index fbdd599..2fb448a 100644 --- a/src/fontso.ts +++ b/src/fontso.ts @@ -32,7 +32,7 @@ import { DataStream } from './data-stream.js'; import Fido from './fido.js'; -import { Color, Shape, ShapeType, Size, Surface, Texture, Vertex } from './galileo.js'; +import { Color, Shape, ShapeType, type Size, type Surface, Texture, type Vertex } from './galileo.js'; import Game from './game.js'; interface Glyph @@ -63,28 +63,32 @@ class Font return defaultFont; } - static fromFile(fileName: string) + static async fromFile(fileName: string) { - return new Font(fileName).whenReady(); + const fontURL = Game.urlOf(fileName); + const fileData = await Fido.fetchData(fontURL); + const font = new Font(fileData); + font.fileName = Game.fullPath(fileName); + return font; } atlas!: Texture; - complete = false; - exception: unknown; - fileName: string; + fileName: string | undefined; glyphs: Glyph[] = []; lineHeight = 0; maxWidth = 0; numGlyphs = 0; - promise: Promise | null = null; stride!: number; - constructor(fileName: string) + constructor(...args: | [ ArrayBuffer ] + | [ string ]) { - this.fileName = Game.urlOf(fileName); - this.promise = Fido.fetchData(this.fileName).then((data) => { - let stream = new DataStream(data); - let rfn = stream.readStruct({ + if (typeof args[0] === 'string') { + throw Error("'new Font' with filename is not supported in Oozaru."); + } + else if (args[0] instanceof ArrayBuffer) { + let dataStream = new DataStream(args[0]); + let rfn = dataStream.readStruct({ signature: 'string/4', version: 'uint16-le', numGlyphs: 'uint16-le', @@ -99,14 +103,14 @@ class Font const numAcross = Math.ceil(Math.sqrt(rfn.numGlyphs)); this.stride = 1.0 / numAcross; for (let i = 0; i < rfn.numGlyphs; ++i) { - let charInfo = stream.readStruct({ + let charInfo = dataStream.readStruct({ width: 'uint16-le', height: 'uint16-le', reserved: 'reserve/28', }); this.lineHeight = Math.max(this.lineHeight, charInfo.height); this.maxWidth = Math.max(this.maxWidth, charInfo.width); - const pixelData = stream.readBytes(charInfo.width * charInfo.height * 4); + const pixelData = dataStream.readBytes(charInfo.width * charInfo.height * 4); this.glyphs.push({ width: charInfo.width, height: charInfo.height, @@ -123,36 +127,19 @@ class Font const y = Math.floor(i / numAcross) * this.lineHeight; this.atlas.upload(glyph.pixelData, x, y, glyph.width, glyph.height); } - this.complete = true; - }, (error) => { - this.exception = error; - }) + } + else { + throw RangeError("Invalid argument(s) passed to 'new Font'."); + } } get height() { - this.checkIfReady(); return this.lineHeight; } - get ready() - { - if (this.exception !== undefined) - throw this.exception; - if (this.complete) - this.promise = null; - return this.complete; - } - - checkIfReady() - { - if (this.promise !== null) - throw Error(`Font from file ${this.fileName} was used without a ready check.`); - } - drawText(surface: Surface, x: number, y: number, text: string | number | boolean, color = Color.White, wrapWidth?: number) { - this.checkIfReady(); text = text.toString(); if (wrapWidth !== undefined) { const lines = this.wordWrap(text, wrapWidth); @@ -166,7 +153,6 @@ class Font getTextSize(text: string | number | boolean, wrapWidth?: number): Size { - this.checkIfReady(); text = text.toString(); if (wrapWidth !== undefined) { const lines = this.wordWrap(text, wrapWidth); @@ -185,7 +171,6 @@ class Font heightOf(text: string | number | boolean, wrapWidth?: number) { - this.checkIfReady(); return this.getTextSize(text, wrapWidth).height; } @@ -225,22 +210,8 @@ class Font Shape.drawImmediate(surface, ShapeType.Triangles, this.atlas, vertices); } - async whenReady() - { - if (this.exception !== undefined) - throw this.exception; - if (this.promise !== null) { - await this.promise; - if (this.exception !== undefined) - throw this.exception; - this.promise = null; - } - return this; - } - widthOf(text: string | number | boolean) { - this.checkIfReady(); text = text.toString(); let cp: number | undefined; let ptr = 0; @@ -258,7 +229,6 @@ class Font wordWrap(text: string | number | boolean, wrapWidth: number) { - this.checkIfReady(); text = text.toString(); const lines: string[] = []; let codepoints: number[] = []; diff --git a/src/galileo.ts b/src/galileo.ts index 1fdadf9..02b938c 100644 --- a/src/galileo.ts +++ b/src/galileo.ts @@ -520,20 +520,26 @@ class Shader return defaultShader; } - static fromFiles(options: ShaderFileOptions) + static async fromFiles(options: ShaderFileOptions) { - return new Shader(options).whenReady(); - } + const vertexShaderURL = Game.urlOf(options.vertexFile); + const fragmentShaderURL = Game.urlOf(options.fragmentFile); + const sources = await Promise.all([ + Fido.fetchText(vertexShaderURL), + Fido.fetchText(fragmentShaderURL), + ]); + return new Shader({ + vertexSource: sources[0], + fragmentSource: sources[1], + }) +} - complete = false; - exception: unknown; fragmentShaderSource = ""; glFragmentShader: WebGLShader; glProgram: WebGLProgram; glVertexShader: WebGLShader; modelViewMatrix = Transform.Identity; projection = Transform.Identity; - promise: Promise | null = null; uniformIDs: { [x: string]: WebGLUniformLocation | null } = {}; vertexShaderSource = ""; valuesToSet: { [x: string]: { type: string, value: any } } = {}; @@ -550,16 +556,7 @@ class Shader this.glFragmentShader = fragmentShader; if ('vertexFile' in options && options.vertexFile !== undefined) { - const vertexURL = Game.urlOf(options.vertexFile); - const fragmentURL = Game.urlOf(options.fragmentFile); - this.promise = Promise.all([ - Fido.fetchText(vertexURL), - Fido.fetchText(fragmentURL), - ]).then((responses) => { - this.compile(responses[0], responses[1]); - }, (error) => { - this.exception = error; - }); + throw Error("'new Shader' with filenames is not supported in Oozaru."); } else if ('vertexSource' in options && options.vertexSource !== undefined) { this.compile(options.vertexSource, options.fragmentSource); @@ -569,18 +566,8 @@ class Shader } } - get ready() - { - if (this.exception !== undefined) - throw this.exception; - if (this.complete) - this.promise = null; - return this.complete; - } - activate(useTexture: boolean) { - this.checkIfReady(); if (activeShader !== this) { gl.useProgram(this.glProgram); for (const name of Object.keys(this.valuesToSet)) { @@ -632,15 +619,8 @@ class Shader this.setBoolean('al_use_tex', useTexture); } - checkIfReady() - { - if (this.promise !== null) - throw Error(`Shader was used before checking if it was ready.`); - } - clone() { - this.checkIfReady(); return new Shader({ vertexSource: this.vertexShaderSource, fragmentSource: this.fragmentShaderSource, @@ -678,7 +658,6 @@ class Shader this.vertexShaderSource = vertexShaderSource; this.fragmentShaderSource = fragmentShaderSource; this.uniformIDs = {}; - this.complete = true; const transformation = this.modelViewMatrix.clone() .compose(this.projection); @@ -834,19 +813,6 @@ class Shader .compose(this.projection); this.setMatrix('al_projview_matrix', transformation); } - - async whenReady() - { - if (this.exception !== undefined) - throw this.exception; - if (this.promise !== null) { - await this.promise; - if (this.exception !== undefined) - throw this.exception; - this.promise = null; - } - return this; - } } export @@ -938,15 +904,17 @@ class Shape export class Texture { - static fromFile(fileName: string) + static async fromFile(fileName: string) { - return new Texture(fileName).whenReady(); + const imageURL = Game.urlOf(fileName); + const image = await Fido.fetchImage(imageURL); + const texture = new Texture(image); + texture.fileName = Game.fullPath(fileName); + return texture; } - exception: unknown; fileName: string | undefined; glTexture: WebGLTexture; - promise: Promise | null = null; size: Size = { width: 0, height: 0 }; constructor(...args: | [ HTMLImageElement ] @@ -958,20 +926,7 @@ class Texture throw new Error(`Engine couldn't create a WebGL texture object.`); this.glTexture = glTexture; if (typeof args[0] === 'string') { - this.fileName = Game.urlOf(args[0]); - this.promise = Fido.fetchImage(this.fileName).then((image) => { - const oldBinding = gl.getParameter(gl.TEXTURE_BINDING_2D); - gl.bindTexture(gl.TEXTURE_2D, glTexture); - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.bindTexture(gl.TEXTURE_2D, oldBinding); - this.size = { width: image.width, height: image.height }; - }, (error) => { - this.exception = error; - }); + throw Error("'new Texture' with filename is not supported in Oozaru."); } else { const oldBinding = gl.getParameter(gl.TEXTURE_BINDING_2D); @@ -1012,35 +967,16 @@ class Texture get height() { - this.checkIfReady(); return this.size.height; } - get ready() - { - if (this.exception !== undefined) - throw this.exception; - const isReady = this.size.width > 0; - if (isReady) - this.promise = null; - return isReady; - } - get width() { - this.checkIfReady(); return this.size.width; } - checkIfReady() - { - if (this.promise !== null) - throw Error(`Texture from file '${this.fileName}' was used without a ready check.`); - } - upload(content: BufferSource, x = 0, y = 0, width = this.width, height = this.height) { - this.checkIfReady(); const pixelData = ArrayBuffer.isView(content) ? new Uint8Array(content.buffer) : new Uint8Array(content); @@ -1050,23 +986,9 @@ class Texture useTexture(textureUnit = 0) { - this.checkIfReady(); gl.activeTexture(gl.TEXTURE0 + textureUnit); gl.bindTexture(gl.TEXTURE_2D, this.glTexture); } - - async whenReady() - { - if (this.exception !== undefined) - throw this.exception; - if (this.promise !== null) { - await this.promise; - if (this.exception !== undefined) - throw this.exception; - this.promise = null; - } - return this; - } } export @@ -1088,7 +1010,6 @@ class Surface extends Texture screenSurface.frameBuffer = null; screenSurface.projection = new Transform() .project2D(0, 0, gl.canvas.width, gl.canvas.height); - screenSurface.promise = null; Object.defineProperty(Surface, 'Screen', { value: screenSurface, writable: false, diff --git a/src/job-queue.ts b/src/job-queue.ts index fdc40ef..b8b2e1e 100644 --- a/src/job-queue.ts +++ b/src/job-queue.ts @@ -215,14 +215,13 @@ function animate() && !job.paused) { job.busy = true; - Promise.resolve(job.callback()) - .then(() => { - job.busy = false; - }) - .catch(exception => { - jobs.length = 0; - throw exception; - }); + Promise.resolve(job.callback()).then(() => { + job.busy = false; + }) + .catch((error) => { + jobs.length = 0; + throw error; + }); } if (job.cancelled || (!job.recurring && job.timer < 0)) continue; // delete it diff --git a/tsconfig.json b/tsconfig.json index c926ca1..09e9b1a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,8 +7,10 @@ "outDir": "web/engine", "exactOptionalPropertyTypes": true, + "importsNotUsedAsValues": "error", "isolatedModules": true, "noEmitOnError": true, + "preserveValueImports": true, "removeComments": true, "sourceMap": true, "strict": true, diff --git a/web/oozaru.json b/web/oozaru.json index d82c83a..c5cfbe9 100644 --- a/web/oozaru.json +++ b/web/oozaru.json @@ -1,5 +1,5 @@ { "name": "Oozaru", "publisher": "Fat Cerberus", - "version": "0.6 WiP" + "version": "0.5.0+" } \ No newline at end of file