/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: 2014-07-06 22:51:43 UTC
  • Revision ID: josh@9ix.org-20140706225143-q72jn0va7v4ssvyy
ignore lovebird, inspect

Show diffs side-by-side

added added

removed removed

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