From 76e4bc4f25c1fe142239f7545272aa7377ff95a5 Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 1 Feb 2026 20:51:13 -0600 Subject: [PATCH] Add Int64 conversion functions to EncodedValue.lua and enhance script middleware selection in project.rs - Implemented int64FromPod and int64ToPod functions for converting between Lua numbers and msgpack Int64 representation, addressing precision concerns for large integers. - Updated the middleware_for_script function to determine the correct script directory based on the RunContext property, improving middleware selection for Script instances. - Adjusted syncback logic to utilize the new middleware selection for Scripts, ensuring accurate handling of different script types during project synchronization. --- plugin/rbx_dom_lua/EncodedValue.lua | 34 +++++++++++++++++-- src/snapshot_middleware/project.rs | 51 ++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/plugin/rbx_dom_lua/EncodedValue.lua b/plugin/rbx_dom_lua/EncodedValue.lua index 0c89fcd5..6cc44ee0 100644 --- a/plugin/rbx_dom_lua/EncodedValue.lua +++ b/plugin/rbx_dom_lua/EncodedValue.lua @@ -4,6 +4,36 @@ local function identity(...) return ... end +-- Convert msgpack Int64 representation to a Lua number +-- Note: Lua numbers are doubles, so precision may be lost for values > 2^53 +local function int64FromPod(pod) + -- If it's already a number, return it + if type(pod) == "number" then + return pod + end + -- If it's a msgpack Int64 table, convert to number + if type(pod) == "table" and pod.mostSignificantPart ~= nil and pod.leastSignificantPart ~= nil then + local most = pod.mostSignificantPart + local least = pod.leastSignificantPart + local value = most * 0x100000000 + least + -- Handle signed integers (negative values have MSB set) + if most >= 0x80000000 then + value = value - 0x10000000000000000 + end + return value + end + return pod +end + +-- Convert a Lua number to msgpack Int64 if needed, otherwise return as-is +-- For values that fit in Lua's safe integer range, returning plain number works +-- because msgpack will encode it appropriately +local function int64ToPod(roblox) + -- Just return the number - msgpack will handle encoding + -- For values outside safe range, this might lose precision + return roblox +end + local function unpackDecoder(f) return function(value) return f(unpack(value)) @@ -302,8 +332,8 @@ types = { }, Int64 = { - fromPod = identity, - toPod = identity, + fromPod = int64FromPod, + toPod = int64ToPod, }, MaterialColors = { diff --git a/src/snapshot_middleware/project.rs b/src/snapshot_middleware/project.rs index 60227d33..8d360915 100644 --- a/src/snapshot_middleware/project.rs +++ b/src/snapshot_middleware/project.rs @@ -55,6 +55,44 @@ fn can_transition_class(old_class: &str, new_class: &str) -> bool { false } +/// Determines the correct script-dir middleware for a Script based on its RunContext property. +/// +/// Script class has a RunContext property that determines the correct middleware: +/// - Server → ServerScriptDir (init.server.luau) +/// - Client → ClientScriptDir (init.client.luau) +/// - Plugin → PluginScriptDir (init.plugin.luau) +/// - Legacy → LegacyScriptDir (init.legacy.luau) +/// +/// If RunContext is not set or unrecognized, defaults to LegacyScriptDir. +fn middleware_for_script(inst: &Instance) -> Middleware { + let run_context_enums = rbx_reflection_database::get() + .ok() + .and_then(|db| db.enums.get("RunContext")) + .map(|e| &e.items); + + let run_context_value = inst.properties.get(&ustr("RunContext")).and_then(|v| match v { + Variant::Enum(e) => Some(e.to_u32()), + _ => None, + }); + + if let (Some(enums), Some(value)) = (run_context_enums, run_context_value) { + for (name, &enum_value) in enums { + if enum_value == value { + return match name.as_ref() { + "Server" => Middleware::ServerScriptDir, + "Client" => Middleware::ClientScriptDir, + "Plugin" => Middleware::PluginScriptDir, + "Legacy" => Middleware::LegacyScriptDir, + _ => Middleware::LegacyScriptDir, + }; + } + } + } + + // Default to LegacyScriptDir if no RunContext or unrecognized + Middleware::LegacyScriptDir +} + pub fn snapshot_project( context: &InstanceContext, vfs: &Vfs, @@ -424,10 +462,10 @@ pub fn syncback_project<'sync>( // determine the middleware from the new instance and let // syncback create the necessary files/directories. if !snapshot.data.is_incremental() { - // Determine middleware based on new instance class + // Determine middleware based on new instance class (and RunContext for Scripts) let inferred_middleware = match new_inst.class.as_str() { "ModuleScript" => Middleware::ModuleScriptDir, - "Script" => Middleware::ServerScriptDir, + "Script" => middleware_for_script(new_inst), "LocalScript" => Middleware::LocalScriptDir, "Folder" => Middleware::Dir, // For other classes, default to Dir @@ -459,7 +497,7 @@ pub fn syncback_project<'sync>( if middleware == Middleware::Dir { let script_middleware = match new_inst.class.as_str() { "ModuleScript" => Some(Middleware::ModuleScriptDir), - "Script" => Some(Middleware::ServerScriptDir), + "Script" => Some(middleware_for_script(new_inst)), "LocalScript" => Some(Middleware::LocalScriptDir), _ => None, }; @@ -482,6 +520,8 @@ pub fn syncback_project<'sync>( | Middleware::ServerScriptDir | Middleware::ClientScriptDir | Middleware::LocalScriptDir + | Middleware::PluginScriptDir + | Middleware::LegacyScriptDir ); if is_script_dir_middleware && new_inst.class == "Folder" { log::debug!( @@ -493,13 +533,14 @@ pub fn syncback_project<'sync>( } // Case 3: Filesystem has a script-dir but new instance is a DIFFERENT script type. - // e.g., ModuleScriptDir (init.luau) but new instance is Script → use ServerScriptDir. + // e.g., ModuleScriptDir (init.luau) but new instance is Script → use correct script-dir. // Without this, changing a ModuleScript to Script in Studio would silently keep // the old init.luau file, causing the script to be read as ModuleScript on next build. + // For Scripts, we also check RunContext to pick the correct middleware (Server/Client/Plugin/Legacy). if is_script_dir_middleware { let correct_middleware = match new_inst.class.as_str() { "ModuleScript" => Some(Middleware::ModuleScriptDir), - "Script" => Some(Middleware::ServerScriptDir), + "Script" => Some(middleware_for_script(new_inst)), "LocalScript" => Some(Middleware::LocalScriptDir), _ => None, };