/zoeplat

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

« back to all changes in this revision

Viewing changes to zoetrope/utils/debug.lua

  • Committer: Josh C
  • Date: 2013-03-02 20:40:57 UTC
  • Revision ID: josh@9ix.org-20130302204057-yrra0a51zgtpq2v2
zoetrope 1.3.1

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