From acc6e97db041d1a2b9fcacb714e5eb397a07e35e Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Sat, 11 Nov 2023 10:00:54 -0800 Subject: [PATCH] debug animations https://merveilles.town/@akkartik/111333888472146119 --- 0002-Debug_animations_in_progress | 2 ++ 0003-refresh_debug_animations | 15 +++++++++++++++ 0004-on.update | 3 +++ 0005-animate | 21 +++++++++++++++++++++ 0006-loiter | 5 +++++ 0007-save_callstack | 7 +++++++ 0008-Debug_animation_period | 1 + 7 files changed, 54 insertions(+) create mode 100644 0002-Debug_animations_in_progress create mode 100644 0003-refresh_debug_animations create mode 100644 0004-on.update create mode 100644 0005-animate create mode 100644 0006-loiter create mode 100644 0007-save_callstack create mode 100644 0008-Debug_animation_period diff --git a/0002-Debug_animations_in_progress b/0002-Debug_animations_in_progress new file mode 100644 index 0000000..11ada4a --- /dev/null +++ b/0002-Debug_animations_in_progress @@ -0,0 +1,2 @@ +-- Intermediate state during debug animations. +Debug_animations_in_progress = {} diff --git a/0003-refresh_debug_animations b/0003-refresh_debug_animations new file mode 100644 index 0000000..6aaa9c9 --- /dev/null +++ b/0003-refresh_debug_animations @@ -0,0 +1,15 @@ +-- update any in-progress debug animations +-- return whether any work remains +refresh_debug_animations = function() + local a = Debug_animations_in_progress + for i=#a,1,-1 do + if coroutine.status(a[i].co) == 'dead' then + table.remove(a, i) + elseif Current_time > a[i].next_run then + local status, err = coroutine.resume(a[i].co) + if status == false then error(err) end + a[i].next_run = Current_time + Debug_animation_period + end + end + return #a > 0 +end diff --git a/0004-on.update b/0004-on.update new file mode 100644 index 0000000..2e7c635 --- /dev/null +++ b/0004-on.update @@ -0,0 +1,3 @@ +on.update = function() + refresh_debug_animations() +end diff --git a/0005-animate b/0005-animate new file mode 100644 index 0000000..2ffb915 --- /dev/null +++ b/0005-animate @@ -0,0 +1,21 @@ +-- A debugging aid to help animate intermediate results within f. +-- Pause animation in the current frame using loiter(). +-- Try to only have one such call in your program. +-- You can have multiple, but things might get confusing if one of them indirectly calls the other, +-- or more generally if a single function ever loiters sometimes under the call tree of one and sometimes under the other. +animate = function(f, ...) + local args = {...} + Error_with_callstack = nil + local co = coroutine.create( + function() + xpcall(function() + f(unpack(args)) + end, + save_callstack) + end) + coroutine.resume(co, ...) + if Error_with_callstack then + error(Error_with_callstack) + end + table.insert(Debug_animations_in_progress, {co=co, next_run=Current_time+0.3}) +end diff --git a/0006-loiter b/0006-loiter new file mode 100644 index 0000000..6c06705 --- /dev/null +++ b/0006-loiter @@ -0,0 +1,5 @@ +-- A debugging aid to animate intermediate results in computations. +-- Can only be called from functions invoked using `animate()`. +loiter = function() + coroutine.yield() +end diff --git a/0007-save_callstack b/0007-save_callstack new file mode 100644 index 0000000..2cd0970 --- /dev/null +++ b/0007-save_callstack @@ -0,0 +1,7 @@ +-- Internal helper for animate() +-- Records the stack of function calls that led to any error within a debug animation coroutine. +-- Lua normally prints out errors with callstacks, but coroutines interrupt the stack unless we do some additional work. +save_callstack = function(err) + local callstack = debug.traceback('', --[[stack frame]]2) + Error_with_callstack = 'Error: ' .. cleaned_up_frame(tostring(err))..'\n'..cleaned_up_callstack(callstack) +end diff --git a/0008-Debug_animation_period b/0008-Debug_animation_period new file mode 100644 index 0000000..ec87efe --- /dev/null +++ b/0008-Debug_animation_period @@ -0,0 +1 @@ +Debug_animation_period = 0.3 -- seconds between animation frames