diff --git a/IodineGBA/core/Emulator.js b/IodineGBA/core/Emulator.js index 9fb2793..3c5afb9 100644 --- a/IodineGBA/core/Emulator.js +++ b/IodineGBA/core/Emulator.js @@ -19,9 +19,7 @@ function GameBoyAdvanceEmulator() { "dynamicSpeed":false //Whether to actively change the target speed for best user experience. }; this.audioFound = 0; //Do we have audio output sink found yet? - this.loaded = false; //Did we initialize IodineGBA? - this.faultFound = false; //Did we run into a fatal error? - this.paused = true; //Are we paused? + this.emulatorStatus = 0x10; //{paused, saves loaded, fault found, loaded} this.offscreenWidth = 240; //Width of the GBA screen. this.offscreenHeight = 160; //Height of the GBA screen. this.BIOS = []; //Initialize BIOS as not existing. @@ -56,11 +54,11 @@ GameBoyAdvanceEmulator.prototype.generateCoreExposed = function () { } } GameBoyAdvanceEmulator.prototype.play = function () { - if (this.paused) { - this.paused = false; - if (!this.loaded && this.BIOS && this.ROM) { + if ((this.emulatorStatus | 0) >= 0x10) { + this.emulatorStatus = this.emulatorStatus & 0xF; + if ((this.emulatorStatus & 0x1) == 0 && this.BIOS && this.ROM) { this.initializeCore(); - this.loaded = true; + this.emulatorStatus = this.emulatorStatus | 0x1; this.importSave(); } this.invalidateMetrics(); @@ -68,20 +66,19 @@ GameBoyAdvanceEmulator.prototype.play = function () { } } GameBoyAdvanceEmulator.prototype.pause = function () { - if (!this.paused) { + if ((this.emulatorStatus | 0) < 0x10) { this.exportSave(); - this.paused = true; + this.emulatorStatus = this.emulatorStatus | 0x10; } } GameBoyAdvanceEmulator.prototype.stop = function () { - this.faultFound = false; - this.loaded = false; + this.emulatorStatus = this.emulatorStatus & 0x1C; this.audioUpdateState = 1; this.pause(); } GameBoyAdvanceEmulator.prototype.restart = function () { - if (this.loaded) { - this.faultFound = false; + if ((this.emulatorStatus & 0x1) == 0x1) { + this.emulatorStatus = this.emulatorStatus & 0x1D; this.exportSave(); this.initializeCore(); this.importSave(); @@ -93,25 +90,23 @@ GameBoyAdvanceEmulator.prototype.restart = function () { GameBoyAdvanceEmulator.prototype.timerCallback = function (lastTimestamp) { //Callback passes us a reference timestamp: this.lastTimestamp = +lastTimestamp; - if (!this.paused) { - if (!this.faultFound && this.loaded) { //Any error pending or no ROM loaded is a show-stopper! - this.iterationStartSequence(); //Run start of iteration stuff. - this.IOCore.enter(this.CPUCyclesTotal | 0); //Step through the emulation core loop. - this.iterationEndSequence(); //Run end of iteration stuff. - } - else { - this.pause(); //Some pending error is preventing execution, so pause. - } + if ((this.emulatorStatus | 0) == 0x5) { //Any error pending or no ROM loaded is a show-stopper! + this.iterationStartSequence(); //Run start of iteration stuff. + this.IOCore.enter(this.CPUCyclesTotal | 0); //Step through the emulation core loop. + this.iterationEndSequence(); //Run end of iteration stuff. + } + else { + this.pause(); //Some pending error is preventing execution, so pause. } } GameBoyAdvanceEmulator.prototype.iterationStartSequence = function () { this.calculateSpeedPercentage(); //Calculate the emulator realtime run speed heuristics. - this.faultFound = true; //If the end routine doesn't unset this, then we are marked as having crashed. + this.emulatorStatus = this.emulatorStatus | 0x2; //If the end routine doesn't unset this, then we are marked as having crashed. this.audioUnderrunAdjustment(); //If audio is enabled, look to see how much we should overclock by to maintain the audio buffer. this.audioPushNewState(); //Check to see if we need to update the audio core for any output changes. } GameBoyAdvanceEmulator.prototype.iterationEndSequence = function () { - this.faultFound = false; //If core did not throw while running, unset the fatal error flag. + this.emulatorStatus = this.emulatorStatus & 0x1D; //If core did not throw while running, unset the fatal error flag. this.clockCyclesSinceStart = ((this.clockCyclesSinceStart | 0) + (this.CPUCyclesTotal | 0)) | 0; //Accumulate tracking. } GameBoyAdvanceEmulator.prototype.attachROM = function (ROM) { @@ -123,7 +118,7 @@ GameBoyAdvanceEmulator.prototype.attachBIOS = function (BIOS) { this.BIOS = BIOS; } GameBoyAdvanceEmulator.prototype.getGameName = function () { - if (!this.faultFound && this.loaded) { + if ((this.emulatorStatus & 0x3) == 0x1) { return this.IOCore.cartridge.name; } else { @@ -149,23 +144,31 @@ GameBoyAdvanceEmulator.prototype.importSave = function () { if (this.saveImportHandler) { var name = this.getGameName(); if (name != "") { - var save = this.saveImportHandler(name); - var saveType = this.saveImportHandler("TYPE_" + name); - if (save && saveType && !this.faultFound && this.loaded) { - var length = save.length | 0; - var convertedSave = getUint8Array(length | 0); - if ((length | 0) > 0) { - for (var index = 0; (index | 0) < (length | 0); index = ((index | 0) + 1) | 0) { - convertedSave[index | 0] = save[index | 0] & 0xFF; + var parentObj = this; + this.emulatorStatus = this.emulatorStatus & 0x1B; + this.saveImportHandler(name, function (save) { + parentObj.emulatorStatus = parentObj.emulatorStatus & 0x1B; + parentObj.saveImportHandler("TYPE_" + name, function (saveType) { + if (save && saveType && (parentObj.emulatorStatus & 0x3) == 0x1) { + var length = save.length | 0; + var convertedSave = getUint8Array(length | 0); + if ((length | 0) > 0) { + for (var index = 0; (index | 0) < (length | 0); index = ((index | 0) + 1) | 0) { + convertedSave[index | 0] = save[index | 0] & 0xFF; + } + parentObj.IOCore.saves.importSave(convertedSave, saveType | 0); + parentObj.emulatorStatus = parentObj.emulatorStatus | 0x4; + } } - this.IOCore.saves.importSave(convertedSave, saveType | 0); - } - } + }, function (){parentObj.emulatorStatus = parentObj.emulatorStatus | 0x4;}); + }, function (){parentObj.emulatorStatus = parentObj.emulatorStatus | 0x4;}); + return; } } + this.emulatorStatus = this.emulatorStatus | 0x4; } GameBoyAdvanceEmulator.prototype.exportSave = function () { - if (this.saveExportHandler && !this.faultFound && this.loaded) { + if (this.saveExportHandler && (this.emulatorStatus & 0x3) == 0x1) { var save = this.IOCore.saves.exportSave(); var saveType = this.IOCore.saves.exportSaveType(); if (save != null && saveType != null) { @@ -254,13 +257,13 @@ GameBoyAdvanceEmulator.prototype.initializeCore = function () { } GameBoyAdvanceEmulator.prototype.keyDown = function (keyPressed) { keyPressed = keyPressed | 0; - if (!this.paused && (keyPressed | 0) >= 0 && (keyPressed | 0) <= 9) { + if ((this.emulatorStatus | 0) < 0x10 && (keyPressed | 0) >= 0 && (keyPressed | 0) <= 9) { this.IOCore.joypad.keyPress(keyPressed | 0); } } GameBoyAdvanceEmulator.prototype.keyUp = function (keyReleased) { keyReleased = keyReleased | 0; - if (!this.paused && (keyReleased | 0) >= 0 && (keyReleased | 0) <= 9) { + if ((this.emulatorStatus | 0) < 0x10 && (keyReleased | 0) >= 0 && (keyReleased | 0) <= 9) { this.IOCore.joypad.keyRelease(keyReleased | 0); } } diff --git a/index.html b/index.html index dcf511a..ead1bbd 100644 --- a/index.html +++ b/index.html @@ -57,6 +57,7 @@ + diff --git a/user_scripts/IodineGBACoreGlueCode.js b/user_scripts/IodineGBACoreGlueCode.js index 6ec6b53..8a523c5 100644 --- a/user_scripts/IodineGBACoreGlueCode.js +++ b/user_scripts/IodineGBACoreGlueCode.js @@ -46,7 +46,7 @@ var IodineGUI = { }; window.onload = function () { //Initialize Iodine: - IodineGUI.Iodine = new GameBoyAdvanceEmulator(); + registerIodineHandler(); //Initialize the timer: registerTimerHandler(); //Initialize the graphics: @@ -60,6 +60,31 @@ window.onload = function () { //Register GUI settings. registerGUISettings(); } +function registerIodineHandler() { + try { + //Will run like shit if missing some of this for the webworker copy: + if (!Math.imul || !window.Int32Array /*|| !window.SharedInt32Array*/) { + throw null; + } + //Try starting Iodine in a webworker: + IodineGUI.Iodine = new IodineGBAWorkerShim(); + addEvent("beforeunload", window, registerBeforeUnloadHandler); + } + catch (e) { + //Otherwise just run on-thread: + IodineGUI.Iodine = new GameBoyAdvanceEmulator(); + } +} +function registerBeforeUnloadHandler(e) { + IodineGUI.Iodine.pause(); + document.getElementById("play").style.display = "none"; + document.getElementById("play").style.display = "inline"; + if (e.preventDefault) { + e.preventDefault(); + } + removeEvent("beforeunload", window, registerBeforeUnloadHandler); + return "IodineGBA needs to process your save data, leaving now may result in not saving current data."; +} function registerTimerHandler() { var rate = 4; IodineGUI.Iodine.setIntervalRate(rate | 0); @@ -77,7 +102,7 @@ function registerTimerHandler() { }, rate | 0); } function registerBlitterHandler() { - IodineGUI.Blitter = new GlueCodeGfx(); + IodineGUI.Blitter = new GlueCodeGfx(240, 160); IodineGUI.Blitter.attachCanvas(document.getElementById("emulator_target")); IodineGUI.Iodine.attachGraphicsFrameHandler(function (buffer) {IodineGUI.Blitter.copyBuffer(buffer);}); } diff --git a/user_scripts/IodineGBAGraphicsGlueCode.js b/user_scripts/IodineGBAGraphicsGlueCode.js index 39b5f71..930f1f0 100644 --- a/user_scripts/IodineGBAGraphicsGlueCode.js +++ b/user_scripts/IodineGBAGraphicsGlueCode.js @@ -8,11 +8,11 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -function GlueCodeGfx() { +function GlueCodeGfx(width, height) { this.didRAF = false; //Set when rAF has been used. this.graphicsFound = 0; //Do we have graphics output sink found yet? - this.offscreenWidth = 240; //Width of the GBA screen. - this.offscreenHeight = 160; //Height of the GBA screen. + this.offscreenWidth = width; //Width of the GBA screen. + this.offscreenHeight = height; //Height of the GBA screen. this.doSmoothing = true; //Cache some frame buffer lengths: var offscreenRGBCount = this.offscreenWidth * this.offscreenHeight * 3; diff --git a/user_scripts/IodineGBASavesGlueCode.js b/user_scripts/IodineGBASavesGlueCode.js index ed54688..c532ff3 100644 --- a/user_scripts/IodineGBASavesGlueCode.js +++ b/user_scripts/IodineGBASavesGlueCode.js @@ -8,18 +8,19 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -function ImportSaveCallback(name) { +function ImportSaveCallback(name, callbackFunc, callbackFuncNoSave) { try { var save = findValue("SAVE_" + name); if (save != null) { writeRedTemporaryText("Loaded save."); - return base64ToArray(save); + callbackFunc(base64ToArray(save)); + return; } } catch (error) { writeRedTemporaryText("Could not read save: " + error.message); } - return null; + callbackFuncNoSave(); } function ExportSave() { IodineGUI.Iodine.exportSave(); diff --git a/user_scripts/IodineGBAWorkerGlueCode.js b/user_scripts/IodineGBAWorkerGlueCode.js new file mode 100644 index 0000000..ee56e1e --- /dev/null +++ b/user_scripts/IodineGBAWorkerGlueCode.js @@ -0,0 +1,223 @@ +"use strict"; +/* + Copyright (C) 2012-2015 Grant Galitz + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +function IodineGBAWorkerShim() { + this.gfx = null; + this.audio = null; + this.audioInitialized = false; + this.speed = null; + this.saveExport = null; + this.saveImport = null; + this.worker = null; + this.shared = null; + this.initialize(); +} +var tempvar = document.getElementsByTagName("script"); +IodineGBAWorkerShim.prototype.filepath = tempvar[tempvar.length-1].src; +IodineGBAWorkerShim.prototype.initialize = function () { + var parentObj = this; + this.worker = new Worker(this.filepath.substring(0, (this.filepath.length | 0) - 3) + "Worker.js"); + this.worker.onmessage = function (event) { + parentObj.decodeMessage(event.data); + } + if (window.SharedInt32Array) { + this.shared = new SharedInt32Array(1); + this.sendBufferBack(25, this.shared); + } +} +IodineGBAWorkerShim.prototype.sendMessageSingle = function (eventCode) { + eventCode = eventCode | 0; + this.worker.postMessage({messageID:eventCode}); +} +IodineGBAWorkerShim.prototype.sendMessageDouble = function (eventCode, eventData) { + eventCode = eventCode | 0; + this.worker.postMessage({messageID:eventCode, payload:eventData}); +} +IodineGBAWorkerShim.prototype.sendBufferBack = function (eventCode, eventData) { + eventCode = eventCode | 0; + this.worker.postMessage({messageID:eventCode, payload:eventData}, [eventData.buffer]); +} +IodineGBAWorkerShim.prototype.play = function () { + this.sendMessageSingle(0); +} +IodineGBAWorkerShim.prototype.pause = function () { + this.sendMessageSingle(1); +} +IodineGBAWorkerShim.prototype.restart = function () { + this.sendMessageSingle(2); +} +IodineGBAWorkerShim.prototype.setIntervalRate = function (rate) { + rate = rate | 0; + this.sendMessageDouble(3, rate | 0); +} +IodineGBAWorkerShim.prototype.timerCallback = function (timestamp) { + timestamp = +timestamp; + if (this.audio && this.audioInitialized) { + if (this.shared) { + this.shared[0] = this.audio.remainingBuffer() | 0; + } + else { + this.sendMessageDouble(23, this.audio.remainingBuffer() | 0); + } + } + this.sendMessageDouble(4, +timestamp); +} +IodineGBAWorkerShim.prototype.attachGraphicsFrameHandler = function (gfx) { + this.gfx = gfx; + this.sendMessageSingle(5); +} +IodineGBAWorkerShim.prototype.attachAudioHandler = function (audio) { + this.audio = audio; + this.sendMessageSingle(6); +} +IodineGBAWorkerShim.prototype.enableAudio = function () { + if (this.audio) { + this.sendMessageSingle(7); + } +} +IodineGBAWorkerShim.prototype.disableAudio = function () { + if (this.audio) { + this.sendMessageSingle(8); + } +} +IodineGBAWorkerShim.prototype.toggleSkipBootROM = function (doEnable) { + doEnable = doEnable | 0; + this.sendMessageDouble(9, doEnable | 0); +} +IodineGBAWorkerShim.prototype.toggleDynamicSpeed = function (doEnable) { + doEnable = doEnable | 0; + this.sendMessageDouble(10, doEnable | 0); +} +IodineGBAWorkerShim.prototype.attachSpeedHandler = function (speed) { + this.speed = speed; + this.sendMessageSingle(11); +} +IodineGBAWorkerShim.prototype.keyDown = function (keyCode) { + keyCode = keyCode | 0; + this.sendMessageDouble(12, keyCode | 0); +} +IodineGBAWorkerShim.prototype.keyUp = function (keyCode) { + keyCode = keyCode | 0; + this.sendMessageDouble(13, keyCode | 0); +} +IodineGBAWorkerShim.prototype.incrementSpeed = function (newSpeed) { + newSpeed = +newSpeed; + this.sendMessageDouble(14, +newSpeed); +} +IodineGBAWorkerShim.prototype.attachBIOS = function (BIOS) { + this.sendMessageDouble(15, BIOS); +} +IodineGBAWorkerShim.prototype.attachROM = function (ROM) { + this.sendMessageDouble(16, ROM); +} +IodineGBAWorkerShim.prototype.exportSave = function () { + this.sendMessageSingle(17); +} +IodineGBAWorkerShim.prototype.attachSaveExportHandler = function (saveExport) { + this.saveExport = saveExport; + this.sendMessageSingle(18); +} +IodineGBAWorkerShim.prototype.attachSaveImportHandler = function (saveImport) { + this.saveImport = saveImport; + this.sendMessageSingle(19); +} +IodineGBAWorkerShim.prototype.decodeMessage = function (data) { + switch (data.messageID) { + case 0: + this.audioInitialize(data.channels | 0, +data.sampleRate, data.bufferLimit | 0); + break; + case 1: + this.audioRegister(); + break; + case 2: + this.audioSetBufferSpace(data.audioBufferContainAmount | 0); + break; + case 3: + this.audioPush(data.audioBuffer, data.audioNumSamplesTotal | 0); + break; + case 4: + this.graphicsPush(data.graphicsBuffer); + break; + case 5: + this.speedPush(+data.speed); + break; + case 6: + this.saveImportRequest(data.saveID); + break; + case 7: + this.saveExportRequest(data.saveID, data.saveData); + break; + case 8: + this.audioUnregister(); + } +} +IodineGBAWorkerShim.prototype.audioInitialize = function (channels, sampleRate, bufferLimit) { + channels = channels | 0; + sampleRate = +sampleRate; + bufferLimit = bufferLimit | 0; + if (this.audio) { + this.audio.initialize(channels | 0, +sampleRate, bufferLimit | 0, function () { + //Disable audio in the callback here: + parentObj.disableAudio(); + }); + this.audioInitialized = true; + } +} +IodineGBAWorkerShim.prototype.audioRegister = function () { + if (this.audio) { + this.audio.register(); + } +} +IodineGBAWorkerShim.prototype.audioUnregister = function () { + if (this.audio) { + this.audio.unregister(); + } +} +IodineGBAWorkerShim.prototype.audioSetBufferSpace = function (bufferSpace) { + bufferSpace = bufferSpace | 0; + if (this.audio) { + this.audio.setBufferSpace(bufferSpace | 0); + } +} +IodineGBAWorkerShim.prototype.audioPush = function (audioBuffer, audioNumSamplesTotal) { + audioNumSamplesTotal = audioNumSamplesTotal | 0; + if (this.audio) { + this.audio.push(audioBuffer, audioNumSamplesTotal | 0); + this.sendBufferBack(21, audioBuffer); + } +} +IodineGBAWorkerShim.prototype.graphicsPush = function (graphicsBuffer) { + if (this.gfx) { + this.gfx(graphicsBuffer); + this.sendBufferBack(22, graphicsBuffer); + } +} +IodineGBAWorkerShim.prototype.speedPush = function (speed) { + speed = +speed; + if (this.speed) { + this.speed(+speed); + } +} +IodineGBAWorkerShim.prototype.saveImportRequest = function (saveID) { + if (this.saveImport) { + var parentObj = this; + this.saveImport(saveID, function (saveData) { + parentObj.sendMessageDouble(20, saveData); + }, + function () { + parentObj.sendMessageSingle(24); + }); + } +} +IodineGBAWorkerShim.prototype.saveExportRequest = function (saveID, saveData) { + if (this.saveExport) { + this.saveExport(saveID, saveData); + } +} \ No newline at end of file diff --git a/user_scripts/IodineGBAWorkerGlueCodeWorker.js b/user_scripts/IodineGBAWorkerGlueCodeWorker.js new file mode 100644 index 0000000..dfa0083 --- /dev/null +++ b/user_scripts/IodineGBAWorkerGlueCodeWorker.js @@ -0,0 +1,229 @@ +"use strict"; +/* + Copyright (C) 2012-2015 Grant Galitz + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +importScripts("../IodineGBA/includes/TypedArrayShim.js"); +importScripts("../IodineGBA/core/Cartridge.js"); +importScripts("../IodineGBA/core/DMA.js"); +importScripts("../IodineGBA/core/Emulator.js"); +importScripts("../IodineGBA/core/Graphics.js"); +importScripts("../IodineGBA/core/RunLoop.js"); +importScripts("../IodineGBA/core/Memory.js"); +importScripts("../IodineGBA/core/IRQ.js"); +importScripts("../IodineGBA/core/JoyPad.js"); +importScripts("../IodineGBA/core/Serial.js"); +importScripts("../IodineGBA/core/Sound.js"); +importScripts("../IodineGBA/core/Timer.js"); +importScripts("../IodineGBA/core/Wait.js"); +importScripts("../IodineGBA/core/CPU.js"); +importScripts("../IodineGBA/core/Saves.js"); +importScripts("../IodineGBA/core/sound/FIFO.js"); +importScripts("../IodineGBA/core/sound/Channel1.js"); +importScripts("../IodineGBA/core/sound/Channel2.js"); +importScripts("../IodineGBA/core/sound/Channel3.js"); +importScripts("../IodineGBA/core/sound/Channel4.js"); +importScripts("../IodineGBA/core/CPU/ARM.js"); +importScripts("../IodineGBA/core/CPU/THUMB.js"); +importScripts("../IodineGBA/core/CPU/CPSR.js"); +importScripts("../IodineGBA/core/graphics/Renderer.js"); +importScripts("../IodineGBA/core/graphics/RendererProxy.js"); +importScripts("../IodineGBA/core/graphics/BGTEXT.js"); +importScripts("../IodineGBA/core/graphics/BG2FrameBuffer.js"); +importScripts("../IodineGBA/core/graphics/BGMatrix.js"); +importScripts("../IodineGBA/core/graphics/AffineBG.js"); +importScripts("../IodineGBA/core/graphics/ColorEffects.js"); +importScripts("../IodineGBA/core/graphics/Mosaic.js"); +importScripts("../IodineGBA/core/graphics/OBJ.js"); +importScripts("../IodineGBA/core/graphics/OBJWindow.js"); +importScripts("../IodineGBA/core/graphics/Window.js"); +importScripts("../IodineGBA/core/graphics/Compositor.js"); +importScripts("../IodineGBA/core/memory/DMA0.js"); +importScripts("../IodineGBA/core/memory/DMA1.js"); +importScripts("../IodineGBA/core/memory/DMA2.js"); +importScripts("../IodineGBA/core/memory/DMA3.js"); +importScripts("../IodineGBA/core/cartridge/SaveDeterminer.js"); +importScripts("../IodineGBA/core/cartridge/SRAM.js"); +importScripts("../IodineGBA/core/cartridge/FLASH.js"); +importScripts("../IodineGBA/core/cartridge/EEPROM.js"); +var Iodine = new GameBoyAdvanceEmulator(); +//Spare audio buffers: +var audioBufferPool = []; +var audioBufferPassCount = 0; +//Spare graphics buffers: +var graphicsBufferPool = []; +var graphicsBufferPassCount = 0; +//Save callbacks waiting to be satisfied: +var saveImportPool = []; +//Cached timestamp: +var timestamp = 0; +//Event decoding: +self.onmessage = function (event) { + var data = event.data; + switch (data.messageID | 0) { + case 0: + Iodine.play(); + break; + case 1: + Iodine.pause(); + break; + case 2: + Iodine.restart(); + break; + case 3: + Iodine.setIntervalRate(data.payload | 0); + setInterval(function() {Iodine.timerCallback(+timestamp);}, data.payload | 0); + break; + case 4: + timestamp = +data.payload; + break; + case 5: + Iodine.attachGraphicsFrameHandler(graphicsFrameHandler); + break; + case 6: + Iodine.attachAudioHandler(audioHandler); + break; + case 7: + Iodine.enableAudio(); + break; + case 8: + Iodine.disableAudio(); + break; + case 9: + Iodine.toggleSkipBootROM(!!data.payload); + break; + case 10: + Iodine.toggleDynamicSpeed(!!data.payload); + break; + case 11: + Iodine.attachSpeedHandler(speedHandler); + break; + case 12: + Iodine.keyDown(data.payload | 0); + break; + case 13: + Iodine.keyUp(data.payload | 0); + break; + case 14: + Iodine.incrementSpeed(+data.payload); + break; + case 15: + Iodine.attachBIOS(data.payload); + break; + case 16: + Iodine.attachROM(data.payload); + break; + case 17: + Iodine.exportSave(); + break; + case 18: + Iodine.attachSaveExportHandler(saveExportHandler); + break; + case 19: + Iodine.attachSaveImportHandler(saveImportHandler); + break; + case 20: + processSaveImportSuccess(data.payload); + break; + case 21: + repoolAudioBuffer(data.payload); + break; + case 22: + repoolGraphicsBuffer(data.payload); + break; + case 23: + audioHandler.remainingBufferCache = data.payload; + break; + case 24: + processSaveImportFail(); + break; + case 25: + attachAudioMetricHook(data.payload); + } +} +function graphicsFrameHandler(swizzledFrame) { + if ((graphicsBufferPassCount | 0) < 2) { + var buffer = getFreeGraphicsBuffer(swizzledFrame.length); + buffer.set(swizzledFrame); + graphicsBufferPassCount = ((graphicsBufferPassCount | 0) + 1) | 0; + postMessage({messageID:4, graphicsBuffer:buffer}, [buffer.buffer]); + } +} +//Shim for our audio api: +var audioHandler = { + shared:null, + push:function (audioBuffer, amountToSend) { + if ((audioBufferPassCount | 0) < 10) { + var buffer = getFreeAudioBuffer(amountToSend | 0); + buffer.set(audioBuffer); + audioBufferPassCount = ((audioBufferPassCount | 0) + 1) | 0; + postMessage({messageID:3, audioBuffer:buffer, audioNumSamplesTotal:amountToSend | 0}, [buffer.buffer]); + } + }, + register:function () { + postMessage({messageID:1}); + }, + unregister:function () { + postMessage({messageID:8}); + }, + setBufferSpace:function (spaceContain) { + postMessage({messageID:2, audioBufferContainAmount:spaceContain}); + }, + initialize:function (channels, sampleRate, bufferLimit) { + postMessage({messageID:0, channels:channels, sampleRate:sampleRate, bufferLimit:bufferLimit}); + }, + remainingBuffer:function () { + if (this.shared) { + return this.shared[0]; + } + return this.remainingBufferCache; + }, + remainingBufferCache:0 +}; +function attachAudioMetricHook(buffer) { + audioHandler.shared = new SharedInt32Array(buffer.buffer); +} +function speedHandler(speed) { + postMessage({messageID:5, speed:speed}); +} +function saveExportHandler(saveID, saveData) { + postMessage({messageID:7, saveID:saveID, saveData:saveData}); +} +function saveImportHandler(saveID, saveCallback, noSaveCallback) { + postMessage({messageID:6, saveID:saveID}); + saveImportPool.push([saveCallback, noSaveCallback]); +} +function processSaveImportSuccess(saveData) { + saveImportPool.shift()[0](saveData); +} +function processSaveImportFail() { + saveImportPool.shift()[1](); +} +function repoolAudioBuffer(buffer) { + audioBufferPassCount = ((audioBufferPassCount | 0) - 1) | 0; + audioBufferPool.push(buffer); +} +function repoolGraphicsBuffer(buffer) { + graphicsBufferPassCount = ((graphicsBufferPassCount | 0) - 1) | 0; + graphicsBufferPool.push(buffer); +} +function getFreeGraphicsBuffer(amountToSend) { + if (graphicsBufferPool.length == 0) { + return new getUint8Array(amountToSend); + } + return graphicsBufferPool.shift(); +} +function getFreeAudioBuffer(amountToSend) { + while (audioBufferPool.length > 0) { + var buffer = audioBufferPool.shift(); + if (buffer.length >= amountToSend) { + return buffer; + } + } + return new getFloat32Array(amountToSend); +} \ No newline at end of file