Skip to content

Commit

Permalink
Sync loader
Browse files Browse the repository at this point in the history
  • Loading branch information
x0k committed Feb 23, 2024
1 parent 6f8b530 commit 1c23856
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 89 deletions.
6 changes: 1 addition & 5 deletions raylib.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function makePlatform({ canvas }) {
loadImage(filename) {
var img = new Image();
img.src = filename;
return { status: "loaded", data: img }
return img
},
/** Blocking platform API */
render() {
Expand Down Expand Up @@ -88,7 +88,6 @@ export const EVENT_TYPE = {
KEY_UP: 2,
WHEEL_MOVE: 3,
MOUSE_MOVE: 4,
ADD_FONT: 5,
}

export class BlockingRaylibJs extends RaylibJsBase {
Expand Down Expand Up @@ -140,9 +139,6 @@ export class BlockingRaylibJs extends RaylibJsBase {
case EVENT_TYPE.STOP:
this.stop()
return
case EVENT_TYPE.ADD_FONT:
this.platform.addFont(event.data)
return
default:
throw new Error(`Unknown event type: ${event.type}`);
}
Expand Down
13 changes: 3 additions & 10 deletions raylib_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,16 +293,9 @@ export class RaylibJsBase {
const buffer = this.wasm.instance.exports.memory.buffer;
const [id, width, height, mipmaps, format] = new Uint32Array(buffer, texture_ptr, 5);
const img = this.images[id];
switch (img.status) {
case "loaded":
// // TODO: implement tinting for DrawTexture
// const tint = getColorFromMemory(buffer, color_ptr);
this.ctx.drawImage(img.data, posX, posY);
case "loading":
return;
case "error":
this.platform.traceLog(LOG_FATAL, `Failed to load image: ${img.error}`);
}
// // TODO: implement tinting for DrawTexture
// const tint = getColorFromMemory(buffer, color_ptr);
this.ctx.drawImage(img, posX, posY);
}

// TODO: codepoints are not implemented
Expand Down
158 changes: 87 additions & 71 deletions raylib_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const REQUEST_MESSAGE_TYPE = {
KEY_UP: 4,
WHEEL_MOVE: 5,
MOUSE_MOVE: 6,
ADD_FONT: 7,
}

const RESPONSE_MESSAGE_TYPE = {
Expand All @@ -20,35 +19,20 @@ const RESPONSE_MESSAGE_TYPE = {
TRACE_LOG: 3,
RENDER: 4,
LOAD_FONT: 5,
LOAD_IMAGE: 6,
}

function makePlatform({ self, impl, rendering, renderer, rendererPort }) {
function makePlatform({
self,
rendering,
renderer,
rendererPort,
syncLoader,
}) {
const renderHandler = {
[RENDERER.MAIN_THREAD]: self,
[RENDERER.WORKER_THREAD]: rendererPort,
}[renderer]
const loadFont = {
[IMPL.GAME_FRAME]: (family, fileName) => {
new FontFace(family, `url(${fileName})`).load().then(
f => self.fonts.add(f),
console.error,
)
},
[IMPL.BLOCKING]: (family, fileName) => {
self.postMessage({
type: RESPONSE_MESSAGE_TYPE.LOAD_FONT,
family,
fileName,
})
},
[IMPL.LOCKING]: (family, fileName) => {
self.postMessage({
type: RESPONSE_MESSAGE_TYPE.LOAD_FONT,
family,
fileName,
})
},
}[impl]
const render = {
[RENDERING_CTX.DD]: (ctx) => {
const data = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height)
Expand Down Expand Up @@ -90,27 +74,27 @@ function makePlatform({ self, impl, rendering, renderer, rendererPort }) {
args,
})
},
loadFont,
addFont(font) {
self.fonts.add(new FontFace(font.family, font.buffer))
loadFont(family, fileName) {
self.postMessage({
type: RESPONSE_MESSAGE_TYPE.LOAD_FONT,
family,
fileName,
})
self.fonts.add(new FontFace(family, syncLoader.readFontBuffer()))
},
loadImage(filename) {
const img = {
status: "loading",
data: undefined,
error: undefined
}
fetch(filename)
.then(res => res.blob())
.then(blob => createImageBitmap(blob))
.then(data => {
img.status = "loaded"
img.data = data
}, (error) => {
img.status = "error"
img.error = error
})
return img
loadImage(fileName) {
self.postMessage({
type: RESPONSE_MESSAGE_TYPE.LOAD_IMAGE,
fileName,
})
const imgData = syncLoader.readImageData()
const canvas = new OffscreenCanvas(
imgData.width,
imgData.height
)
const ctx = canvas.getContext('2d')
ctx.putImageData(imgData, 0, 0)
return canvas
},
render,
}
Expand All @@ -127,6 +111,7 @@ export function makeWorkerMessagesHandler(self) {
rendererPort,
eventsBuffer,
statusBuffer,
syncLoaderBuffer
}) => {
if (raylibJs) {
raylibJs.stop()
Expand All @@ -136,10 +121,10 @@ export function makeWorkerMessagesHandler(self) {
canvas,
platform: makePlatform({
self,
impl,
rendering,
renderer,
rendererPort,
syncLoader: new SyncLoader(syncLoaderBuffer),
}),
rendering,
eventsQueue: new EventsQueue(eventsBuffer),
Expand Down Expand Up @@ -285,24 +270,31 @@ export class RaylibJsWorker {
this.handlers[RESPONSE_MESSAGE_TYPE.TRACE_LOG] = ({ logLevel, message, args }) => {
platform.traceLog(logLevel, message, args)
}
/** Blocking platform API */
this.handlers[RESPONSE_MESSAGE_TYPE.RENDER] = () => {}
this.handlers[RESPONSE_MESSAGE_TYPE.LOAD_FONT] = ({ family, fileName }) => {
this.handlers[RESPONSE_MESSAGE_TYPE.LOAD_FONT] = ({ fileName }) => {
fetch(fileName).then(r => r.arrayBuffer()).then(
(buffer) => this.eventsQueue.push({
type: REQUEST_MESSAGE_TYPE.ADD_FONT,
data: {
family,
buffer,
}
}),
(buffer) => this.syncLoader.pushFontBuffer(buffer),
console.error,
)
}
this.handlers[RESPONSE_MESSAGE_TYPE.LOAD_IMAGE] = ({ fileName }) => {
const img = new Image()
img.onload = () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0)
const imgData = ctx.getImageData(0, 0, img.width, img.height)
this.syncLoader.pushImageData(imgData)
}
// TODO: img.onerror
img.src = fileName
}

const eventsBuffer = window.SharedArrayBuffer
? new SharedArrayBuffer(204800)
: new ArrayBuffer(204800)
? new SharedArrayBuffer(1024)
: new ArrayBuffer(1024)
this.eventsQueue = new EventsQueue(eventsBuffer)
this.eventsSender = {
[IMPL.GAME_FRAME]: (event) => this.worker.postMessage(event),
Expand All @@ -315,6 +307,11 @@ export class RaylibJsWorker {
: new ArrayBuffer(4)
this.status = new Int32Array(statusBuffer)

const syncLoaderBuffer = window.SharedArrayBuffer
? new SharedArrayBuffer(640 * 1024)
: new ArrayBuffer(640 * 1024)
this.syncLoader = new SyncLoader(syncLoaderBuffer)

const channel = new MessageChannel()
// https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
let offscreen = undefined
Expand Down Expand Up @@ -362,6 +359,7 @@ export class RaylibJsWorker {
canvas: offscreen,
eventsBuffer,
statusBuffer,
syncLoaderBuffer,
rendering,
impl,
renderer,
Expand Down Expand Up @@ -463,7 +461,6 @@ const MESSAGE_TYPE_TO_EVENT_TYPE = {
[REQUEST_MESSAGE_TYPE.WHEEL_MOVE]: EVENT_TYPE.WHEEL_MOVE,
[REQUEST_MESSAGE_TYPE.MOUSE_MOVE]: EVENT_TYPE.MOUSE_MOVE,
[REQUEST_MESSAGE_TYPE.STOP]: EVENT_TYPE.STOP,
[REQUEST_MESSAGE_TYPE.ADD_FONT]: EVENT_TYPE.ADD_FONT,
}

class EventsQueue {
Expand All @@ -479,11 +476,6 @@ class EventsQueue {
this.queue.pushFloat(data.y)
return
}
case REQUEST_MESSAGE_TYPE.ADD_FONT: {
this.queue.pushString(data.family)
this.queue.pushBytes(new Uint8Array(data.buffer))
return
}
default:
this.queue.pushInt(data)
return
Expand All @@ -508,18 +500,42 @@ class EventsQueue {
}
})
break
case EVENT_TYPE.ADD_FONT:
handler({
type,
data: {
family: gen.next().value.string,
buffer: gen.next().value.bytes.buffer
}
})
break
default:
handler({ type, data: gen.next().value.int })
}
}
}
}

class SyncLoader {
constructor(sharedMemoryBuffer) {
this.queue = new SharedQueue(sharedMemoryBuffer)
}

pushFontBuffer(fontBuffer) {
this.queue.pushBytes(new Uint8Array(fontBuffer))
this.queue.commit()
}

readFontBuffer() {
const g = this.queue.waitAndRead()
return g.next().value.bytes.buffer
}

pushImageData(imgData) {
this.queue.pushUint(imgData.width)
this.queue.pushUint(imgData.height)
this.queue.pushBytes(imgData.data)
this.queue.commit()
}

readImageData() {
const g = this.queue.waitAndRead()
const w = g.next().value.uint
const h = g.next().value.uint
const imgData = new ImageData(w, h)
imgData.data.set(g.next().value.bytes)
return imgData
}

}
21 changes: 18 additions & 3 deletions shared_queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class SharedQueue {
}

pushBytes(bytes) {
// TODO: push operation should consider the last index (free space)
if (this.byteArr.length < bytes.length + 12) { // commit + len + padding
throw new Error(`Too large`)
}
Expand All @@ -60,8 +61,9 @@ export class SharedQueue {
return
}
this.nextIndex()
this.uintArr[this.index] = this.index
Atomics.store(this.uintArr, this.lastIndex, this.index)
this.intArr[this.index] = this.index
Atomics.store(this.intArr, this.lastIndex, this.index)
Atomics.notify(this.intArr, this.lastIndex)
this.lastIndex = this.index
}

Expand Down Expand Up @@ -97,8 +99,12 @@ export class SharedQueue {
return this.decoder.decode(this.bytes)
}

get blob() {
return new Blob([this.bytes])
}

pop(handle) {
this.index = Atomics.load(this.uintArr, this.lastIndex)
this.index = Atomics.load(this.intArr, this.lastIndex)
if (this.index === this.lastIndex) {
return
}
Expand All @@ -117,4 +123,13 @@ export class SharedQueue {
}
}

*waitAndRead() {
Atomics.wait(this.intArr, this.lastIndex, this.index);
// index already updated, so this read is safe
this.index = this.intArr[this.lastIndex];
while (this.index !== this.nextLastIndex()) {
yield this
}
}

}

0 comments on commit 1c23856

Please sign in to comment.