/ld26-basecode

To get this branch, use:
bzr branch http://9ix.org/bzr/ld26-basecode

« back to all changes in this revision

Viewing changes to zoetrope/core/cached.lua

  • Committer: Josh C
  • Date: 2013-04-23 15:22:15 UTC
  • Revision ID: josh@9ix.org-20130423152215-hacpxl91eiiq2ras
zoetrope 1.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
-- Class: Cached
 
2
-- This helps you re-use assets in your app instead of creating extraneous
 
3
-- copies of them. It also hides Love-related calls so that your code is
 
4
-- more portable.
 
5
--
 
6
-- If you're using a class built into Zoetrope, you do not need to use
 
7
-- this class directly. They take care of setting things up for you
 
8
-- appropriately. However, if you're rolling your own, you'll want to use
 
9
-- this to save memory.
 
10
--
 
11
-- This class is not meant to be created directly. Instead, call
 
12
-- methods on Cached directly, e.g. Cached:sound(), Cached:image(), and so on.
 
13
--
 
14
-- Extends:
 
15
--              <Class>
 
16
 
 
17
Cached = Class:extend
 
18
{
 
19
        -- Property: defaultGlyphs
 
20
        -- The default character order of a bitmap font, if none is specified
 
21
        -- in a <font> call.
 
22
        defaultGlyphs = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`' ..
 
23
                                        'abcdefghijklmnopqrstuvwxyz{|}~',
 
24
 
 
25
        -- private property: library
 
26
        -- a table to store already-instantiated assets
 
27
        _library = { image = {}, text = {}, sound = {}, font = {}, binds = {}, },
 
28
 
 
29
        -- Method: image
 
30
        -- Returns a cached image asset.
 
31
        --
 
32
        -- Arguments:
 
33
        --              path - pathname to image file
 
34
        --
 
35
        -- Returns:
 
36
        --              Love image object
 
37
 
 
38
        image = function (self, path)
 
39
                if STRICT then
 
40
                        assert(type(path) == 'string', 'path must be a string')
 
41
                end
 
42
 
 
43
                local realPath = self:_absolutePath(path)
 
44
 
 
45
                if not self._library.image[realPath] then
 
46
                        self._library.image[realPath] = love.graphics.newImage(realPath)
 
47
                end
 
48
 
 
49
                return self._library.image[realPath]
 
50
        end,
 
51
 
 
52
        -- Method: text
 
53
        -- Returns a cached text asset.
 
54
        --
 
55
        -- Arguments:
 
56
        --              path - pathname to text file
 
57
        --
 
58
        -- Returns:
 
59
        --              string
 
60
 
 
61
        text = function (self, path)
 
62
                if STRICT then
 
63
                        assert(type(path) == 'string', 'path must be a string')
 
64
                end
 
65
 
 
66
                local realPath = self:_absolutePath(path)
 
67
 
 
68
                if not self._library.text[realPath] then
 
69
                        self._library.text[realPath] = love.filesystem.read(realPath)
 
70
                end
 
71
 
 
72
                return self._library.text[realPath]
 
73
        end,
 
74
 
 
75
        -- Method: sound
 
76
        -- Returns a cached sound asset.
 
77
        --
 
78
        -- Arguments:
 
79
        --              path - pathname to sound file
 
80
        --              length - either 'short' or 'long'. *It's very important to pass
 
81
        --                               the correct option here.* A short sound is loaded entirely
 
82
        --                               into memory, while a long one is streamed from disk. If you
 
83
        --                               mismatch, you'll either hear a delay in the sound (short sounds
 
84
        --                               played from disk) or your app will freeze (long sounds played from
 
85
        --                               memory).
 
86
        -- 
 
87
        -- Returns:
 
88
        --              Either a Love SoundData object (for short sounds) or a
 
89
        --              Love Decoder object (for long sounds). Either can be used to
 
90
        --              create a Love Source object.
 
91
        --
 
92
        -- See Also:
 
93
        --              <playSound>, <sound>
 
94
 
 
95
        sound = function (self, path, length)
 
96
                if STRICT then
 
97
                        assert(type(path) == 'string', 'path must be a string')
 
98
                end
 
99
 
 
100
                local realPath = self:_absolutePath(path)
 
101
 
 
102
                if not self._library.sound[realPath] then
 
103
                        if length == 'short' then
 
104
                                self._library.sound[realPath] = love.sound.newSoundData(realPath)
 
105
                        elseif length == 'long' then
 
106
                                self._library.sound[realPath] = love.sound.newDecoder(realPath)
 
107
                        else
 
108
                                error('length must be either "short" or "long"')
 
109
                        end
 
110
                end
 
111
 
 
112
                return self._library.sound[path]
 
113
        end,
 
114
 
 
115
        -- Method: font
 
116
        -- Returns a cached font asset.
 
117
        --
 
118
        -- Arguments:
 
119
        -- Can be:
 
120
        --              * A single number. This uses Love's default outline font at that point size.
 
121
        --              * A single string. This uses a bitmap font given by this pathname, and assumes that
 
122
        --                the characters come in
 
123
        --                <printable ASCII order at https://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters>.
 
124
        --              * A string, then a number. This uses an outline font whose pathname is the first argument,
 
125
        --                at the point size given in the second argument.
 
126
        --              * Two strings. The first is treated as a pathname to a bitmap font, the second
 
127
        --                as the character order in the font.
 
128
        --
 
129
        -- Returns:
 
130
        --              Love font object
 
131
 
 
132
        font = function (self, ...)
 
133
                local arg = {...}
 
134
                local libKey = arg[1]
 
135
 
 
136
                if type(libKey) == 'string' then
 
137
                        libKey = self:_absolutePath(libKey)
 
138
                end
 
139
 
 
140
                if #arg > 1 then libKey = libKey .. arg[2] end
 
141
 
 
142
                if not self._library.font[libKey] then
 
143
                        local font, image
 
144
 
 
145
                        if #arg == 1 then
 
146
                                if type(arg[1]) == 'number' then
 
147
                                        font = love.graphics.newFont(arg[1])
 
148
                                elseif type(arg[1]) == 'string' then
 
149
                                        image = Cached:image(arg[1])
 
150
                                        font = love.graphics.newImageFont(image, self.defaultGlyphs)
 
151
                                else
 
152
                                        error("don't understand single argument: " .. arg[1])
 
153
                                end
 
154
                        elseif #arg == 2 then
 
155
                                if type(arg[2]) == 'number' then
 
156
                                        font = love.graphics.newFont(arg[1], arg[2])
 
157
                                elseif type(arg[2]) == 'string' then
 
158
                                        image = Cached:image(arg[1])
 
159
                                        font = love.graphics.newImageFont(image, arg[2])
 
160
                                else
 
161
                                        error("don't understand arguments: " .. arg[1] .. ", " .. arg[2])
 
162
                                end
 
163
                        else
 
164
                                error("too many arguments; should be at most two")
 
165
                        end
 
166
 
 
167
                        self._library.font[libKey] = font
 
168
                end
 
169
 
 
170
                return self._library.font[libKey]
 
171
        end,
 
172
 
 
173
        -- Function: bind
 
174
        -- Returns a function that's bound to an object so it can be later called with
 
175
        -- the correct context. This can be abbreviated as just bind().
 
176
        --
 
177
        -- Arguments:
 
178
        --              obj - object to use as function owner
 
179
        --              func - either a string name of a property of obj, or a free-standing
 
180
        --                         function.
 
181
        --              ... - any number of extra arguments 
 
182
 
 
183
        bind = function (self, obj, func, ...)
 
184
                local arg = {...}
 
185
 
 
186
                if STRICT and type(func) == 'string' then
 
187
                        assert(type(obj[func]) == 'function', 'asked to bind an object to a non-existent method named ' .. func)
 
188
                end
 
189
 
 
190
                -- look for previous bind
 
191
                
 
192
                for key, value in pairs(self._library.binds) do
 
193
                        if key[1] == func and key[2] == obj then
 
194
                                local match = true
 
195
 
 
196
                                for i = 1, #arg do
 
197
                                        if key[i + 2] ~= arg[i] then
 
198
                                                match = false
 
199
                                                break
 
200
                                        end
 
201
                                end
 
202
 
 
203
                                if match then
 
204
                                        return value
 
205
                                end
 
206
                        end
 
207
                end
 
208
 
 
209
                -- have to create a new one
 
210
                -- note that we have to create a compound key, hence the loop above
 
211
 
 
212
                local result = function()
 
213
                        if type(func) == 'string' then
 
214
                                return obj[func](obj, unpack(arg))
 
215
                        else
 
216
                                return func(obj, unpack(arg))
 
217
                        end
 
218
                end
 
219
        
 
220
                self._library.binds[{func, obj, arg}] = result
 
221
                return result
 
222
        end,
 
223
 
 
224
        -- internal function: _absolutePath
 
225
        -- Replaces any .. references in a path, where possible. 
 
226
        --
 
227
        -- Arguments:
 
228
        --              rawPath - string path to expand
 
229
        --
 
230
        -- Returns:
 
231
        --              string
 
232
 
 
233
        _absolutePath = function (self, rawPath)
 
234
                local matches
 
235
                local result = rawPath
 
236
 
 
237
                repeat
 
238
                        result, matches = string.gsub(result, '[^/]+/%.%./', '')
 
239
                until matches == 0
 
240
 
 
241
                return result
 
242
        end
 
243
}