Reenable threaded loading

This commit is contained in:
Luke Hubmayer-Werner 2024-07-26 21:26:50 +09:30
parent 8df54eda28
commit ee2a207c30
2 changed files with 49 additions and 33 deletions

View File

@ -3,7 +3,7 @@ extends Node
signal loading_stage_updated(stage) # String signal loading_stage_updated(stage) # String
signal rom_loaded signal rom_loaded
const THREADED_LOADING := false var THREADED_LOADING := OS.can_use_threads()
const STRUCT := preload('res://scripts/struct.gd') const STRUCT := preload('res://scripts/struct.gd')
const STRUCT_SNES := preload('res://scripts/loaders/snes/structs.gd') const STRUCT_SNES := preload('res://scripts/loaders/snes/structs.gd')
@ -21,7 +21,7 @@ var rom_snes := File.new()
var snes_data := {} var snes_data := {}
var snes_bytes: PoolByteArray var snes_bytes: PoolByteArray
var snes_buffer: StreamPeerBuffer var snes_buffer: StreamPeerBuffer
var thread: Thread var threads: Array # Thread
func load_snes_structs(buffer: StreamPeerBuffer) -> Dictionary: func load_snes_structs(buffer: StreamPeerBuffer) -> Dictionary:
var data := {} var data := {}
@ -64,8 +64,10 @@ func load_snes_structs(buffer: StreamPeerBuffer) -> Dictionary:
return data return data
var load_start_tick := 0 var load_start_tick := 0
func _on_loader_loading_stage_updated(stage: String, loader: String) -> void: func _on_loader_loading_stage_updated(stage: String, loader: String, threaded: bool = false) -> void:
var output := '%s: %s' % [loader, stage] var output := '%s: %s' % [loader, stage]
if threaded:
output += ' (background thread)'
#print(Time.get_time_string_from_system() + ' ' + output) #print(Time.get_time_string_from_system() + ' ' + output)
print('@%dms - %s' % [Time.get_ticks_msec() - load_start_tick, output]) print('@%dms - %s' % [Time.get_ticks_msec() - load_start_tick, output])
emit_signal('loading_stage_updated', output) emit_signal('loading_stage_updated', output)
@ -73,9 +75,25 @@ func _on_loader_loading_stage_updated(stage: String, loader: String) -> void:
if scenetree: if scenetree:
yield(scenetree, 'idle_frame') yield(scenetree, 'idle_frame')
func _load_snes_audio(data_and_buffer: Array): func _unpack_threaded_args(obj_method_args: Array) -> void:
yield(_on_loader_loading_stage_updated('Loading sound samples and music data', 'SoundLoader'), 'completed') # Thread.start() only passes one argument, totally insane API deficiency
SoundLoader.parse_rom(data_and_buffer[0], data_and_buffer[1]) var obj: Object = obj_method_args[0]
var method: String = obj_method_args[1]
var args: Array = obj_method_args[2]
obj.callv(method, args)
func _load_stage(stage: String, loader: String, obj: Object, method: String, args: Array, threaded_loading: bool = false) -> void:
yield(_on_loader_loading_stage_updated(stage, loader, threaded_loading), 'completed')
if threaded_loading:
var thread := Thread.new()
match thread.start(self, '_unpack_threaded_args', [obj, method, args]):
ERR_CANT_CREATE:
print_debug('Failed to create thread for loading %s: %s'%[loader, stage])
OS.quit(1)
OK:
self.threads.append(thread)
else:
obj.callv(method, args)
func load_snes_rom_from_bytes(bytes: PoolByteArray) -> void: func load_snes_rom_from_bytes(bytes: PoolByteArray) -> void:
load_start_tick = Time.get_ticks_msec() load_start_tick = Time.get_ticks_msec()
@ -85,32 +103,32 @@ func load_snes_rom_from_bytes(bytes: PoolByteArray) -> void:
self.snes_buffer = StreamPeerBuffer.new() self.snes_buffer = StreamPeerBuffer.new()
self.snes_buffer.data_array = bytes self.snes_buffer.data_array = bytes
# Thread anything we don't need structs loaded for
# Give threads their own buffer, avoid file pointer conflicts
self._load_stage('Loading strings', 'StringLoader', StringLoader, 'load_snes_rom', [self.snes_buffer.duplicate()], THREADED_LOADING)
# Most loaders depend on Structs being loaded, this is a bottleneck
yield(_on_loader_loading_stage_updated('Loading struct definitions', 'StructLoader'), 'completed') yield(_on_loader_loading_stage_updated('Loading struct definitions', 'StructLoader'), 'completed')
self.snes_data = load_snes_structs(self.snes_buffer) self.snes_data = load_snes_structs(self.snes_buffer.duplicate())
if THREADED_LOADING:
# Give this its own buffer if threaded, avoid file pointer conflicts self._load_stage('Loading sounds', 'SoundLoader', SoundLoader, 'parse_rom', [self.snes_data, self.snes_buffer.duplicate()], THREADED_LOADING)
var _thread_error = thread.start(self, '_load_snes_audio', [self.snes_data, self.snes_buffer.duplicate()]) self._load_stage('Parsing music data', 'MusicManager', MusicManager, 'load_snes_rom', [self.snes_buffer.duplicate()], THREADED_LOADING)
else:
yield(self._load_snes_audio([self.snes_data, self.snes_buffer]), 'completed')
yield(_on_loader_loading_stage_updated('Loading strings', 'StringLoader'), 'completed')
yield(StringLoader.load_snes_rom(self.snes_buffer, true), 'completed')
SpriteLoader.reset() SpriteLoader.reset()
self._load_stage('Loading sprites', 'SpriteLoader', SpriteLoader, 'load_from_structs', [self.snes_data])
self._load_stage('Loading enemy battle sprites', 'SpriteLoader', SpriteLoader, 'load_enemy_battle_sprites', [self.snes_data, self.snes_buffer])
self._load_stage('Loading battle backgrounds', 'SpriteLoader', SpriteLoader, 'load_battle_bgs', [self.snes_data, self.snes_buffer])
yield(_on_loader_loading_stage_updated('Loading sprites', 'SpriteLoader'), 'completed') self._load_stage('Loading map data', 'MapLoader', MapLoader, 'load_snes_rom', [self.snes_data, self.snes_buffer])
SpriteLoader.load_from_structs(self.snes_data)
yield(_on_loader_loading_stage_updated('Loading enemy battle sprites', 'SpriteLoader'), 'completed') for thread in self.threads:
SpriteLoader.load_enemy_battle_sprites(self.snes_data, self.snes_buffer) thread.wait_to_finish()
yield(_on_loader_loading_stage_updated('Loading battle backgrounds', 'SpriteLoader'), 'completed') self.threads.clear()
SpriteLoader.load_battle_bgs(self.snes_data, self.snes_buffer)
yield(_on_loader_loading_stage_updated('Loading map data', 'MapLoader'), 'completed')
MapLoader.load_snes_rom(self.snes_data, self.snes_buffer)
yield(_on_loader_loading_stage_updated('Parsing music data', 'MusicManager'), 'completed')
MusicManager.load_snes_rom(self.snes_buffer)
yield(_on_loader_loading_stage_updated('Finished loading!', 'RomLoader'), 'completed') yield(_on_loader_loading_stage_updated('Finished loading!', 'RomLoader'), 'completed')
emit_signal('rom_loaded') emit_signal('rom_loaded')
var music = load('res://scripts/loaders/snes/music_ff5.gd').new() # var music = load('res://scripts/loaders/snes/music_ff5.gd').new()
music.disassemble_sfx(self.snes_buffer) # music.disassemble_sfx(self.snes_buffer)
func load_snes_rom(filename: String) -> void: func load_snes_rom(filename: String) -> void:
var error := rom_snes.open(filename, File.READ) var error := rom_snes.open(filename, File.READ)
@ -140,8 +158,6 @@ func load_psx_image(filename: String):
func _ready(): func _ready():
if THREADED_LOADING:
thread = Thread.new()
structdefs.merge(STRUCT.get_base_structarraytypes()) structdefs.merge(STRUCT.get_base_structarraytypes())
STRUCT.parse_struct_definitions_from_tsv_filename('res://data/structs_SNES_stubs.tsv', structdefs) STRUCT.parse_struct_definitions_from_tsv_filename('res://data/structs_SNES_stubs.tsv', structdefs)
STRUCT.parse_struct_definitions_from_tsv_filename('res://data/5/structs/SNES_stubs.tsv', structdefs) STRUCT.parse_struct_definitions_from_tsv_filename('res://data/5/structs/SNES_stubs.tsv', structdefs)
@ -154,6 +170,5 @@ func _ready():
pass pass
func _exit_tree() -> void: func _exit_tree() -> void:
pass for thread in self.threads:
if THREADED_LOADING:
thread.wait_to_finish() thread.wait_to_finish()

View File

@ -98,10 +98,11 @@ func _load_block(block_name: String, buffer: StreamPeerBuffer, is_RPGe: bool = f
self.tables_raw[block_name] = raw_strings self.tables_raw[block_name] = raw_strings
self.tables[block_name] = strings self.tables[block_name] = strings
func load_snes_rom(buffer: StreamPeerBuffer, is_RPGe: bool = false) -> void: func load_snes_rom(buffer: StreamPeerBuffer) -> void:
emit_signal('loading_stage_updated', 'load_snes_rom called') # emit_signal('loading_stage_updated', 'load_snes_rom called')
var is_RPGe: bool = buffer.get_size() > 0x200000 # Naive check for >2MiB
for block_name in SNES_block_addresses: for block_name in SNES_block_addresses:
emit_signal('loading_stage_updated', 'Loading string block "%s"'%block_name) # emit_signal('loading_stage_updated', 'Loading string block "%s"'%block_name)
var scenetree := get_tree() var scenetree := get_tree()
if scenetree: if scenetree:
yield(scenetree, 'idle_frame') yield(scenetree, 'idle_frame')