/ld28

To get this branch, use:
bzr branch http://9ix.org/bzr/ld28
1 by Josh C
zoetrope 1.4
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
}