diff --git a/modules/client/commands/init.luau b/modules/client/commands/init.luau index 0e9693d..0d064fa 100644 --- a/modules/client/commands/init.luau +++ b/modules/client/commands/init.luau @@ -83,20 +83,48 @@ function Commands:Run(command, context, arguments) return true, result end -function Commands:ProcessServer(data, input, inputContext) - return Network:InvokeServer("processCommand", data, input, inputContext) +function Commands:ProcessServer(prefix, commandName, arguments, contextData, input, inputContext) + contextData.prefix = prefix + contextData.commandName = commandName + contextData.arguments = arguments + return Network:InvokeServer("processCommand", contextData, input, inputContext) end -function Commands:ProcessClient(input, inputContext) - -- Check if the input matches a command string, if not then we can ignore it. +function Commands:ProcessClient(namespace, commandName, arguments, input, inputContext) + local command = namespace.commandLookup[string.lower(commandName)] + if not command then + return ProcessResult.Fail, `Command "{commandName}" was not found.` + end + + local context = Commands:CreateContext(command, input, inputContext) + local valid, message = Commands:Validate(command, context, arguments) + if not valid then + return ProcessResult.Error, message + end + + local success, commandMessage = Commands:Run(command, context, arguments) + if not success then + return ProcessResult.Error, commandMessage + end + + return ProcessResult.Success, commandMessage, context.Data +end + +function Commands:Process(input, inputContext) + -- Check if the input matches a command string, if not then we can silently ignore it. if not string.match(input, "^.+/") then - return ProcessResult.Fail, "Not a valid command string." + if inputContext ~= InputContext.Chat then + Output:append(Output.MessageType.Error, "Not a valid command string.") + end + + return end local arguments = string.split(input, "/") local prefix = table.remove(arguments, 1) if not prefix then - return ProcessResult.Fail, "No prefix was found." + Output:append(Output.MessageType.Error, "No prefix was found.") + return end -- Remove last argument if empty. @@ -115,53 +143,41 @@ function Commands:ProcessClient(input, inputContext) -- Not default namespace, command should be the 2nd split string (prefix/command/arguments). commandName = table.remove(arguments, 1) else - return ProcessResult.Fail, "No command was given." - end - - local command = namespace.commandLookup[string.lower(commandName)] - if not command then - return ProcessResult.Fail, `Command "{commandName}" was not found.` - end - - local context = Commands:CreateContext(command, input, inputContext) - local valid, message = Commands:Validate(command, context, arguments) - if not valid then - return ProcessResult.Error, message - end - - local success, commandMessage = Commands:Run(command, context, arguments) - if not success then - return ProcessResult.Error, commandMessage - end - - return ProcessResult.Success, commandMessage, context.Data -end - -function Commands:Process(input, inputContext) - local clientResult, clientMessage, contextData = Commands:ProcessClient(input, inputContext) - if clientResult ~= ProcessResult.Success then - -- Only output fail messages if the inputContext wasn't the chat (because you can also send chat messages in chat, duh), - -- Error messages should always be displayed. - if - clientResult == ProcessResult.Error - or (clientResult == ProcessResult.Fail and inputContext ~= InputContext.Chat) - then - Output:append(Output.MessageType.Error, clientMessage or "Unexpected error occured while running command.") - end - + Output:append(Output.MessageType.Error, "No command was given.") return - elseif clientMessage then - Output:append(Output.MessageType.Success, clientMessage) end + commandName = commandName:split(" ") + + for i, commandName in pairs(commandName) do + local clientResult, clientMessage, contextData = + Commands:ProcessClient(namespace, commandName, arguments, input, inputContext) + if clientResult ~= ProcessResult.Success then + -- Only output fail messages if the inputContext wasn't the chat (because you can also send chat messages in chat, duh), + -- Error messages should always be displayed. + if + clientResult == ProcessResult.Error + or (clientResult == ProcessResult.Fail and inputContext ~= InputContext.Chat) + then + Output:append( + Output.MessageType.Error, + clientMessage or "Unexpected error occured while running command." + ) + end - local serverResult, serverMessage = Commands:ProcessServer(contextData, input, inputContext) - if serverResult ~= ProcessResult.Success then - -- We don't check if the ProcessResult was a Fail or Error here, because it should always be an Error. - Output:append(Output.MessageType.Error, serverMessage or "Unexpected error occured while running command.") + continue + elseif clientMessage then + Output:append(Output.MessageType.Success, clientMessage) + end - return - elseif serverMessage then - Output:append(Output.MessageType.Success, serverMessage) + local serverResult, serverMessage = + Commands:ProcessServer(prefix, commandName, arguments, contextData, input, inputContext) + if serverResult ~= ProcessResult.Success then + -- We don't check if the ProcessResult was a Fail or Error here, because it should always be an Error. + Output:append(Output.MessageType.Error, serverMessage or "Unexpected error occured while running command.") + continue + elseif serverMessage then + Output:append(Output.MessageType.Success, serverMessage) + end end end diff --git a/modules/client/wm/sandbox/rules.luau b/modules/client/wm/sandbox/rules.luau index 479d2a0..5a55b8a 100644 --- a/modules/client/wm/sandbox/rules.luau +++ b/modules/client/wm/sandbox/rules.luau @@ -40,19 +40,16 @@ function Module.Init(_, Sandbox) local parentThread = coroutine.running() local warnThread + local fullName = GetFullName(self) + local traceback = debug.traceback(`Infinite yield possible on '{fullName}:WaitForChild("{childName}")`, 2) if not timeOut and typeof(self) == "Instance" then - local fullName = GetFullName(self) warnThread = task.delay(5, function() -- Incase the WaitForChild thread stopped yielding (could be because the function threw an error), then don't emit the warning if coroutine.status(parentThread) ~= "suspended" then return end - -- TODO: Add stacktrace - ManagerCommunication:Send( - "warn", - `Infinite yield possible on '{fullName}:WaitForChild("{childName}")'` - ) + ManagerCommunication:Send("warn", traceback) end) sandbox.Threads[warnThread] = true end diff --git a/modules/server/commands/init.luau b/modules/server/commands/init.luau index 5f03957..e2b6863 100644 --- a/modules/server/commands/init.luau +++ b/modules/server/commands/init.luau @@ -79,34 +79,11 @@ function Commands:Run(command, context, arguments) end function Commands:Process(player, data, input, inputContext) - -- Check if the input matches a command string, if not then we can ignore it. - if not string.match(input, "^.+/") then - return ProcessResult.Fail, "Not a valid command string." - end - - local arguments = string.split(input, "/") - local prefix = table.remove(arguments, 1) - if not prefix then - return ProcessResult.Fail, "No prefix was found." - end - - -- Remove last argument if empty. - if #arguments[#arguments] == 0 then - table.remove(arguments, #arguments) - end - - -- Find namespace, and get the command. - local commandName + local prefix, arguments, commandName = data.prefix, data.arguments, data.commandName local namespace = Commands.namespaceLookup[string.lower(prefix)] if not namespace then -- No namespace with prefix found, default namespace, command should be the 1st split string (command/arguments). - commandName = prefix namespace = Commands.namespaces.default - elseif arguments[1] and #arguments[1] > 0 then -- Check if there is a 2nd split string, otherwise no command was given. - -- Not default namespace, command should be the 2nd split string (prefix/command/arguments). - commandName = table.remove(arguments, 1) - else - return ProcessResult.Fail, "No command was given." end local command = namespace.commandLookup[string.lower(commandName)] diff --git a/modules/server/wm/sandbox/rules.luau b/modules/server/wm/sandbox/rules.luau index 9842582..31946cf 100644 --- a/modules/server/wm/sandbox/rules.luau +++ b/modules/server/wm/sandbox/rules.luau @@ -95,20 +95,16 @@ function Module.Init(_, Sandbox) local parentThread = coroutine.running() local warnThread + local fullName = GetFullName(self) + local traceback = debug.traceback(`Infinite yield possible on '{fullName}:WaitForChild("{childName}")`, 2) if not timeOut and typeof(self) == "Instance" then - local fullName = GetFullName(self) warnThread = task.delay(5, function() -- Incase the WaitForChild thread stopped yielding (could be because the function threw an error), then don't emit the warning if coroutine.status(parentThread) ~= "suspended" then return end - -- TODO: Add stacktrace - ManagerCommunication:Send( - "warn", - sandbox.Owner, - `Infinite yield possible on '{fullName}:WaitForChild("{childName}")'` - ) + ManagerCommunication:Send("warn", sandbox.Owner, traceback) end) sandbox.Threads[warnThread] = true end @@ -150,8 +146,7 @@ function Module.Init(_, Sandbox) assertTerminated(sandbox) if type(func) ~= "function" then - -- TODO: Add stacktrace - ManagerCommunication:Send("error", sandbox.Owner, Errors.connectNonFunction()) + ManagerCommunication:Send("error", sandbox.Owner, debug.traceback(Errors.connectNonFunction(), 2)) return Connect(unwrap(self)) end @@ -171,8 +166,7 @@ function Module.Init(_, Sandbox) assertTerminated(sandbox) if type(func) ~= "function" then - -- TODO: Add stacktrace - ManagerCommunication:Send("error", sandbox.Owner, Errors.connectNonFunction()) + ManagerCommunication:Send("error", sandbox.Owner, debug.traceback(Errors.connectNonFunction(), 2)) return ConnectParallel(unwrap(self)) end @@ -192,8 +186,7 @@ function Module.Init(_, Sandbox) assertTerminated(sandbox) if type(func) ~= "function" then - -- TODO: Add stacktrace - ManagerCommunication:Send("error", sandbox.Owner, Errors.connectNonFunction()) + ManagerCommunication:Send("error", sandbox.Owner, debug.traceback(Errors.connectNonFunction(), 2)) return Once(unwrap(self)) end