diff --git a/Editor/Core/SceneMemoryReport.cs b/Editor/Core/SceneMemoryReport.cs index 81ab27e..1a88e2c 100644 --- a/Editor/Core/SceneMemoryReport.cs +++ b/Editor/Core/SceneMemoryReport.cs @@ -43,9 +43,9 @@ namespace SplashEdit.EditorCode public const long SPU_RESERVED = 0x1010; public const long USABLE_SPU = TOTAL_SPU - SPU_RESERVED; - // Fixed runtime overhead from C++ (renderer.hh constants) - public const long BUMP_ALLOC_TOTAL = 2L * 8096 * 24; // ~380KB - public const long OT_TOTAL = 2L * 16384 * 4; // ~128KB + // Fixed runtime overhead from C++ (renderer.hh constants, now configurable) + public static long BUMP_ALLOC_TOTAL => 2L * SplashSettings.BumpSize; + public static long OT_TOTAL => 2L * SplashSettings.OtSize * 4; public const long VIS_REFS = 4096 * 4; // 16KB public const long STACK_ESTIMATE = 32 * 1024; // 32KB public const long LUA_OVERHEAD = 16 * 1024; // 16KB approximate @@ -53,6 +53,11 @@ namespace SplashEdit.EditorCode public long FixedOverhead => BUMP_ALLOC_TOTAL + OT_TOTAL + VIS_REFS + STACK_ESTIMATE + LUA_OVERHEAD; + // Heap estimate and warnings + public long EstimatedHeapFree => USABLE_RAM - TotalRamUsage; + public bool IsHeapWarning => EstimatedHeapFree < 128 * 1024; // < 128KB free + public bool IsHeapCritical => EstimatedHeapFree < 64 * 1024; // < 64KB free + /// RAM used by scene data (live portion of splashpack). public long SceneRamUsage => splashpackLiveSize > 0 ? splashpackLiveSize : splashpackFileSize; diff --git a/Editor/Core/SplashBuildPaths.cs b/Editor/Core/SplashBuildPaths.cs index b666720..1f09cd1 100644 --- a/Editor/Core/SplashBuildPaths.cs +++ b/Editor/Core/SplashBuildPaths.cs @@ -166,6 +166,45 @@ namespace SplashEdit.EditorCode EnsureGitIgnore(); } + // ───── Lua bytecode compilation paths ───── + + /// + /// Directory for Lua source files extracted during export. + /// + public static string LuaSrcDir => + Path.Combine(BuildOutputDir, "lua_src"); + + /// + /// Directory for compiled Lua bytecode files. + /// + public static string LuaCompiledDir => + Path.Combine(BuildOutputDir, "lua_compiled"); + + /// + /// Manifest file listing input/output pairs for the PS1 Lua compiler. + /// + public static string LuaManifestPath => + Path.Combine(LuaSrcDir, "manifest.txt"); + + /// + /// Sentinel file written by luac_psx when compilation is complete. + /// Contains "OK" on success or "ERROR" on failure. + /// + public static string LuaDoneSentinel => + Path.Combine(LuaSrcDir, "__done__"); + + /// + /// Path to the luac_psx PS1 compiler executable (built from tools/luac_psx/). + /// + public static string LuacPsxExePath => + Path.Combine(NativeSourceDir, "tools", "luac_psx", "luac_psx.ps-exe"); + + /// + /// Path to the luac_psx tools directory (for building the compiler). + /// + public static string LuacPsxDir => + Path.Combine(NativeSourceDir, "tools", "luac_psx"); + /// /// Checks if PCSX-Redux is installed in the tools directory. /// diff --git a/Editor/Core/SplashControlPanel.cs b/Editor/Core/SplashControlPanel.cs index 92945b5..a5e2afc 100644 --- a/Editor/Core/SplashControlPanel.cs +++ b/Editor/Core/SplashControlPanel.cs @@ -37,6 +37,7 @@ namespace SplashEdit.EditorCode // ───── Build State ───── private static bool _isBuilding; private static bool _isRunning; + private static bool _luaBytecodeCompiled; private static Process _emulatorProcess; // ───── Scene List ───── @@ -669,6 +670,13 @@ namespace SplashEdit.EditorCode new GUIContent("FPS Overlay", "Show an FPS counter at top-left during gameplay"), SplashSettings.FpsOverlay); + SplashSettings.OtSize = EditorGUILayout.IntField( + new GUIContent("OT Size", "Ordering table entries. Lower = less RAM, shallower Z-sorting."), + SplashSettings.OtSize); + SplashSettings.BumpSize = EditorGUILayout.IntField( + new GUIContent("Bump Alloc Size", "Per-frame primitive buffer (bytes). Lower = less RAM, fewer triangles per frame."), + SplashSettings.BumpSize); + // Serial port (only for Real Hardware) if (SplashSettings.Target == BuildTarget.RealHardware) { @@ -789,14 +797,18 @@ namespace SplashEdit.EditorCode GUILayout.Label($"Scene: {report.sceneName}", PSXEditorStyles.SectionHeader); EditorGUILayout.Space(4); - // Main RAM bar - DrawMemoryBar("Main RAM", - report.TotalRamUsage, SceneMemoryReport.USABLE_RAM, + // Main RAM bar — segmented: OT | Bump | Scene | Heap + DrawSegmentedMemoryBar("Main RAM", + SceneMemoryReport.USABLE_RAM, report.RamPercent, - new Color(0.3f, 0.6f, 1f), - $"Scene: {FormatBytes(report.SceneRamUsage)} | " + - $"Fixed: {FormatBytes(report.FixedOverhead)} | " + - $"Free: {FormatBytes(report.RamFree)}"); + new (string label, long size, Color color)[] { + ("OT", SceneMemoryReport.OT_TOTAL, new Color(0.9f, 0.4f, 0.2f)), + ("Bump", SceneMemoryReport.BUMP_ALLOC_TOTAL, new Color(0.8f, 0.6f, 0.1f)), + ("Scene", report.SceneRamUsage, new Color(0.3f, 0.6f, 1.0f)), + ("Other", SceneMemoryReport.VIS_REFS + SceneMemoryReport.STACK_ESTIMATE + SceneMemoryReport.LUA_OVERHEAD, + new Color(0.5f, 0.5f, 0.5f)), + }, + report.EstimatedHeapFree); EditorGUILayout.Space(4); @@ -842,6 +854,15 @@ namespace SplashEdit.EditorCode $"{report.clutCount} CLUTs", PSXEditorStyles.RichLabel); + if (report.IsHeapCritical) + EditorGUILayout.HelpBox( + $"Estimated heap: {report.EstimatedHeapFree / 1024}KB free. Lua scripts may OOM!", + MessageType.Error); + else if (report.IsHeapWarning) + EditorGUILayout.HelpBox( + $"Estimated heap: {report.EstimatedHeapFree / 1024}KB free. Consider reducing OT/Bump sizes.", + MessageType.Warning); + EditorGUILayout.EndVertical(); EditorGUILayout.Space(4); } @@ -889,6 +910,62 @@ namespace SplashEdit.EditorCode GUILayout.Label(details, EditorStyles.miniLabel); } + private void DrawSegmentedMemoryBar( + string label, long total, float percent, + (string label, long size, Color color)[] segments, + long heapFree) + { + long used = 0; + foreach (var seg in segments) used += seg.size; + + // Label row + EditorGUILayout.BeginHorizontal(); + GUILayout.Label(label, EditorStyles.boldLabel, GUILayout.Width(70)); + GUILayout.Label($"{FormatBytes(used)} / {FormatBytes(total)} ({percent:F1}%) | Heap free: {FormatBytes(heapFree)}", + EditorStyles.miniLabel); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + + // Bar + Rect barRect = GUILayoutUtility.GetRect(0, 16, GUILayout.ExpandWidth(true)); + EditorGUI.DrawRect(barRect, new Color(0.15f, 0.15f, 0.17f)); + + // Draw each segment + float x = barRect.x; + foreach (var seg in segments) + { + float w = barRect.width * Mathf.Clamp01((float)seg.size / total); + if (w > 0) + EditorGUI.DrawRect(new Rect(x, barRect.y, w, barRect.height), seg.color); + x += w; + } + + // Border + DrawRectOutline(barRect, new Color(0.3f, 0.3f, 0.35f)); + + // Percent overlay + var style = new GUIStyle(EditorStyles.miniLabel) + { + alignment = TextAnchor.MiddleCenter, + normal = { textColor = Color.white } + }; + GUI.Label(barRect, $"{percent:F1}%", style); + + // Legend row + EditorGUILayout.BeginHorizontal(); + foreach (var seg in segments) + { + // Color swatch + label + Rect swatchRect = GUILayoutUtility.GetRect(10, 10, GUILayout.Width(10)); + swatchRect.y += 2; + EditorGUI.DrawRect(swatchRect, seg.color); + GUILayout.Label($"{seg.label}: {FormatBytes(seg.size)}", EditorStyles.miniLabel); + GUILayout.Space(6); + } + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + } + private static void DrawRectOutline(Rect rect, Color color) { EditorGUI.DrawRect(new Rect(rect.x, rect.y, rect.width, 1), color); @@ -930,6 +1007,7 @@ namespace SplashEdit.EditorCode } _isBuilding = true; + _luaBytecodeCompiled = false; var console = EditorWindow.GetWindow(); console.titleContent = new GUIContent("PSX Console", EditorGUIUtility.IconContent("d_UnityEditor.ConsoleWindow").image); @@ -946,7 +1024,32 @@ namespace SplashEdit.EditorCode } Log("Toolchain OK.", LogType.Log); + // Collect Lua files from all scenes and compile to bytecode + Log("Collecting Lua scripts...", LogType.Log); + EditorUtility.DisplayProgressBar("SplashEdit", "Collecting Lua scripts...", 0.2f); + var luaFiles = CollectLuaSources(); + if (luaFiles != null && luaFiles.Count > 0) + { + Log($"Found {luaFiles.Count} Lua script(s). Compiling...", LogType.Log); + EditorUtility.DisplayProgressBar("SplashEdit", "Compiling Lua scripts...", 0.3f); + var bytecodeMap = await CompileLuaAsync(luaFiles); + if (bytecodeMap == null) + { + Log("Lua compilation failed.", LogType.Error); + return; + } + PSXSceneWriter.CompiledLuaBytecode = bytecodeMap; + _luaBytecodeCompiled = true; + Log($"Compiled {bytecodeMap.Count} Lua script(s) to bytecode.", LogType.Log); + } + else + { + Log("No Lua scripts found.", LogType.Log); + PSXSceneWriter.CompiledLuaBytecode = null; + } + Log("Exporting scenes...", LogType.Log); + EditorUtility.DisplayProgressBar("SplashEdit", "Exporting scenes...", 0.4f); if (!ExportAllScenes()) { Log("Export failed.", LogType.Error); @@ -954,6 +1057,9 @@ namespace SplashEdit.EditorCode } Log($"Exported {_sceneList.Count} scene(s).", LogType.Log); + // Clear bytecode cache after export + PSXSceneWriter.CompiledLuaBytecode = null; + Log("Compiling native code...", LogType.Log); EditorUtility.DisplayProgressBar("SplashEdit", "Compiling native code...", 0.6f); if (!await CompileNativeAsync()) @@ -1222,6 +1328,345 @@ namespace SplashEdit.EditorCode Log("Wrote scene manifest.", LogType.Log); } + // ───── Lua bytecode compilation ───── + + /// + /// Scan all scenes in the scene list and collect unique Lua source files. + /// Writes each source to PSXBuild/lua_src/ for compilation. + /// + private Dictionary CollectLuaSources() + { + SplashBuildPaths.EnsureDirectories(); + string luaSrcDir = SplashBuildPaths.LuaSrcDir; + if (Directory.Exists(luaSrcDir)) + Directory.Delete(luaSrcDir, true); + Directory.CreateDirectory(luaSrcDir); + + string currentScenePath = SceneManager.GetActiveScene().path; + var luaFileMap = new Dictionary(); // LuaFile -> filename (no extension) + + for (int i = 0; i < _sceneList.Count; i++) + { + var scene = _sceneList[i]; + if (scene.asset == null) continue; + + try + { + EditorSceneManager.OpenScene(scene.path, OpenSceneMode.Single); + // Collect from object exporters + var objExporters = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None); + foreach (var objExporter in objExporters) + { + if (objExporter.LuaFile != null && !luaFileMap.ContainsKey(objExporter.LuaFile)) + { + string name = $"script_{luaFileMap.Count}"; + luaFileMap[objExporter.LuaFile] = name; + } + } + + // Collect scene-level Lua file + var sceneExporter = UnityEngine.Object.FindFirstObjectByType(); + if (sceneExporter != null && sceneExporter.SceneLuaFile != null && + !luaFileMap.ContainsKey(sceneExporter.SceneLuaFile)) + { + string name = $"script_{luaFileMap.Count}"; + luaFileMap[sceneExporter.SceneLuaFile] = name; + } + + // Collect from trigger boxes + var triggerBoxes = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None); + foreach (var tb in triggerBoxes) + { + if (tb.LuaFile != null && !luaFileMap.ContainsKey(tb.LuaFile)) + { + string name = $"script_{luaFileMap.Count}"; + luaFileMap[tb.LuaFile] = name; + } + } + } + catch (Exception ex) + { + Log($"Error scanning '{scene.name}' for Lua files: {ex.Message}", LogType.Warning); + } + } + + // Write source files to disk + foreach (var kvp in luaFileMap) + { + string srcPath = Path.Combine(luaSrcDir, kvp.Value + ".lua"); + File.WriteAllText(srcPath, kvp.Key.LuaScript, new System.Text.UTF8Encoding(false)); + } + + // Restore original scene + if (!string.IsNullOrEmpty(currentScenePath)) + EditorSceneManager.OpenScene(currentScenePath, OpenSceneMode.Single); + + return luaFileMap; + } + + /// + /// Compile Lua source files to PS1 bytecode using luac_psx inside PCSX-Redux. + /// Returns a dictionary mapping LuaFile assets to their compiled bytecode, + /// or null on failure. + /// + private async Task> CompileLuaAsync(Dictionary luaFileMap) + { + string luaSrcDir = SplashBuildPaths.LuaSrcDir; + string nativeDir = SplashBuildPaths.NativeSourceDir; + + // Build luac_psx if needed + string luacDir = SplashBuildPaths.LuacPsxDir; + string luacExe = SplashBuildPaths.LuacPsxExePath; + + if (!File.Exists(luacExe)) + { + Log("Building Lua compiler (luac_psx)...", LogType.Log); + if (!await BuildLuacPsxAsync(luacDir)) + { + Log("Failed to build luac_psx.", LogType.Error); + return null; + } + } + + // Generate manifest.txt + string manifestPath = SplashBuildPaths.LuaManifestPath; + using (var sw = new StreamWriter(manifestPath, false, new System.Text.UTF8Encoding(false))) + { + foreach (var kvp in luaFileMap) + { + sw.WriteLine(kvp.Value + ".lua"); + sw.WriteLine(kvp.Value + ".luac"); + } + } + + // Clean up previous sentinel + string sentinelPath = SplashBuildPaths.LuaDoneSentinel; + if (File.Exists(sentinelPath)) + File.Delete(sentinelPath); + + // Launch PCSX-Redux headless with luac_psx + string reduxBinary = SplashBuildPaths.PCSXReduxBinary; + if (!File.Exists(reduxBinary)) + { + Log("PCSX-Redux not found. Install it via the Toolchain section.", LogType.Error); + return null; + } + + string args = $"--no-ui --run --fastboot --pcdrv --stdout --pcdrvbase \"{luaSrcDir}\" --loadexe \"{luacExe}\""; + Log($"luac_psx: {luacExe}", LogType.Log); + Log($"pcdrvbase: {luaSrcDir}", LogType.Log); + Log($"manifest: {File.ReadAllText(manifestPath).Trim()}", LogType.Log); + + var psi = new ProcessStartInfo + { + FileName = reduxBinary, + Arguments = args, + WorkingDirectory = luaSrcDir, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + Process process = null; + try + { + process = new Process { StartInfo = psi, EnableRaisingEvents = true }; + var stdoutBuf = new System.Text.StringBuilder(); + + var stderrBuf = new System.Text.StringBuilder(); + process.OutputDataReceived += (s, e) => + { + if (e.Data != null) + { + stdoutBuf.AppendLine(e.Data); + } + }; + process.ErrorDataReceived += (s, e) => + { + if (e.Data != null) + { + stderrBuf.AppendLine(e.Data); + } + }; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + // Poll for sentinel file (100ms interval, 30s timeout) + bool done = false; + int elapsed = 0; + const int timeoutMs = 30000; + const int pollMs = 100; + + while (elapsed < timeoutMs) + { + await Task.Delay(pollMs); + elapsed += pollMs; + + if (File.Exists(sentinelPath)) + { + done = true; + break; + } + + // Check if process died unexpectedly + if (process.HasExited) + { + Log("PCSX-Redux exited unexpectedly during Lua compilation.", LogType.Error); + break; + } + } + + // Kill emulator + if (!process.HasExited) + { + try { process.Kill(); } catch { } + } + + if (!done) + { + Log("Lua compilation timed out (30s).", LogType.Error); + return null; + } + + // Check sentinel content + // Give a tiny delay for file to flush on Windows + await Task.Delay(200); + string sentinelContent = File.ReadAllText(sentinelPath).Trim(); + if (string.IsNullOrEmpty(sentinelContent)) + sentinelContent = "(empty)"; + if (sentinelContent != "OK") + { + // Sentinel contains error details from luac_psx + foreach (string errLine in sentinelContent.Split('\n')) + { + if (!string.IsNullOrWhiteSpace(errLine)) + Log(errLine.TrimEnd(), LogType.Error); + } + + // Dump full stdout and stderr for diagnosis + string fullStdout = stdoutBuf.ToString().Trim(); + string fullStderr = stderrBuf.ToString().Trim(); + + if (!string.IsNullOrEmpty(fullStdout)) + { + Log("--- PCSX-Redux stdout ---", LogType.Log); + foreach (string line in fullStdout.Split('\n')) + { + if (!string.IsNullOrWhiteSpace(line)) + LogToPanel(line.TrimEnd(), LogType.Log); + } + } + else + { + Log("No stdout captured from PCSX-Redux.", LogType.Warning); + } + + if (!string.IsNullOrEmpty(fullStderr)) + { + Log("--- PCSX-Redux stderr ---", LogType.Log); + foreach (string line in fullStderr.Split('\n')) + { + if (!string.IsNullOrWhiteSpace(line)) + LogToPanel(line.TrimEnd(), LogType.Log); + } + } + return null; + } + + // Read compiled bytecode files, keyed by source text + var result = new Dictionary(); + foreach (var kvp in luaFileMap) + { + string luacPath = Path.Combine(luaSrcDir, kvp.Value + ".luac"); + if (!File.Exists(luacPath)) + { + Log($"Missing compiled bytecode: {kvp.Value}.luac", LogType.Error); + return null; + } + result[kvp.Key.LuaScript] = File.ReadAllBytes(luacPath); + } + + return result; + } + catch (Exception ex) + { + Log($"Lua compilation error: {ex.Message}", LogType.Error); + return null; + } + finally + { + if (process != null) + { + if (!process.HasExited) + try { process.Kill(); } catch { } + process.Dispose(); + } + } + } + + /// + /// Build the luac_psx PS1 compiler executable. + /// + private async Task BuildLuacPsxAsync(string luacDir) + { + int jobCount = Math.Max(1, SystemInfo.processorCount - 1); + string makeCmd = $"make -j{jobCount}"; + + var psi = new ProcessStartInfo + { + FileName = Application.platform == RuntimePlatform.WindowsEditor ? "cmd.exe" : "/bin/bash", + Arguments = Application.platform == RuntimePlatform.WindowsEditor + ? $"/c \"cd /d \"{luacDir}\" && {makeCmd}\"" + : $"-c \"cd \\\"{luacDir}\\\" && {makeCmd}\"", + WorkingDirectory = luacDir, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + try + { + var process = new Process { StartInfo = psi, EnableRaisingEvents = true }; + var stderrBuf = new System.Text.StringBuilder(); + process.OutputDataReceived += (s, e) => { }; + process.ErrorDataReceived += (s, e) => + { + if (e.Data != null) stderrBuf.AppendLine(e.Data); + }; + + var tcs = new TaskCompletionSource(); + process.Exited += (s, e) => tcs.TrySetResult(process.ExitCode); + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + int exitCode = await tcs.Task; + process.Dispose(); + + if (exitCode != 0) + { + foreach (string line in stderrBuf.ToString().Split('\n')) + { + if (!string.IsNullOrWhiteSpace(line)) + LogToPanel(line.Trim(), LogType.Error); + } + return false; + } + + return true; + } + catch (Exception ex) + { + Log($"Failed to build luac_psx: {ex.Message}", LogType.Error); + return false; + } + } + // ───── Step 3: Compile ───── private async void CompileOnly() @@ -1266,9 +1711,20 @@ namespace SplashEdit.EditorCode if (SplashSettings.FpsOverlay) buildArg += " FPSOVERLAY=1"; + buildArg += $" OT_SIZE={SplashSettings.OtSize} BUMP_SIZE={SplashSettings.BumpSize}"; + + // Use noparser Lua library when bytecode was pre-compiled + string noparserPrefix = ""; + if (_luaBytecodeCompiled) + { + buildArg += " NOPARSER=1"; + // Build liblua-noparser.a first + noparserPrefix = "make -C third_party/nugget/third_party/psxlua psx-noparser && "; + } + int jobCount = Math.Max(1, SystemInfo.processorCount - 1); string cleanPrefix = SplashSettings.CleanBuild ? "make clean && " : ""; - string makeCmd = $"{cleanPrefix}make all -j{jobCount} {buildArg}".Trim(); + string makeCmd = $"{cleanPrefix}{noparserPrefix}make all -j{jobCount} {buildArg}".Trim(); Log($"Running: {makeCmd}", LogType.Log); var psi = new ProcessStartInfo diff --git a/Editor/Core/SplashSettings.cs b/Editor/Core/SplashSettings.cs index 53779f8..5ad784e 100644 --- a/Editor/Core/SplashSettings.cs +++ b/Editor/Core/SplashSettings.cs @@ -149,6 +149,19 @@ namespace SplashEdit.EditorCode set => EditorPrefs.SetBool(Prefix + "FpsOverlay", value); } + // --- Renderer sizes --- + public static int OtSize + { + get => EditorPrefs.GetInt(Prefix + "OtSize", 2048 * 4); + set => EditorPrefs.SetInt(Prefix + "OtSize", value); + } + + public static int BumpSize + { + get => EditorPrefs.GetInt(Prefix + "BumpSize", 8096 * 16); + set => EditorPrefs.SetInt(Prefix + "BumpSize", value); + } + // --- Export settings --- public static float DefaultGTEScaling { @@ -188,7 +201,8 @@ namespace SplashEdit.EditorCode "PCSXReduxPath", "PCSXReduxPCdrvBase", "SerialPort", "SerialBaudRate", "ResWidth", "ResHeight", "DualBuffering", "VerticalLayout", "GTEScaling", "AutoValidate", - "LicenseFilePath", "ISOVolumeLabel" + "LicenseFilePath", "ISOVolumeLabel", + "OtSize", "BumpSize" }; foreach (string key in keys) diff --git a/Runtime/PSXSceneWriter.cs b/Runtime/PSXSceneWriter.cs index 4355184..ef71657 100644 --- a/Runtime/PSXSceneWriter.cs +++ b/Runtime/PSXSceneWriter.cs @@ -12,6 +12,13 @@ namespace SplashEdit.RuntimeCode /// public static class PSXSceneWriter { + /// + /// Pre-compiled Lua bytecode, keyed by Lua source text. + /// When populated, Write() packs bytecode instead of source text. + /// Set by the build pipeline after running luac_psx, cleared after export. + /// + public static Dictionary CompiledLuaBytecode { get; set; } + /// /// All scene data needed to produce a .bin file. /// Populated by PSXSceneExporter before calling . @@ -239,7 +246,12 @@ namespace SplashEdit.RuntimeCode { luaOffset.PlaceholderPositions.Add(writer.BaseStream.Position); writer.Write((int)0); // placeholder - writer.Write((uint)Encoding.UTF8.GetByteCount(luaFile.LuaScript)); + + // Use compiled bytecode length if available, otherwise source length + if (CompiledLuaBytecode != null && CompiledLuaBytecode.TryGetValue(luaFile.LuaScript, out byte[] bytecode)) + writer.Write((uint)bytecode.Length); + else + writer.Write((uint)Encoding.UTF8.GetByteCount(luaFile.LuaScript)); } // ────────────────────────────────────────────────────── @@ -429,12 +441,26 @@ namespace SplashEdit.RuntimeCode // Data sections // ══════════════════════════════════════════════════════ - // Lua data + // Lua data (bytecode if compiled, source text otherwise) + int luaIdx = 0; + log?.Invoke($"CompiledLuaBytecode: {(CompiledLuaBytecode != null ? CompiledLuaBytecode.Count + " entries" : "null")}", LogType.Log); foreach (LuaFile luaFile in luaFiles) { AlignToFourBytes(writer); luaOffset.DataOffsets.Add(writer.BaseStream.Position); - writer.Write(Encoding.UTF8.GetBytes(luaFile.LuaScript)); + + if (CompiledLuaBytecode != null && CompiledLuaBytecode.TryGetValue(luaFile.LuaScript, out byte[] bytecode)) + { + log?.Invoke($" Lua [{luaIdx}]: using bytecode ({bytecode.Length} bytes)", LogType.Log); + writer.Write(bytecode); + } + else + { + byte[] srcBytes = Encoding.UTF8.GetBytes(luaFile.LuaScript); + log?.Invoke($" Lua [{luaIdx}]: FALLBACK to source ({srcBytes.Length} bytes, script hash={luaFile.LuaScript.GetHashCode()})", LogType.Warning); + writer.Write(srcBytes); + } + luaIdx++; } // Mesh data