1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 2020 by James R.
4 // Copyright (C) 2020 by Sonic Team Junior.
5 //
6 // This program is free software distributed under the
7 // terms of the GNU General Public License, version 2.
8 // See the 'LICENSE' file for more details.
9 //-----------------------------------------------------------------------------
10 /// \file  lua_taglib.c
11 /// \brief tag list iterator for Lua scripting
12 
13 #include "doomdef.h"
14 #include "taglist.h"
15 #include "r_state.h"
16 
17 #include "lua_script.h"
18 #include "lua_libs.h"
19 
20 #ifdef MUTABLE_TAGS
21 #include "z_zone.h"
22 #endif
23 
tag_iterator(lua_State * L)24 static int tag_iterator(lua_State *L)
25 {
26 	INT32 tag = lua_isnil(L, 2) ? -1 : lua_tonumber(L, 2);
27 	do
28 	{
29 		if (++tag >= MAXTAGS)
30 			return 0;
31 	}
32 	while (! in_bit_array(tags_available, tag)) ;
33 	lua_pushnumber(L, tag);
34 	return 1;
35 }
36 
37 enum {
38 #define UPVALUE lua_upvalueindex
39 	up_garray         = UPVALUE(1),
40 	up_max_elements   = UPVALUE(2),
41 	up_element_array  = UPVALUE(3),
42 	up_sizeof_element = UPVALUE(4),
43 	up_meta           = UPVALUE(5),
44 #undef UPVALUE
45 };
46 
next_element(lua_State * L,const mtag_t tag,const size_t p)47 static INT32 next_element(lua_State *L, const mtag_t tag, const size_t p)
48 {
49 	taggroup_t ** garray = lua_touserdata(L, up_garray);
50 	const size_t * max_elements = lua_touserdata(L, up_max_elements);
51 	return Taggroup_Iterate(garray, *max_elements, tag, p);
52 }
53 
push_element(lua_State * L,void * element)54 static void push_element(lua_State *L, void *element)
55 {
56 	if (LUA_RawPushUserdata(L, element) == LPUSHED_NEW)
57 	{
58 		lua_pushvalue(L, up_meta);
59 		lua_setmetatable(L, -2);
60 	}
61 }
62 
push_next_element(lua_State * L,const INT32 element)63 static void push_next_element(lua_State *L, const INT32 element)
64 {
65 	char * element_array = *(char **)lua_touserdata(L, up_element_array);
66 	const size_t sizeof_element = lua_tonumber(L, up_sizeof_element);
67 	push_element(L, &element_array[element * sizeof_element]);
68 }
69 
70 struct element_iterator_state {
71 	mtag_t tag;
72 	size_t p;
73 };
74 
element_iterator(lua_State * L)75 static int element_iterator(lua_State *L)
76 {
77 	struct element_iterator_state * state = lua_touserdata(L, 1);
78 	if (lua_isnoneornil(L, 3))
79 		state->p = 0;
80 	lua_pushnumber(L, ++state->p);
81 	lua_gettable(L, 1);
82 	return 1;
83 }
84 
lib_iterateTags(lua_State * L)85 static int lib_iterateTags(lua_State *L)
86 {
87 	if (lua_gettop(L) < 2)
88 	{
89 		lua_pushcfunction(L, tag_iterator);
90 		return 1;
91 	}
92 	else
93 		return tag_iterator(L);
94 }
95 
lib_numTags(lua_State * L)96 static int lib_numTags(lua_State *L)
97 {
98 	lua_pushnumber(L, num_tags);
99 	return 1;
100 }
101 
lib_getTaggroup(lua_State * L)102 static int lib_getTaggroup(lua_State *L)
103 {
104 	struct element_iterator_state *state;
105 
106 	mtag_t tag;
107 
108 	if (lua_gettop(L) > 1)
109 		return luaL_error(L, "too many arguments");
110 
111 	if (lua_isnoneornil(L, 1))
112 	{
113 		tag = MTAG_GLOBAL;
114 	}
115 	else
116 	{
117 		tag = lua_tonumber(L, 1);
118 		luaL_argcheck(L, tag >= -1, 1, "tag out of range");
119 	}
120 
121 	state = lua_newuserdata(L, sizeof *state);
122 	state->tag = tag;
123 	state->p = 0;
124 
125 	lua_pushvalue(L, lua_upvalueindex(1));
126 	lua_setmetatable(L, -2);
127 
128 	return 1;
129 }
130 
lib_getTaggroupElement(lua_State * L)131 static int lib_getTaggroupElement(lua_State *L)
132 {
133 	const size_t p = luaL_checknumber(L, 2) - 1;
134 	const mtag_t tag = *(mtag_t *)lua_touserdata(L, 1);
135 	const INT32 element = next_element(L, tag, p);
136 
137 	if (element == -1)
138 		return 0;
139 	else
140 	{
141 		push_next_element(L, element);
142 		return 1;
143 	}
144 }
145 
lib_numTaggroupElements(lua_State * L)146 static int lib_numTaggroupElements(lua_State *L)
147 {
148 	const mtag_t tag = *(mtag_t *)lua_touserdata(L, 1);
149 	if (tag == MTAG_GLOBAL)
150 		lua_pushnumber(L, *(size_t *)lua_touserdata(L, up_max_elements));
151 	else
152 	{
153 		const taggroup_t ** garray = lua_touserdata(L, up_garray);
154 		lua_pushnumber(L, Taggroup_Count(garray[tag]));
155 	}
156 	return 1;
157 }
158 
159 #ifdef MUTABLE_TAGS
160 static int meta_ref[2];
161 #endif
162 
has_valid_field(lua_State * L)163 static int has_valid_field(lua_State *L)
164 {
165 	int equal;
166 	lua_rawgeti(L, LUA_ENVIRONINDEX, 1);
167 	equal = lua_rawequal(L, 2, -1);
168 	lua_pop(L, 1);
169 	return equal;
170 }
171 
valid_taglist(lua_State * L,int idx,boolean getting)172 static taglist_t * valid_taglist(lua_State *L, int idx, boolean getting)
173 {
174 	taglist_t *list = *(taglist_t **)lua_touserdata(L, idx);
175 
176 	if (list == NULL)
177 	{
178 		if (getting && has_valid_field(L))
179 			lua_pushboolean(L, 0);
180 		else
181 			LUA_ErrInvalid(L, "taglist");/* doesn't actually return */
182 		return NULL;
183 	}
184 	else
185 		return list;
186 }
187 
check_taglist(lua_State * L,int idx)188 static taglist_t * check_taglist(lua_State *L, int idx)
189 {
190 	if (lua_isuserdata(L, idx) && lua_getmetatable(L, idx))
191 	{
192 		lua_getref(L, meta_ref[0]);
193 		lua_getref(L, meta_ref[1]);
194 
195 		if (lua_rawequal(L, -3, -2) || lua_rawequal(L, -3, -1))
196 		{
197 			lua_pop(L, 3);
198 			return valid_taglist(L, idx, false);
199 		}
200 	}
201 
202 	return luaL_argerror(L, idx, "must be a tag list"), NULL;
203 }
204 
taglist_get(lua_State * L)205 static int taglist_get(lua_State *L)
206 {
207 	const taglist_t *list = valid_taglist(L, 1, true);
208 
209 	if (list == NULL)/* valid check */
210 		return 1;
211 
212 	if (lua_isnumber(L, 2))
213 	{
214 		const size_t i = lua_tonumber(L, 2);
215 
216 		if (list && i <= list->count)
217 		{
218 			lua_pushnumber(L, list->tags[i - 1]);
219 			return 1;
220 		}
221 		else
222 			return 0;
223 	}
224 	else if (has_valid_field(L))
225 	{
226 		lua_pushboolean(L, 1);
227 		return 1;
228 	}
229 	else
230 	{
231 		lua_getmetatable(L, 1);
232 		lua_replace(L, 1);
233 		lua_rawget(L, 1);
234 		return 1;
235 	}
236 }
237 
taglist_len(lua_State * L)238 static int taglist_len(lua_State *L)
239 {
240 	const taglist_t *list = valid_taglist(L, 1, false);
241 	lua_pushnumber(L, list->count);
242 	return 1;
243 }
244 
taglist_equal(lua_State * L)245 static int taglist_equal(lua_State *L)
246 {
247 	const taglist_t *lhs = check_taglist(L, 1);
248 	const taglist_t *rhs = check_taglist(L, 2);
249 	lua_pushboolean(L, Tag_Compare(lhs, rhs));
250 	return 1;
251 }
252 
taglist_iterator(lua_State * L)253 static int taglist_iterator(lua_State *L)
254 {
255 	const taglist_t *list = valid_taglist(L, 1, false);
256 	const size_t i = 1 + lua_tonumber(L, lua_upvalueindex(1));
257 	if (i <= list->count)
258 	{
259 		lua_pushnumber(L, list->tags[i - 1]);
260 		/* watch me exploit an upvalue as a control because
261 			I want to use the control as the value */
262 		lua_pushnumber(L, i);
263 		lua_replace(L, lua_upvalueindex(1));
264 		return 1;
265 	}
266 	else
267 		return 0;
268 }
269 
taglist_iterate(lua_State * L)270 static int taglist_iterate(lua_State *L)
271 {
272 	check_taglist(L, 1);
273 	lua_pushnumber(L, 0);
274 	lua_pushcclosure(L, taglist_iterator, 1);
275 	lua_pushvalue(L, 1);
276 	return 2;
277 }
278 
taglist_find(lua_State * L)279 static int taglist_find(lua_State *L)
280 {
281 	const taglist_t *list = check_taglist(L, 1);
282 	const mtag_t tag = luaL_checknumber(L, 2);
283 	lua_pushboolean(L, Tag_Find(list, tag));
284 	return 1;
285 }
286 
taglist_shares(lua_State * L)287 static int taglist_shares(lua_State *L)
288 {
289 	const taglist_t *lhs = check_taglist(L, 1);
290 	const taglist_t *rhs = check_taglist(L, 2);
291 	lua_pushboolean(L, Tag_Share(lhs, rhs));
292 	return 1;
293 }
294 
295 /* only sector tags are mutable... */
296 
297 #ifdef MUTABLE_TAGS
sector_of_taglist(taglist_t * list)298 static size_t sector_of_taglist(taglist_t *list)
299 {
300 	return (sector_t *)((char *)list - offsetof (sector_t, tags)) - sectors;
301 }
302 
this_taglist(lua_State * L)303 static int this_taglist(lua_State *L)
304 {
305 	lua_settop(L, 1);
306 	return 1;
307 }
308 
taglist_add(lua_State * L)309 static int taglist_add(lua_State *L)
310 {
311 	taglist_t *list = *(taglist_t **)luaL_checkudata(L, 1, META_SECTORTAGLIST);
312 	const mtag_t tag = luaL_checknumber(L, 2);
313 
314 	if (! Tag_Find(list, tag))
315 	{
316 		Taggroup_Add(tags_sectors, tag, sector_of_taglist(list));
317 		Tag_Add(list, tag);
318 	}
319 
320 	return this_taglist(L);
321 }
322 
taglist_remove(lua_State * L)323 static int taglist_remove(lua_State *L)
324 {
325 	taglist_t *list = *(taglist_t **)luaL_checkudata(L, 1, META_SECTORTAGLIST);
326 	const mtag_t tag = luaL_checknumber(L, 2);
327 
328 	size_t i;
329 
330 	for (i = 0; i < list->count; ++i)
331 	{
332 		if (list->tags[i] == tag)
333 		{
334 			if (list->count > 1)
335 			{
336 				memmove(&list->tags[i], &list->tags[i + 1],
337 						(list->count - 1 - i) * sizeof (mtag_t));
338 				list->tags = Z_Realloc(list->tags,
339 						(--list->count) * sizeof (mtag_t), PU_LEVEL, NULL);
340 				Taggroup_Remove(tags_sectors, tag, sector_of_taglist(list));
341 			}
342 			else/* reset to default tag */
343 				Tag_SectorFSet(sector_of_taglist(list), 0);
344 			break;
345 		}
346 	}
347 
348 	return this_taglist(L);
349 }
350 #endif/*MUTABLE_TAGS*/
351 
LUA_InsertTaggroupIterator(lua_State * L,taggroup_t * garray[],size_t * max_elements,void * element_array,size_t sizeof_element,const char * meta)352 void LUA_InsertTaggroupIterator
353 (		lua_State *L,
354 		taggroup_t *garray[],
355 		size_t * max_elements,
356 		void * element_array,
357 		size_t sizeof_element,
358 		const char * meta)
359 {
360 	lua_createtable(L, 0, 3);
361 		lua_pushlightuserdata(L, garray);
362 		lua_pushlightuserdata(L, max_elements);
363 
364 		lua_pushvalue(L, -2);
365 		lua_pushvalue(L, -2);
366 		lua_pushlightuserdata(L, element_array);
367 		lua_pushnumber(L, sizeof_element);
368 		luaL_getmetatable(L, meta);
369 		lua_pushcclosure(L, lib_getTaggroupElement, 5);
370 		lua_setfield(L, -4, "__index");
371 
372 		lua_pushcclosure(L, lib_numTaggroupElements, 2);
373 		lua_setfield(L, -2, "__len");
374 
375 		lua_pushcfunction(L, element_iterator);
376 		lua_setfield(L, -2, "__call");
377 	lua_pushcclosure(L, lib_getTaggroup, 1);
378 	lua_setfield(L, -2, "tagged");
379 }
380 
381 static luaL_Reg taglist_lib[] = {
382 	{"iterate", taglist_iterate},
383 	{"find", taglist_find},
384 	{"shares", taglist_shares},
385 #ifdef MUTABLE_TAGS
386 	{"add", taglist_add},
387 	{"remove", taglist_remove},
388 #endif
389 	{0}
390 };
391 
open_taglist(lua_State * L)392 static void open_taglist(lua_State *L)
393 {
394 	luaL_register(L, "taglist", taglist_lib);
395 
396 	lua_getfield(L, -1, "find");
397 	lua_setfield(L, -2, "has");
398 }
399 
400 #define new_literal(L, s) \
401 	(lua_pushliteral(L, s), luaL_ref(L, -2))
402 
403 #ifdef MUTABLE_TAGS
404 static int
405 #else
406 static void
407 #endif
set_taglist_metatable(lua_State * L,const char * meta)408 set_taglist_metatable(lua_State *L, const char *meta)
409 {
410 	luaL_newmetatable(L, meta);
411 		lua_pushcfunction(L, taglist_get);
412 		lua_createtable(L, 0, 1);
413 			new_literal(L, "valid");
414 		lua_setfenv(L, -2);
415 		lua_setfield(L, -2, "__index");
416 
417 		lua_pushcfunction(L, taglist_len);
418 		lua_setfield(L, -2, "__len");
419 
420 		lua_pushcfunction(L, taglist_equal);
421 		lua_setfield(L, -2, "__eq");
422 #ifdef MUTABLE_TAGS
423 	return luaL_ref(L, LUA_REGISTRYINDEX);
424 #endif
425 }
426 
LUA_TagLib(lua_State * L)427 int LUA_TagLib(lua_State *L)
428 {
429 	lua_newuserdata(L, 0);
430 		lua_createtable(L, 0, 2);
431 			lua_createtable(L, 0, 1);
432 				lua_pushcfunction(L, lib_iterateTags);
433 				lua_setfield(L, -2, "iterate");
434 			lua_setfield(L, -2, "__index");
435 
436 			lua_pushcfunction(L, lib_numTags);
437 			lua_setfield(L, -2, "__len");
438 		lua_setmetatable(L, -2);
439 	lua_setglobal(L, "tags");
440 
441 	open_taglist(L);
442 
443 #ifdef MUTABLE_TAGS
444 	meta_ref[0] = set_taglist_metatable(L, META_TAGLIST);
445 	meta_ref[1] = set_taglist_metatable(L, META_SECTORTAGLIST);
446 #else
447 	set_taglist_metatable(L, META_TAGLIST);
448 #endif
449 
450 	return 0;
451 }
452