/ld27

To get this branch, use:
bzr branch http://9ix.org/bzr/ld27

« back to all changes in this revision

Viewing changes to zoetrope/utils/debug.lua

  • Committer: Josh C
  • Date: 2013-08-24 04:11:48 UTC
  • Revision ID: josh@9ix.org-20130824041148-npjyxboq6urktc8w
switch mazes

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
-- Class: DebugConsole
 
2
-- It can be used to keep track of fps, the position of a sprite,
 
3
-- and so on. It only updates when visible.
 
4
--
 
5
-- This also allows debugging hotkeys -- e.g. you could set it so that
 
6
-- pressing Control-Alt-I toggles invincibility of the player sprite.
 
7
-- Out of the box:
 
8
--              - Control-Alt-F toggles fullscreen
 
9
--              - Control-Alt-Q quits the app.
 
10
--              - Control-Alt-P deactivates the view.
 
11
--              - Control-Alt-R reloads all app code from on disk.
 
12
--              - Control-Alt-S saves a screenshot to the app's directory --
 
13
--                see https://love2d.org/wiki/love.filesystem for where this is.
 
14
 
 
15
DebugConsole = Group:extend
 
16
{
 
17
        -- Property: toggleKey
 
18
        -- What key toggles visibility. By default, this is the tab key.
 
19
        toggleKey = 'tab',
 
20
 
 
21
        -- Property: hotkeyModifiers
 
22
        -- A table of modifier keys that must be held in order to activate
 
23
        -- a debugging hotkey (set via <addHotkey()>). If you want hotkeys to
 
24
        -- activate without having to hold any keys down, set this to nil.
 
25
        hotkeyModifiers = {'ctrl', 'alt'},
 
26
 
 
27
        -- Property: watchBasics
 
28
        -- If true, the console will automatically start watching the frames
 
29
        -- per second and memory usage. Changing this value after the object has
 
30
        -- been created has no effect.
 
31
        watchBasics = true,
 
32
 
 
33
        -- Property: watchWidth
 
34
        -- How wide the sidebar, where watch values are displayed, should be.
 
35
        watchWidth = 150,
 
36
 
 
37
        -- Property: inputHistory
 
38
        -- A table of previously-entered commands.
 
39
        inputHistory = {},
 
40
 
 
41
        -- Property: inputHistoryIndex
 
42
        -- Which history entry, if any, we are displaying.
 
43
        inputHistoryIndex = 1,
 
44
 
 
45
        -- Property: bg
 
46
        -- The background <Fill> used to darken the view.
 
47
 
 
48
        -- Property: log
 
49
        -- The <Text> sprite showing recent lines in the log.
 
50
 
 
51
        -- Property: watchList
 
52
        -- The <Text> sprite showing the state of all watched variables.
 
53
 
 
54
        -- Property: input
 
55
        -- The <TextInput> that the user types into to enter commands.
 
56
 
 
57
        -- Property: prompt
 
58
        -- The <Text> sprite that shows a > in front of commands.
 
59
 
 
60
        -- internal property: _bindings
 
61
        -- Keeps track of debugging hotkeys.
 
62
 
 
63
        new = function (self, obj)
 
64
                local width = the.app.width
 
65
                local height = the.app.height
 
66
 
 
67
                obj = self:extend(obj)
 
68
                
 
69
                obj.visible = false
 
70
                obj._watches = {}
 
71
                obj._hotkeys = {}
 
72
 
 
73
                obj.fill = Fill:new{ x = 0, y = 0, width = width, height = height, fill = {0, 0, 0, 200} }
 
74
                obj:add(obj.fill)
 
75
 
 
76
                obj.log = Text:new{ x = 4, y = 4, width = width - self.watchWidth - 8, height = height - 8, text = '' }
 
77
                obj:add(obj.log)
 
78
 
 
79
                obj.watchList = Text:new{ x = width - self.watchWidth - 4, y = 4,
 
80
                                                                   width = self.watchWidth - 8, height = height - 8, text = '', wordWrap = false }
 
81
                obj:add(obj.watchList)
 
82
 
 
83
                obj.prompt = Text:new{ x = 4, y = 0, width = 100, text = '>' }
 
84
                obj:add(obj.prompt)
 
85
 
 
86
                local inputIndent = obj.log._fontObj:getWidth('>') + 4
 
87
                obj.input = TextInput:new
 
88
                {
 
89
                        x = inputIndent, y = 0, width = the.app.width,
 
90
                        active = false,
 
91
                        onType = function (self, char)
 
92
                                return char ~= the.console.toggleKey
 
93
                        end
 
94
                }
 
95
                obj:add(obj.input)
 
96
 
 
97
                -- some default behavior
 
98
 
 
99
                obj:addHotkey('f', function() the.app:toggleFullscreen() end)
 
100
                obj:addHotkey('p', function()
 
101
                        the.view.active = not the.view.active
 
102
                        if the.view.active then
 
103
                                the.view:tint()
 
104
                        else
 
105
                                the.view:tint(0, 0, 0, 200)
 
106
                        end
 
107
                end)
 
108
                obj:addHotkey('q', love.event.quit)
 
109
                if debugger then obj:addHotkey('r', debugger.reload) end
 
110
                obj:addHotkey('s', function() the.app:saveScreenshot('screenshot.png') end)
 
111
                
 
112
                if obj.watchBasics then
 
113
                        obj:watch('FPS', 'love.timer.getFPS()')
 
114
                        obj:watch('Memory', 'math.floor(collectgarbage("count") / 1024) .. "M"')
 
115
                end
 
116
 
 
117
                -- hijack print function
 
118
                -- this is nasty to debug if it goes wrong, be careful
 
119
 
 
120
                obj._oldPrint = print
 
121
                print = function (...)
 
122
                        local caller = debug.getinfo(2)
 
123
 
 
124
                        if caller.linedefined ~= 0 then
 
125
                                obj.log.text = obj.log.text .. '(' .. caller.short_src .. ':' .. caller.linedefined .. ') '
 
126
                        end
 
127
 
 
128
                        for _, value in pairs{...} do
 
129
                                obj.log.text = obj.log.text .. tostring(value) .. ' '
 
130
                        end
 
131
 
 
132
                        obj.log.text = obj.log.text .. '\n'
 
133
                        obj._updateLog = true
 
134
                        obj._oldPrint(...)
 
135
                end
 
136
 
 
137
                debugger._unsourcedPrint = function (...)
 
138
                        for _, value in pairs{...} do
 
139
                                obj.log.text = obj.log.text .. tostring(value) .. ' '
 
140
                        end
 
141
 
 
142
                        obj.log.text = obj.log.text .. '\n'
 
143
                        obj._updateLog = true
 
144
                        obj._oldPrint(...)
 
145
                end
 
146
 
 
147
                the.console = obj
 
148
                if obj.onNew then obj.onNew() end
 
149
                return obj
 
150
        end,
 
151
 
 
152
        -- Method: watch
 
153
        -- Adds an expression to be watched.
 
154
        --
 
155
        -- Arguments:
 
156
        --              label - string label
 
157
        --              expression - expression to evaluate as a string
 
158
 
 
159
        watch = function (self, label, expression)
 
160
                table.insert(self._watches, { label = label,
 
161
                                                                          func = loadstring('return ' .. expression) })
 
162
        end,
 
163
 
 
164
        -- Method: addHotkey
 
165
        -- Adds a hotkey to execute a function. This hotkey will require
 
166
        -- holding down whatever modifiers are set in <hotkeyModifiers>.
 
167
        --
 
168
        -- Arguments:
 
169
        --              key - key to trigger the hotkey
 
170
        --              func - function to run. This will receive the key that
 
171
        --                         was pressed, so you can re-use functions (i.e. 
 
172
        --                         the 1 key loads level 1, the 2 key loads level 2).
 
173
        --
 
174
        -- Returns:
 
175
        --              nothing
 
176
 
 
177
        addHotkey = function (self, key, func)
 
178
                table.insert(self._hotkeys, { key = key, func = func })
 
179
        end,
 
180
 
 
181
        -- Method: execute
 
182
        -- Safely executes a string of code and prints the result.
 
183
        --
 
184
        -- Arguments:
 
185
        --              code - string code to execute
 
186
        --
 
187
        -- Returns:
 
188
        --              string result
 
189
 
 
190
        execute = function (self, code)
 
191
                if string.sub(code, 1, 1) == '=' then
 
192
                        code = 'debugger._unsourcedPrint (' .. string.sub(code, 2) .. ')'
 
193
                end
 
194
 
 
195
                local func, err = loadstring(code)
 
196
 
 
197
                if func then
 
198
                        local ok, result = pcall(func)
 
199
 
 
200
                        if not ok then
 
201
                                debugger._unsourcedPrint('Error, ' .. tostring(result) .. '\n')
 
202
                        else
 
203
                                debugger._unsourcedPrint('')
 
204
                        end
 
205
 
 
206
                        return tostring(result)
 
207
                else
 
208
                        debugger._unsourcedPrint('Syntax error, ' .. string.gsub(tostring(err), '^.*:', '') .. '\n')
 
209
                end
 
210
        end,
 
211
 
 
212
        -- Method: show
 
213
        -- Shows the debug console.
 
214
        -- 
 
215
        -- Arguments:
 
216
        --              none
 
217
        --
 
218
        -- Returns:
 
219
        --              nothing
 
220
 
 
221
        show = function (self)
 
222
                self.visible = true
 
223
                self.input.active = true
 
224
        end,
 
225
 
 
226
        -- Method: hide
 
227
        -- Hides the debug console.
 
228
        -- 
 
229
        -- Arguments:
 
230
        --              none
 
231
        --
 
232
        -- Returns:
 
233
        --              nothing
 
234
 
 
235
        hide = function (self)
 
236
                self.visible = false
 
237
                self.input.active = false
 
238
        end,
 
239
 
 
240
        update = function (self, elapsed)
 
241
                -- listen for visibility key
 
242
 
 
243
                if the.keys:justPressed(self.toggleKey) then
 
244
                        self.visible = not self.visible
 
245
                        self.input.active = self.visible
 
246
                end
 
247
 
 
248
                -- listen for hotkeys
 
249
 
 
250
                local modifiers = (self.hotkeyModifiers == nil)
 
251
 
 
252
                if not modifiers then
 
253
                        modifiers = true
 
254
 
 
255
                        for _, key in pairs(self.hotkeyModifiers) do
 
256
                                if not the.keys:pressed(key) then
 
257
                                        modifiers = false
 
258
                                        break
 
259
                                end
 
260
                        end
 
261
                end
 
262
 
 
263
                if modifiers then
 
264
                        for _, hotkey in pairs(self._hotkeys) do
 
265
                                if the.keys:justPressed(hotkey.key) then
 
266
                                        hotkey.func(hotkey.key)
 
267
                                end
 
268
                        end
 
269
                end
 
270
 
 
271
                if self.visible then
 
272
                        -- update watches
 
273
 
 
274
                        self.watchList.text = ''
 
275
                        
 
276
                        for _, watch in pairs(self._watches) do
 
277
                                local ok, value = pcall(watch.func)
 
278
                                if not ok then value = nil end
 
279
 
 
280
                                self.watchList.text = self.watchList.text .. watch.label .. ': ' .. tostring(value) .. '\n'
 
281
                        end
 
282
 
 
283
                        -- update log
 
284
 
 
285
                        if self._updateLog then
 
286
                                local lineHeight = self.log._fontObj:getHeight()
 
287
                                local _, height = self.log:getSize()
 
288
                                local linesToDelete = math.ceil((height - the.app.height - 20) / lineHeight)
 
289
                                
 
290
                                if linesToDelete > 0 then
 
291
                                        self.log.text = string.gsub(self.log.text, '.-\n', '', linesToDelete) 
 
292
                                        height = height - linesToDelete * lineHeight
 
293
                                end
 
294
 
 
295
                                self.prompt.y = height + 4
 
296
                                self.input.y = height + 4
 
297
                                self._updateLog = false
 
298
                        end
 
299
 
 
300
                        -- handle special keys at the console
 
301
 
 
302
                        if the.keys:pressed('ctrl') and the.keys:justPressed('a') then
 
303
                                self.input.caret = 0
 
304
                        end
 
305
 
 
306
                        if the.keys:pressed('ctrl') and the.keys:justPressed('e') then
 
307
                                self.input.caret = string.len(self.input.text)
 
308
                        end
 
309
 
 
310
                        if the.keys:pressed('ctrl') and the.keys:justPressed('k') then
 
311
                                self.input.caret = 0
 
312
                                self.input.text = ''
 
313
                        end
 
314
 
 
315
                        if the.keys:justPressed('up') and self.inputHistoryIndex > 1 then
 
316
                                -- save what the user was in the middle of typing
 
317
 
 
318
                                self.inputHistory[self.inputHistoryIndex] = self.input.text
 
319
 
 
320
                                self.input.text = self.inputHistory[self.inputHistoryIndex - 1]
 
321
                                self.input.caret = string.len(self.input.text)
 
322
                                self.inputHistoryIndex = self.inputHistoryIndex - 1
 
323
                        end
 
324
 
 
325
                        if the.keys:justPressed('down') and self.inputHistoryIndex < #self.inputHistory then
 
326
                                self.input.text = self.inputHistory[self.inputHistoryIndex + 1]
 
327
                                self.input.caret = string.len(self.input.text)
 
328
                                self.inputHistoryIndex = self.inputHistoryIndex + 1
 
329
                        end
 
330
 
 
331
                        if the.keys:justPressed('return') then
 
332
                                debugger._unsourcedPrint('>' .. self.input.text)
 
333
                                self:execute(self.input.text)
 
334
                                table.insert(self.inputHistory, self.inputHistoryIndex, self.input.text)
 
335
 
 
336
                                while #self.inputHistory > self.inputHistoryIndex do
 
337
                                        table.remove(self.inputHistory)
 
338
                                end
 
339
 
 
340
                                self.inputHistoryIndex = self.inputHistoryIndex + 1
 
341
                                self.input.text = ''
 
342
                                self.input.caret = 0
 
343
                        end
 
344
                end
 
345
 
 
346
                Group.update(self, elapsed)
 
347
        end
 
348
}
 
