Skip to content

Commit 162f1ad

Browse files
committed
Fix possible crashes on Threading
1 parent 6951ac8 commit 162f1ad

File tree

4 files changed

+89
-63
lines changed

4 files changed

+89
-63
lines changed

source/funkin/backend/scripting/MultiThreadedScript.hx

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
package funkin.backend.scripting;
22

3-
#if ALLOW_MULTITHREADING
4-
import sys.thread.Thread;
5-
#end
6-
73
import hscript.IHScriptCustomBehaviour;
4+
import funkin.backend.utils.EngineUtil;
85

96
class MultiThreadedScript implements IFlxDestroyable implements IHScriptCustomBehaviour {
10-
var thread:#if ALLOW_MULTITHREADING Thread #else Dynamic #end;
11-
127
/**
138
* Script being ran.
149
*/
@@ -46,13 +41,6 @@ class MultiThreadedScript implements IFlxDestroyable implements IHScriptCustomBe
4641

4742
script.load();
4843

49-
#if ALLOW_MULTITHREADING
50-
thread = Thread.createWithEventLoop(function() {
51-
// Prevent the thread from being auto deleted
52-
Thread.current().events.promise();
53-
});
54-
#end
55-
5644
__variables = Type.getInstanceFields(Type.getClass(this));
5745
}
5846

@@ -69,7 +57,7 @@ class MultiThreadedScript implements IFlxDestroyable implements IHScriptCustomBe
6957

