Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion rokit.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ rojo = "rojo-rbx/rojo@7.7.0-rc.1"
lune = "lune-org/lune@0.10.4"
blink = "1axen/blink@0.18.6"
darklua = "seaofvoices/darklua@0.17.3"
selene = "Kampfkarren/selene@0.29.0"
luau-lsp = "JohnnyMorganz/luau-lsp@1.59.0"
StyLua = "JohnnyMorganz/StyLua@2.3.1"
selene = "Kampfkarren/selene@0.29.0"
184 changes: 128 additions & 56 deletions src/shared/debugger.luau
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
local iris = require("@pkg/iris").Init()
local stack = require("@pkg/stack")

--[=[
@interface log
@within debugger
@field message string -- The message logged.
@field status "success" | "warn" | "error" -- The status of the log.
@field time number -- When the message was logged.
@field count number -- The amount of times the same message was sent. A value of 1 means the message isn't repeated.

Represents a logged message.
]=]
export type log = {
message: string,
status: "success" | "warn" | "error",
Expand All @@ -19,70 +29,132 @@ local output = stack.new(MAX_LOGS)

local started = false

return {
iris = iris,
output = output,

log = function(message: string, status: "success" | "warn" | "error")
local now = os.clock()
local last = output:peek() :: log?

if last and last.message == message and last.status == status then
last.count += 1
last.time = now

return
end

output:push({
message = message,
status = status,
time = now,
count = 1,
})
end,

add_debugger = function(window: { string }, callback: () -> (), priority: number)
table.insert(debuggers, {
window = window,
callback = callback,
priority = priority,
})

table.sort(debuggers, function(a, b)
return a.priority < b.priority
end)
end,

start = function(start_callback: () -> boolean?)
if started then
--[=[
@class debugger

Implements logging and debug widgets.
]=]
local debugger = {}

--[=[
@prop iris typeof(iris)
@within debugger

A reference to the Iris library, which implements immediate-mode UI.
This is used for debugger windows.

See [Iris' documentation](https://sirmallard.github.io/Iris/docs/intro)
and it's [API reference.](https://sirmallard.github.io/Iris/api/Iris)
]=]
debugger.iris = iris

--[=[
@prop output stack<Log>
@within debugger

A stack of all messages logged, which is rendered in the output.
]=]
debugger.output = output

--[=[
@function log
@within debugger

Logs a message to the output. Logs are categorized by status:

- `success`
- `warn`
- `error`, noting that errors do not block execution

@param message string -- The message to log.
@param status "success" | "warn" | "error" -- The status of the message.
]=]
function debugger.log(message: string, status: "success" | "warn" | "error")
local now = os.clock()
local last = output:peek() :: log?

if last and last.message == message and last.status == status then
last.count += 1
last.time = now

return
end

output:push({
message = message,
status = status,
time = now,
count = 1,
})
end

--[=[
@function add_debugger
@within debugger

Adds a new window to the debugger. Every frame, the callback is called to
render the window.

@param window { string } -- A tuple table, where the first value is the window's name.
@param callback () -> () -- A callback which is called every frame to render the window.
@param priority number -- The priority of the debugger.
]=]
function debugger.add_debugger(window: { string }, callback: () -> (), priority: number)
table.insert(debuggers, {
window = window,
callback = callback,
priority = priority,
})

table.sort(debuggers, function(a, b)
return a.priority < b.priority
end)
end

--[=[
@function start
@within debugger

Starts the debugger.

@param start_callback () -> boolean? -- If specified, whether if the debugger should be rendered.
]=]
function debugger.start(start_callback: () -> boolean?)
if started then
return
end

started = true

iris:Connect(function()
if start_callback ~= nil and start_callback() == false then
return
end

started = true
iris.PushConfig({ RichText = true })

iris:Connect(function()
if start_callback ~= nil and start_callback() == false then
return
end
for name, debugger in ipairs(debuggers) do
iris.Window(debugger.window)

iris.PushConfig({ RichText = true })
debugger.callback()

for _, debugger in ipairs(debuggers) do
iris.Window(debugger.window)
iris.End()
end

debugger.callback()
iris.PopConfig()
end)
end

iris.End()
end
--[=[
@function cleanup
@within debugger

iris.PopConfig()
end)
end,
Cleans outputted logs and all debuggers.
]=]
function debugger.cleanup()
table.clear(debuggers)

cleanup = function()
table.clear(debuggers)
output:clear()
end

output:clear()
end,
}
return table.freeze(debugger)