Potentially CLOSES #1310, CLOSES #1005, CLOSES #1291

This commit is contained in:
bryanthaboi
2026-08-18 15:49:47 -04:00
parent 7583ba8729
commit 99806ead25
1216 changed files with 264324 additions and 147175 deletions
+278
View File
@@ -32,12 +32,17 @@ FILESYSTEM_CPP = LOVE_SRC / "src" / "modules" / "filesystem" / "physfs" / "Files
IOS_MM = LOVE_SRC / "src" / "common" / "ios.mm"
IOS_H = LOVE_SRC / "src" / "common" / "ios.h"
SYSTEM_CPP = LOVE_SRC / "src" / "modules" / "system" / "System.cpp"
AUDIO_H = LOVE_SRC / "src" / "modules" / "audio" / "openal" / "Audio.h"
AUDIO_CPP = LOVE_SRC / "src" / "modules" / "audio" / "openal" / "Audio.cpp"
ENTITLEMENTS_SRC = IOS_DIR / "overlays" / "love-ios.entitlements"
NATIVE_FILES = ("GRPickerBridge.swift", "GRHealthBridge.swift", "GRBootstrap.m")
MARKER = "gen1recomp iOS picker bridge"
POOL_PAUSE_MARKER = "poolPaused"
AUDIO_EVENT_MARKER = "gr_pushAudioEvent"
# Headers must land outside `namespace love { namespace system {`.
WRAP_INCLUDES = """
// %s: headers for the native-bridge functions below.
@@ -406,6 +411,277 @@ def patch_ios_haptics():
print("patch_love_src: iOS haptics use Taptic Engine impact presets")
def patch_audio_pool_gate():
header = pristine(AUDIO_H, (POOL_PAUSE_MARKER,))
header_include_original = "#include <stack>\n#include <cmath>\n"
header_include_replacement = "#include <stack>\n#include <cmath>\n#include <atomic>\n"
if header_include_original not in header:
fail(f"audio header include anchor not found in {AUDIO_H}")
header = header.replace(header_include_original, header_include_replacement, 1)
header_original = (
"\tpublic:\n"
"\t\tPoolThread(Pool *pool);\n"
"\t\tvirtual ~PoolThread();\n"
"\t\tvoid setFinish();\n"
"\t\tvoid threadFunction();\n"
"\t};\n"
)
header_replacement = (
"\tpublic:\n"
"\t\tPoolThread(Pool *pool);\n"
"\t\tvirtual ~PoolThread();\n"
"\t\tvoid setFinish();\n"
"\t\tvoid setPoolPaused(bool paused);\n"
"\t\tvoid threadFunction();\n"
"\n"
"\tprivate:\n"
"\t\tstd::atomic<bool> poolPaused;\n"
"\t};\n"
)
if header_original not in header:
fail(f"PoolThread declaration anchor not found in {AUDIO_H}")
AUDIO_H.write_text(header.replace(header_original, header_replacement, 1))
text = pristine(AUDIO_CPP, (POOL_PAUSE_MARKER,))
ctor_original = (
"Audio::PoolThread::PoolThread(Pool *pool)\n"
"\t: pool(pool)\n"
"\t, finish(false)\n"
"{\n"
)
ctor_replacement = (
"Audio::PoolThread::PoolThread(Pool *pool)\n"
"\t: pool(pool)\n"
"\t, finish(false)\n"
"\t, poolPaused(false)\n"
"{\n"
)
if ctor_original not in text:
fail(f"PoolThread constructor anchor not found in {AUDIO_CPP}")
text = text.replace(ctor_original, ctor_replacement, 1)
loop_original = "\t\tpool->update();\n\t\tsleep(5);\n"
loop_replacement = (
"\t\tif (!poolPaused.load(std::memory_order_acquire))\n"
"\t\t\tpool->update();\n"
"\n"
"\t\tsleep(5);\n"
)
if loop_original not in text:
fail(f"pool update loop anchor not found in {AUDIO_CPP}")
text = text.replace(loop_original, loop_replacement, 1)
finish_original = (
"void Audio::PoolThread::setFinish()\n"
"{\n"
"\tthread::Lock lock(mutex);\n"
"\tfinish = true;\n"
"}\n"
)
finish_replacement = (
"void Audio::PoolThread::setFinish()\n"
"{\n"
"\tthread::Lock lock(mutex);\n"
"\tfinish = true;\n"
"}\n"
"\n"
"void Audio::PoolThread::setPoolPaused(bool paused)\n"
"{\n"
"\tpoolPaused.store(paused, std::memory_order_release);\n"
"}\n"
)
if finish_original not in text:
fail(f"setFinish anchor not found in {AUDIO_CPP}")
text = text.replace(finish_original, finish_replacement, 1)
pause_original = (
"#else\n"
"\talcMakeContextCurrent(nullptr);\n"
"#endif\n"
"}\n"
"\n"
"void Audio::resumeContext()\n"
)
pause_replacement = (
"#else\n"
"\tif (poolThread)\n"
"\t\tpoolThread->setPoolPaused(true);\n"
"\talcMakeContextCurrent(nullptr);\n"
"#endif\n"
"}\n"
"\n"
"void Audio::resumeContext()\n"
)
if pause_original not in text:
fail(f"pauseContext anchor not found in {AUDIO_CPP}")
text = text.replace(pause_original, pause_replacement, 1)
resume_original = (
"#else\n"
"\tif (context && alcGetCurrentContext() != context)\n"
"\t\talcMakeContextCurrent(context);\n"
"#endif\n"
)
resume_replacement = (
"#else\n"
"\tif (context && alcGetCurrentContext() != context)\n"
"\t\talcMakeContextCurrent(context);\n"
"\tif (poolThread)\n"
"\t\tpoolThread->setPoolPaused(false);\n"
"#endif\n"
)
if resume_original not in text:
fail(f"resumeContext anchor not found in {AUDIO_CPP}")
text = text.replace(resume_original, resume_replacement, 1)
AUDIO_CPP.write_text(text)
print("patch_love_src: audio pool thread gated while the context is paused")
IOS_AUDIO_EVENT_HELPER = """static void gr_pushAudioEvent(const char *name)
{
auto ev = love::Module::getInstance<love::event::Event>(love::Module::M_EVENT);
if (ev == nullptr)
return;
love::event::Message *msg = new love::event::Message(name);
ev->push(msg);
msg->release();
}
"""
IOS_ROUTE_CHANGE_METHOD = """
- (void)audioSessionRouteChange:(NSNotification *)note
{
@synchronized (self)
{
NSNumber *reason = note.userInfo[AVAudioSessionRouteChangeReasonKey];
NSUInteger value = reason.unsignedIntegerValue;
if (value != AVAudioSessionRouteChangeReasonOldDeviceUnavailable
&& value != AVAudioSessionRouteChangeReasonNewDeviceAvailable)
return;
auto audio = love::Module::getInstance<love::audio::Audio>(love::Module::M_AUDIO);
if (!audio)
{
NSLog(@"LoveAudioInterruptionListener could not get love audio module");
return;
}
gr_pushAudioEvent("audiosuspend");
audio->pauseContext();
dispatch_async(dispatch_get_main_queue(), ^{
@synchronized (self)
{
NSError *err = nil;
if (![[AVAudioSession sharedInstance] setActive:YES error:&err])
NSLog(@"Error reactivating AVAudioSession: %@", [err localizedDescription]);
auto resumed = love::Module::getInstance<love::audio::Audio>(love::Module::M_AUDIO);
if (resumed)
resumed->resumeContext();
gr_pushAudioEvent("audioreset");
}
});
}
}
"""
IOS_ROUTE_OBSERVER = """
[center addObserver:[LoveAudioInterruptionListener shared]
selector:@selector(audioSessionRouteChange:)
name:AVAudioSessionRouteChangeNotification
object:session];
"""
def patch_ios_audio_route():
text = IOS_MM.read_text()
if AUDIO_EVENT_MARKER in text:
print("patch_love_src: iOS audio route-change handling already present")
return
include_anchor = '#include "modules/audio/Audio.h"\n'
if include_anchor not in text:
fail(f"audio module include not found in {IOS_MM}")
text = text.replace(
include_anchor, include_anchor + '#include "modules/event/Event.h"\n', 1)
listener_anchor = "@interface LoveAudioInterruptionListener : NSObject\n"
if listener_anchor not in text:
fail(f"audio listener interface not found in {IOS_MM}")
text = text.replace(listener_anchor,
IOS_AUDIO_EVENT_HELPER + listener_anchor, 1)
interruption_original = (
"\t\tNSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];\n"
"\t\tif (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan)\n"
"\t\t\taudio->pauseContext();\n"
"\t\telse\n"
"\t\t\taudio->resumeContext();\n"
"\t}\n"
"}\n"
)
interruption_replacement = (
"\t\tNSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];\n"
"\t\tif (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan)\n"
"\t\t{\n"
"\t\t\tgr_pushAudioEvent(\"audiosuspend\");\n"
"\t\t\taudio->pauseContext();\n"
"\t\t}\n"
"\t\telse\n"
"\t\t{\n"
"\t\t\taudio->resumeContext();\n"
"\t\t\tgr_pushAudioEvent(\"audioreset\");\n"
"\t\t}\n"
"\t}\n"
"}\n"
+ IOS_ROUTE_CHANGE_METHOD
)
if interruption_original not in text:
fail(f"audio interruption handler not found in {IOS_MM}")
text = text.replace(interruption_original, interruption_replacement, 1)
active_original = (
"\t\t\tNSLog(@\"ERROR:could not get love audio module\");\n"
"\t\t\treturn;\n"
"\t\t}\n"
"\t\taudio->resumeContext();\n"
"\t}\n"
"}\n"
)
active_replacement = (
"\t\t\tNSLog(@\"ERROR:could not get love audio module\");\n"
"\t\t\treturn;\n"
"\t\t}\n"
"\t\taudio->resumeContext();\n"
"\t\tgr_pushAudioEvent(\"audioreset\");\n"
"\t}\n"
"}\n"
)
if active_original not in text:
fail(f"applicationBecameActive handler not found in {IOS_MM}")
text = text.replace(active_original, active_replacement, 1)
observer_anchor = (
"\t\t[center addObserver:[LoveAudioInterruptionListener shared]\n"
"\t\t\t selector:@selector(audioSessionInterruption:)\n"
"\t\t\t name:AVAudioSessionInterruptionNotification\n"
"\t\t\t object:session];\n"
)
if observer_anchor not in text:
fail(f"audio interruption observer not found in {IOS_MM}")
text = text.replace(observer_anchor, observer_anchor + IOS_ROUTE_OBSERVER, 1)
IOS_MM.write_text(text)
print("patch_love_src: iOS audio route changes suspend and rebuild playback")
def patch_pbxproj():
text = pristine(PBXPROJ)
@@ -473,6 +749,8 @@ def main():
copy_native_files()
patch_public_documents()
patch_ios_haptics()
patch_ios_audio_route()
patch_audio_pool_gate()
patch_wrap_system()
patch_pbxproj()