349
 
 
350
-- Function: debugger.reload
 
351
-- Resets the entire app and forces all code to be reloaded from 
 
352
-- on disk. via https://love2d.org/forums/viewtopic.php?f=3&t=7965
 
353
-- 
 
354
-- Arguments:
 
355
--              none
 
356
--
 
357
-- Returns:
 
358
--              nothing
 
359
 
 
360
if debugger then
 
361
        debugger.reload = function()
 
362
                if DEBUG then
 
363
                        love.audio.stop()
 
364
 
 
365
                        -- create local references to needed variables
 
366
                        -- because we're about to blow the global scope away
 
367
 
 
368
                        local initialGlobals = debugger._initialGlobals
 
369
                        local initialPackages = debugger._initialPackages
 
370
                        
 
371
                        -- reset global scope
 
372
 
 
373
                        for key, _ in pairs(_G) do
 
374
                                _G[key] = initialGlobals[key]
 
375
                        end
 
376
 
 
377
                        -- reload main file and restart
 
378
 
 
379
                        for key, _ in pairs(package.loaded) do
 
380
                                if not initialPackages[key] then
 
381
                                        package.loaded[key] = nil
 
382
                                end
 
383
                        end
 
384
 
 
385
                        require('main')
 
386
                        love.load()
 
387
                end
 
388
        end
 
389
end