diff --git a/scripts/loaders/RomLoader.gd b/scripts/loaders/RomLoader.gd index f1b80ad..7a9aa18 100644 --- a/scripts/loaders/RomLoader.gd +++ b/scripts/loaders/RomLoader.gd @@ -3,7 +3,7 @@ extends Node signal loading_stage_updated(stage) # String signal rom_loaded -const THREADED_LOADING := false +var THREADED_LOADING := OS.can_use_threads() const STRUCT := preload('res://scripts/struct.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_bytes: PoolByteArray var snes_buffer: StreamPeerBuffer -var thread: Thread +var threads: Array # Thread func load_snes_structs(buffer: StreamPeerBuffer) -> Dictionary: var data := {} @@ -64,8 +64,10 @@ func load_snes_structs(buffer: StreamPeerBuffer) -> Dictionary: return data 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] + if threaded: + output += ' (background thread)' #print(Time.get_time_string_from_system() + ' ' + output) print('@%dms - %s' % [Time.get_ticks_msec() - load_start_tick, output]) emit_signal('loading_stage_updated', output) @@ -73,9 +75,25 @@ func _on_loader_loading_stage_updated(stage: String, loader: String) -> void: if scenetree: yield(scenetree, 'idle_frame') -func _load_snes_audio(data_and_buffer: Array): - yield(_on_loader_loading_stage_updated('Loading sound samples and music data', 'SoundLoader'), 'completed') - SoundLoader.parse_rom(data_and_buffer[0], data_and_buffer[1]) +func _unpack_threaded_args(obj_method_args: Array) -> void: + # Thread.start() only passes one argument, totally insane API deficiency + 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: 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.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') - self.snes_data = load_snes_structs(self.snes_buffer) - if THREADED_LOADING: - # Give this its own buffer if threaded, avoid file pointer conflicts - var _thread_error = thread.start(self, '_load_snes_audio', [self.snes_data, self.snes_buffer.duplicate()]) - 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') + self.snes_data = load_snes_structs(self.snes_buffer.duplicate()) + + self._load_stage('Loading sounds', 'SoundLoader', SoundLoader, 'parse_rom', [self.snes_data, self.snes_buffer.duplicate()], THREADED_LOADING) + self._load_stage('Parsing music data', 'MusicManager', MusicManager, 'load_snes_rom', [self.snes_buffer.duplicate()], THREADED_LOADING) + 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') - SpriteLoader.load_from_structs(self.snes_data) - yield(_on_loader_loading_stage_updated('Loading enemy battle sprites', 'SpriteLoader'), 'completed') - SpriteLoader.load_enemy_battle_sprites(self.snes_data, self.snes_buffer) - yield(_on_loader_loading_stage_updated('Loading battle backgrounds', 'SpriteLoader'), 'completed') - 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) + self._load_stage('Loading map data', 'MapLoader', MapLoader, 'load_snes_rom', [self.snes_data, self.snes_buffer]) + + for thread in self.threads: + thread.wait_to_finish() + self.threads.clear() yield(_on_loader_loading_stage_updated('Finished loading!', 'RomLoader'), 'completed') emit_signal('rom_loaded') - var music = load('res://scripts/loaders/snes/music_ff5.gd').new() - music.disassemble_sfx(self.snes_buffer) + # var music = load('res://scripts/loaders/snes/music_ff5.gd').new() + # music.disassemble_sfx(self.snes_buffer) func load_snes_rom(filename: String) -> void: var error := rom_snes.open(filename, File.READ) @@ -140,8 +158,6 @@ func load_psx_image(filename: String): func _ready(): - if THREADED_LOADING: - thread = Thread.new() 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/5/structs/SNES_stubs.tsv', structdefs) @@ -154,6 +170,5 @@ func _ready(): pass func _exit_tree() -> void: - pass - if THREADED_LOADING: + for thread in self.threads: thread.wait_to_finish() diff --git a/scripts/loaders/StringLoader.gd b/scripts/loaders/StringLoader.gd index 7215318..9f5d696 100644 --- a/scripts/loaders/StringLoader.gd +++ b/scripts/loaders/StringLoader.gd @@ -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[block_name] = strings -func load_snes_rom(buffer: StreamPeerBuffer, is_RPGe: bool = false) -> void: - emit_signal('loading_stage_updated', 'load_snes_rom called') +func load_snes_rom(buffer: StreamPeerBuffer) -> void: + # 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: - 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() if scenetree: yield(scenetree, 'idle_frame')