7058
public function call(func:String, args:Array<Dynamic>) {
7159
#if ALLOW_MULTITHREADING
72-
thread.events.run(function() {
60+
EngineUtil.execAsync(() -> {
7361
callEnded = false;
7462
returnValue = script.call(func, args);
7563
callEnded = true;
@@ -85,13 +73,5 @@ class MultiThreadedScript implements IFlxDestroyable implements IHScriptCustomBe
8573
script.call("destroy");
8674
script.destroy();
8775
}
88-
89-
#if ALLOW_MULTITHREADING
90-
if (thread != null) {
91-
thread.events.runPromised(function() {
92-
// close the thing
93-
});
94-
}
95-
#end
9676
}
9777
}

source/funkin/backend/system/Main.hx

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import funkin.backend.assets.ModsFolder;
1313
import funkin.backend.system.framerate.Framerate;
1414
import funkin.backend.system.framerate.SystemInfo;
1515
import funkin.backend.system.modules.*;
16+
import funkin.backend.utils.EngineUtil;
1617
import funkin.editors.SaveWarning;
1718
import funkin.options.PlayerSettings;
1819
import openfl.Assets;
@@ -22,10 +23,6 @@ import openfl.text.TextFormat;
2223
import openfl.utils.AssetLibrary;
2324
import sys.FileSystem;
2425
import sys.io.File;
25-
26-
#if ALLOW_MULTITHREADING
27-
import sys.thread.Thread;
28-
#end
2926
#if android
3027
import android.content.Context;
3128
import android.os.Build;
@@ -61,7 +58,10 @@ class Main extends Sprite
6158
// You can pretty much ignore everything from here on - your code should go in your states.
6259

6360
#if ALLOW_MULTITHREADING
64-
public static var gameThreads:Array<Thread> = [];
61+
// DEPRECATED
62+
@:dox(hide) public static var gameThreads(get, set):Array<sys.thread.Thread>;
63+
static function get_gameThreads() return EngineUtil.gameThreads;
64+
static function set_gameThreads(v) return EngineUtil.gameThreads = v;
6565
#end
6666

6767
public static function preInit() {
@@ -99,16 +99,8 @@ class Main extends Sprite
9999
#end;
100100
public static var startedFromSource:Bool = #if TEST_BUILD true #else false #end;
101101

102-
103-
private static var __threadCycle:Int = 0;
104-
public static function execAsync(func:Void->Void) {
105-
#if ALLOW_MULTITHREADING
106-
var thread = gameThreads[(__threadCycle++) % gameThreads.length];
107-
thread.events.run(func);
108-
#else
109-
func();
110-
#end
111-
}
102+
// DEPRECATED
103+
@:dox(hide) public static function execAsync(func:Void->Void) EngineUtil.execAsync(func);
112104

113105
private static function getTimer():Int {
114106
return time = Lib.getTimer();
@@ -120,10 +112,6 @@ class Main extends Sprite
120112
MemoryUtil.init();
121113
@:privateAccess
122114
FlxG.game.getTimer = getTimer;
123-
#if ALLOW_MULTITHREADING
124-
for(i in 0...4)
125-
gameThreads.push(Thread.createWithEventLoop(function() {Thread.current().events.promise();}));
126-
#end
127115
FunkinCache.init();
128116
Paths.assetsTree = new AssetsLibraryList();
129117

source/funkin/backend/utils/EngineUtil.hx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
package funkin.backend.utils;
22

3+
#if ALLOW_MULTITHREADING
4+
import sys.thread.Thread;
5+
#end
6+
7+
#if !macro
38
import funkin.backend.scripting.MultiThreadedScript;
49
import funkin.backend.scripting.Script;
10+
#end
511

612
final class EngineUtil {
13+
#if !macro
714
/**
815
* Starts a new multithreaded script.
916
* This script will share all the variables with the current one, which means already existing callbacks will be replaced by new ones on conflict.
@@ -12,4 +19,32 @@ final class EngineUtil {
1219
public static function startMultithreadedScript(path:String) {
1320
return new MultiThreadedScript(path, Script.curScript);
1421
}
22+
#end
23+
24+
#if ALLOW_MULTITHREADING
25+
public static var gameThreads:Array<Thread> = [];
26+
27+
private static var maxThreads:Int = 4;
28+
private static var threadCycle:Int = 0;
29+
private static var threadsInitialized:Bool = false;
30+
#else
31+
public static var gameThreads:Array<Dynamic> = [];
32+
#end
33+
34+
/**
35+
* Execute a function asynchronously using existing threads when initialized with ALLOW_MULTITHREADING.
36+
* @param func Void -> Void
37+
*/
38+
public static function execAsync(func:Void->Void) {
39+
#if ALLOW_MULTITHREADING
40+
if (!threadsInitialized) {
41+
threadsInitialized = true;
42+
for (i in 0...maxThreads) gameThreads.push(Thread.createWithEventLoop(() -> Thread.current().events.promise()));
43+
}
44+
gameThreads[threadCycle].events.run(func);
45+
if (++threadCycle >= maxThreads) threadCycle = 0;
46+
#else
47+
func();
48+
#end
49+
}
1550
}

source/lime/_internal/backend/native/NativeAudioSource.hx

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,18 @@ class NativeAudioSource {
122122
var arrayType:TypedArrayType;
123123
var loopPoints:Array<Int>; // In Samples
124124

125-
static var threadRunning:Bool = false;
126125
static var streamSources:Array<NativeAudioSource> = [];
127126
static var queuedStreamSources:Array<NativeAudioSource> = [];
128127

129128
static var streamMutex:Mutex = new Mutex();
130-
static var streamThread:Thread;
131129
static var streamTimer:Timer;
132130

131+
#if !ALLOW_MULTITHREADING
132+
static var wasEmpty:Bool = false;
133+
static var threadRunning:Bool = false;
134+
static var streamThread:Thread;
135+
#end
136+
133137
var streamRemove:Bool;
134138

135139
var bufferLength:Int; // Size in bytes for current streamed audio buffers.
@@ -469,27 +473,31 @@ class NativeAudioSource {
469473
streamMutex.release();
470474
}
471475

472-
static function streamThreadRun() {
473-
var i:Int, source:NativeAudioSource, process:Int, v:Int;
474-
475-
while ((i = Thread.readMessage(true)) != 0) {
476-
streamMutex.acquire();
477-
while (i-- > 0) {
478-
if ((source = streamSources[i]).streamRemove) continue;
479-
else if (source.parent.buffer == null) {
480-
source.stopStream();
481-
continue;
482-
}
476+
static function streamBuffersUpdate() {
477+
streamMutex.acquire();
483478

484-
process = source.requestBuffers < STREAM_MIN_BUFFERS ? STREAM_MIN_BUFFERS - source.requestBuffers : 0;
485-
process = STREAM_PROCESS_BUFFERS > process ? STREAM_PROCESS_BUFFERS : process;
486-
if ((process = (v = STREAM_MAX_BUFFERS - source.requestBuffers) > process ? process : v) > 0) source.fillBuffers(process);
479+
var i:Int = streamSources.length, source:NativeAudioSource, process:Int, v:Int;
480+
while (i-- > 0) {
481+
if ((source = streamSources[i]).streamRemove) continue;
482+
else if (source.parent.buffer == null) {
483+
source.stopStream();
484+
continue;
487485
}
488-
streamMutex.release();
486+
487+
process = source.requestBuffers < STREAM_MIN_BUFFERS ? STREAM_MIN_BUFFERS - source.requestBuffers : 0;
488+
process = STREAM_PROCESS_BUFFERS > process ? STREAM_PROCESS_BUFFERS : process;
489+
if ((process = (v = STREAM_MAX_BUFFERS - source.requestBuffers) > process ? process : v) > 0) source.fillBuffers(process);
489490
}
490491

492+
streamMutex.release();
493+
}
494+
495+
#if !ALLOW_MULTITHREADING
496+
static function streamThreadRun() {
497+
while (Thread.readMessage(true)) streamBuffersUpdate();
491498
threadRunning = false;
492499
}
500+
#end
493501

494502
static function streamUpdate() {
495503
if (!streamMutex.tryAcquire()) return;
@@ -512,13 +520,28 @@ class NativeAudioSource {
512520
}
513521
}
514522

515-
streamMutex.release();
523+
#if ALLOW_MULTITHREADING
524+
if (streamSources.length != 0) funkin.backend.utils.EngineUtil.execAsync(streamBuffersUpdate);
525+
#else
516526
if (streamSources.length == 0) {
517-
streamTimer.stop();
518-
if (threadRunning) streamThread.sendMessage(0);
527+
if (wasEmpty) {
528+
wasEmpty = false;
529+
streamTimer.stop();
530+
if (threadRunning) streamThread.sendMessage(1);
531+
}
532+
else {
533+
wasEmpty = true;
534+
streamTimer = resetTimer(streamTimer, 1000, streamUpdate);
535+
}
519536
}
520-
else if (threadRunning || (threadRunning = (streamThread = Thread.create(streamThreadRun)) != null))
521-
streamThread.sendMessage(streamSources.length);
537+
else {
538+
wasEmpty = false;
539+
if (threadRunning || (threadRunning = (streamThread = Thread.create(streamThreadRun)) != null))
540+
streamThread.sendMessage(1);
541+
}
542+
#end
543+
544+
streamMutex.release();
522545
}
523546

524547
function removeStream() {

0 commit comments

Comments
 (0)