diff options
Diffstat (limited to 'src')
32 files changed, 10991 insertions, 0 deletions
diff --git a/src/GuiLua/GuiLua.c b/src/GuiLua/GuiLua.c new file mode 100644 index 0000000..c7a368c --- /dev/null +++ b/src/GuiLua/GuiLua.c | |||
@@ -0,0 +1,456 @@ | |||
1 | /* GuiLua - a GUI library that implements matrix-RAD style stuff. | ||
2 | |||
3 | Provides the skang and widget Lua packages. | ||
4 | |||
5 | In the initial intended use case, several applications will be using | ||
6 | this all at once, with one central app hosting all the GUIs. | ||
7 | |||
8 | Basically this should deal with "windows" and their contents. A | ||
9 | "window" in this case is hosted in the central app as some sort of | ||
10 | internal window, but the user can "tear off" those windows, then they | ||
11 | get their own OS hosted window. This could be done by the hosting app | ||
12 | sending the current window contents to the original app as a skang file. | ||
13 | |||
14 | Between the actual GUI and the app might be a socket, or a stdin/out | ||
15 | pipe. Just like matrix-RAD, this should be transparent to the app. | ||
16 | Also just like matrix-RAD, widgets can be connected to variable / | ||
17 | functions (C or Lua), and any twiddlings with those widgets runs the | ||
18 | function / changes the variable, again transparent to the app, except | ||
19 | for any registered get/set methods. | ||
20 | |||
21 | This interface between the GUI and the app is "skang" files, which are | ||
22 | basically Lua scripts. The GUI and the app can send skang files back | ||
23 | and forth, usually the app sends actual GUI stuff, and usually the GUI | ||
24 | sends variable twiddles or action calls. Usually. | ||
25 | |||
26 | To start with, this will be used to support multiple apps hosting their | ||
27 | windows in extantz, allowing the big viewer blob to be split up into | ||
28 | modules. At some point converting LL XML based UI shit into skang could | ||
29 | be done. Also, this should be an exntension to LuaSL, so in-world | ||
30 | scripts can have a poper GUI for a change. | ||
31 | |||
32 | |||
33 | NOTES and TODOs - | ||
34 | |||
35 | Lua scripts do - | ||
36 | require 'widget' -> loads widget.c | ||
37 | Widget.c is a library like test_c. | ||
38 | It starts up GuiLua.c app, with stdin/stdout pipe. | ||
39 | Widget.c then acts as a proxy for all the widget stuff. | ||
40 | So Lua modules via C libraries can work with Elm code that has a special main and has to be an app. | ||
41 | Seems simplest. | ||
42 | |||
43 | Also - | ||
44 | Some of this gets shared with LuaSL, since it came from there anyway. | ||
45 | |||
46 | Finally - | ||
47 | Add a --gui command line option, that runs foo.skang. | ||
48 | Than change the hash bang to use it. | ||
49 | And if there's a matching module, load the module first, call gimmeSkin() on it. | ||
50 | So that any with an internal default skin get that instead. | ||
51 | Same if there's a module, but no skang file. | ||
52 | |||
53 | Making these packages all a sub package of skang seems like a great | ||
54 | idea. On the other hand, looks like most things are just getting folded | ||
55 | into skang anyway. See | ||
56 | http://www.inf.puc-rio.br/~roberto/pil2/chapter15.pdf part 15.5 for | ||
57 | package details. | ||
58 | |||
59 | See if I can use LuaJIT FFI here. Since this will be a library, and | ||
60 | skang apps could be written in C or Lua, perhaps writing this library to | ||
61 | be FFI friendly instead of the usual Lua C binding might be the way to | ||
62 | go? LuaJIT is not ready yet, since it needs include files copied into | ||
63 | Lua files, and does not support macros, which EFL uses a lot of. | ||
64 | |||
65 | For the "GUI hosted in another app" case, we will need some sort of | ||
66 | internal window manager running in that other app. | ||
67 | |||
68 | This might end up running dozens of Lua scripts, and could use the LuaSL | ||
69 | Lua script running system. Moving that into this library might be a | ||
70 | sane idea I think? Or prehaps a separate library that both LuaSL and | ||
71 | GuiLua use? | ||
72 | |||
73 | Raster wants a method of sending Lua tables around as edje messages. | ||
74 | Between C, Edje, Edje Lua, and Lua. Sending between threads, and across | ||
75 | sockets. Using a new edje message type, or eet for sockets, was | ||
76 | suggested, but perhaps Lua skang is a better choice? | ||
77 | |||
78 | Somehow access to the edje_lua2.c bindings should be provided. And | ||
79 | bindings to the rest of EFL when they are done. Assuming the other EFL | ||
80 | developers do proper introspection stuff, or let me do it. | ||
81 | |||
82 | The generic Lua binding helper functions I wrote for edje_lua2.c could | ||
83 | be used here as well, and expanded as discussed on the E devs mailing | ||
84 | list. This would include the thread safe Lua function stuff copied | ||
85 | into the README. | ||
86 | |||
87 | There will eventually be a built in editor, like the zen editor from | ||
88 | matrix-RAD. It might be a separate app. | ||
89 | |||
90 | NAWS should probably live in here to. If I ever get around to writing | ||
91 | it. lol | ||
92 | |||
93 | The pre tokenized widget structure thingy I had planned in the | ||
94 | matrix-RAD TODO just wont work, as it uses symbols. On the other hand, | ||
95 | we will be using Lua tables anyway. B-) | ||
96 | |||
97 | The last half of http://passingcuriosity.com/2009/extending-lua-in-c/ | ||
98 | might be of use. | ||
99 | |||
100 | */ | ||
101 | |||
102 | |||
103 | /* thing package | ||
104 | |||
105 | Currently this is in skang.lua, but should bring this in here later. | ||
106 | |||
107 | */ | ||
108 | |||
109 | |||
110 | /* skang package | ||
111 | |||
112 | Currently this is in skang.lua, but should bring this in here later. | ||
113 | |||
114 | */ | ||
115 | |||
116 | |||
117 | /* stuff & squeal packages | ||
118 | |||
119 | Currently Stuff is in skang.lua, but should bring this in here later. | ||
120 | |||
121 | */ | ||
122 | |||
123 | |||
124 | /* widget package | ||
125 | |||
126 | Currently widget design is in skang.lua, but should bring this in here later. | ||
127 | |||
128 | */ | ||
129 | |||
130 | |||
131 | /* introspection | ||
132 | |||
133 | As detailed in README, EFL introspection doesn't seem to really be on | ||
134 | the radar, but I might get lucky, or I might have to write it myself. | ||
135 | For quick and dirty early testing, I'll probably write a widget package | ||
136 | that has hard coded mappings between some basic "label", "button", etc. | ||
137 | and ordinary elementary widgets. Proper introspection can come later. | ||
138 | |||
139 | */ | ||
140 | |||
141 | |||
142 | |||
143 | #include "GuiLua.h" | ||
144 | |||
145 | |||
146 | globals ourGlobals; | ||
147 | static const char *globName = "ourGlobals"; | ||
148 | |||
149 | |||
150 | // TODO - These functions should be able to deal with multiple windows. | ||
151 | // TODO - Should be able to open external and internal windows, and even switch between them on the fly. | ||
152 | static void _on_done(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) | ||
153 | { | ||
154 | // globals *ourGlobals = data; | ||
155 | |||
156 | // Tell the main loop to stop, which it will, eventually. | ||
157 | elm_exit(); | ||
158 | } | ||
159 | |||
160 | |||
161 | /* Sooo, how to do this - | ||
162 | widget has to be a light userdata | ||
163 | The rest can be Lua sub things? Each with a C function to update the widget. | ||
164 | |||
165 | win.quitter:colour(1,2,3,4) -> win.quitter.colour(win.quitter, 1,2,3,4) -> __call(win.quitter.colour, win.quitter, 1,2,3,4) -> skang.colour(win.quitter.colour, win.quitter, 1,2,3,4) | ||
166 | win.quitter.colour.r = 5 -> direct access to the table, well "direct" via Thing and Mum. We eventually want to call skang.colour() though. | ||
167 | */ | ||
168 | |||
169 | struct _Widget | ||
170 | { | ||
171 | char magic[8]; | ||
172 | Evas_Object *obj; | ||
173 | char *label, *look, *action, *help; | ||
174 | // foreground / background colour | ||
175 | // thing | ||
176 | // types {} | ||
177 | // skangCoord x, y, w, h | ||
178 | }; | ||
179 | |||
180 | static void _on_click(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) | ||
181 | { | ||
182 | globals *ourGlobals; | ||
183 | lua_State *L = data; | ||
184 | struct _Widget *wid; | ||
185 | |||
186 | lua_getfield(L, LUA_REGISTRYINDEX, globName); | ||
187 | ourGlobals = lua_touserdata(L, -1); | ||
188 | lua_pop(L, 1); | ||
189 | |||
190 | wid = evas_object_data_get(obj, "Widget"); | ||
191 | if (wid) | ||
192 | { | ||
193 | PD("Doing action %s", wid->action); | ||
194 | if (0 != luaL_dostring(L, wid->action)) | ||
195 | PE("Error running - %s", wid->action); | ||
196 | } | ||
197 | } | ||
198 | |||
199 | static int widget(lua_State *L) | ||
200 | { | ||
201 | globals *ourGlobals; | ||
202 | char *type = "label"; | ||
203 | char *title = ":"; | ||
204 | int x = 1, y = 1, w = WIDTH/3, h = HEIGHT/3; | ||
205 | |||
206 | lua_getfield(L, LUA_REGISTRYINDEX, globName); | ||
207 | ourGlobals = lua_touserdata(L, -1); | ||
208 | lua_pop(L, 1); | ||
209 | |||
210 | pull_lua(L, 1, "$type $title %x %y %w %h", &type, &title, &x, &y, &w, &h); | ||
211 | |||
212 | // Poor mans introspection, until I write real introspection into EFL. | ||
213 | if (strcmp(type, "button") == 0) | ||
214 | { | ||
215 | struct _Widget *wid; | ||
216 | |||
217 | wid = calloc(1, sizeof(struct _Widget)); | ||
218 | strcpy(wid->magic, "Widget"); | ||
219 | wid->label = strdup(title); | ||
220 | wid->obj = elm_button_add(ourGlobals->win); | ||
221 | elm_object_text_set(wid->obj, title); | ||
222 | evas_object_smart_callback_add(wid->obj, "clicked", _on_click, L); | ||
223 | evas_object_resize(wid->obj, w, h); | ||
224 | evas_object_move(wid->obj, x, y); | ||
225 | evas_object_show(wid->obj); | ||
226 | evas_object_data_set(wid->obj, "Widget", wid); | ||
227 | /* Evas_Object *bt isn't a real pointer it seems. At least Lua bitches about it - | ||
228 | PANIC: unprotected error in call to Lua API (bad light userdata pointer) | ||
229 | So we wrap it. | ||
230 | */ | ||
231 | lua_pushlightuserdata(L, (void *) wid); | ||
232 | return 1; | ||
233 | } | ||
234 | |||
235 | return 0; | ||
236 | } | ||
237 | |||
238 | static int action(lua_State *L) | ||
239 | { | ||
240 | globals *ourGlobals; | ||
241 | struct _Widget *wid = lua_touserdata(L, 1); | ||
242 | char *action = "nada"; | ||
243 | |||
244 | lua_getfield(L, LUA_REGISTRYINDEX, globName); | ||
245 | ourGlobals = lua_touserdata(L, -1); | ||
246 | lua_pop(L, 1); | ||
247 | |||
248 | pull_lua(L, 2, "$", &action); | ||
249 | if (wid && strcmp(wid->magic, "Widget") == 0) | ||
250 | { | ||
251 | PD("Setting action %s", action); | ||
252 | wid->action = strdup(action); | ||
253 | } | ||
254 | return 0; | ||
255 | } | ||
256 | |||
257 | static int colour(lua_State *L) | ||
258 | { | ||
259 | // TODO - This is just a stub for now. | ||
260 | |||
261 | return 0; | ||
262 | } | ||
263 | |||
264 | static int window(lua_State *L) | ||
265 | { | ||
266 | globals *ourGlobals; | ||
267 | char *name = "GuiLua"; | ||
268 | char *title = "GuiLua test harness"; | ||
269 | int w = WIDTH, h = HEIGHT; | ||
270 | |||
271 | lua_getfield(L, LUA_REGISTRYINDEX, globName); | ||
272 | ourGlobals = lua_touserdata(L, -1); | ||
273 | lua_pop(L, 1); | ||
274 | |||
275 | pull_lua(L, 1, "%w %h $title $name", &w, &h, &title, &name); | ||
276 | PI("Setting window to %d %d %s", w, h, title); | ||
277 | |||
278 | if ((ourGlobals->win = elm_win_util_standard_add(name, title))) | ||
279 | { | ||
280 | evas_object_smart_callback_add(ourGlobals->win, "delete,request", _on_done, ourGlobals); | ||
281 | evas_object_resize(ourGlobals->win, w, h); | ||
282 | evas_object_move(ourGlobals->win, 0, 0); | ||
283 | evas_object_show(ourGlobals->win); | ||
284 | |||
285 | lua_pushlightuserdata(L, &ourGlobals->win); | ||
286 | return 1; | ||
287 | } | ||
288 | |||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | static int clear(lua_State *L) | ||
293 | { | ||
294 | // TODO - This is just a stub for now. | ||
295 | |||
296 | return 0; | ||
297 | } | ||
298 | |||
299 | static int loopWindow(lua_State *L) | ||
300 | { | ||
301 | globals *ourGlobals; | ||
302 | |||
303 | lua_getfield(L, LUA_REGISTRYINDEX, globName); | ||
304 | ourGlobals = lua_touserdata(L, -1); | ||
305 | lua_pop(L, 1); | ||
306 | |||
307 | if (ourGlobals->win) | ||
308 | elm_run(); | ||
309 | |||
310 | return 0; | ||
311 | } | ||
312 | |||
313 | static int quit(lua_State *L) | ||
314 | { | ||
315 | globals *ourGlobals; | ||
316 | |||
317 | lua_getfield(L, LUA_REGISTRYINDEX, globName); | ||
318 | ourGlobals = lua_touserdata(L, -1); | ||
319 | lua_pop(L, 1); | ||
320 | |||
321 | _on_done(ourGlobals, NULL, NULL); | ||
322 | |||
323 | return 0; | ||
324 | } | ||
325 | |||
326 | static int closeWindow(lua_State *L) | ||
327 | { | ||
328 | globals *ourGlobals; | ||
329 | |||
330 | lua_getfield(L, LUA_REGISTRYINDEX, globName); | ||
331 | ourGlobals = lua_touserdata(L, -1); | ||
332 | lua_pop(L, 1); | ||
333 | |||
334 | // Elm will delete our buttons to, and EO will bitch four times for each. | ||
335 | if (ourGlobals->win) | ||
336 | evas_object_del(ourGlobals->win); | ||
337 | |||
338 | if (ourGlobals->logDom >= 0) | ||
339 | { | ||
340 | eina_log_domain_unregister(ourGlobals->logDom); | ||
341 | ourGlobals->logDom = -1; | ||
342 | } | ||
343 | |||
344 | // This shuts down Elementary, but keeps the main loop running until all ecore_evas are freed. | ||
345 | elm_shutdown(); | ||
346 | |||
347 | return 0; | ||
348 | } | ||
349 | |||
350 | /* local widget = require 'libGuiLua' | ||
351 | |||
352 | Lua's require() function will strip any stuff from the front of the name | ||
353 | separated by a hyphen, so 'ClientHamr-GuiLua-libGuiLua' -> 'libGuiLua'. Then | ||
354 | it will search through a path, and eventually find this libGuiLua.so (or | ||
355 | libGuiLua.dll or whatever), then call luaopen_libGuiLua(), which should return | ||
356 | a table. The argument (only thing on the stack) for this function will | ||
357 | be 'libGuiLua'. | ||
358 | |||
359 | Normally luaL_register() creates a table of functions, that is the table | ||
360 | returned, but we want to do something different with skang. | ||
361 | */ | ||
362 | int luaopen_GuiLua(lua_State *L) | ||
363 | { | ||
364 | int skang; | ||
365 | |||
366 | // In theory this function only ever gets called once. | ||
367 | memset(&ourGlobals, 0, sizeof(globals)); | ||
368 | ourGlobals.logDom = loggingStartup("GuiLua", ourGlobals.logDom); | ||
369 | |||
370 | elm_policy_set(ELM_POLICY_EXIT, ELM_POLICY_EXIT_NONE); | ||
371 | elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_NONE); | ||
372 | elm_policy_set(ELM_POLICY_THROTTLE, ELM_POLICY_THROTTLE_HIDDEN_ALWAYS); | ||
373 | |||
374 | // These are set via the elementary_config tool, which is hard to find. | ||
375 | elm_config_finger_size_set(0); | ||
376 | elm_config_scale_set(1.0); | ||
377 | |||
378 | // pseudo-indices, special tables that can be accessed like the stack - | ||
379 | // LUA_GLOBALSINDEX - thread environment, where globals are | ||
380 | // LUA_ENVIRONINDEX - C function environment, in this case luaopen_widget() is the C function | ||
381 | // LUA_REGISTRYINDEX - C registry, global, for unique keys use the module name as a string, or a lightuserdata address to a C object in our module. | ||
382 | // lua_upvalueindex(n) - C function upvalues | ||
383 | |||
384 | // Shove ourGlobals into the registry. | ||
385 | lua_pushlightuserdata(L, &ourGlobals); | ||
386 | lua_setfield(L, LUA_REGISTRYINDEX, globName); | ||
387 | |||
388 | // The skang module should have been loaded by now, so we can just grab it out of package.loaded[]. | ||
389 | lua_getglobal(L, "package"); | ||
390 | lua_getfield(L, lua_gettop(L), "loaded"); | ||
391 | lua_remove(L, -2); // Removes "package" | ||
392 | lua_getfield(L, lua_gettop(L), SKANG); | ||
393 | lua_remove(L, -2); // Removes "loaded" | ||
394 | lua_setfield(L, LUA_REGISTRYINDEX, SKANG); | ||
395 | lua_getfield(L, LUA_REGISTRYINDEX, SKANG); // Puts the skang table back on the stack. | ||
396 | skang = lua_gettop(L); | ||
397 | |||
398 | // Define our functions. | ||
399 | //thingasm{'window', 'The size and title of the application Frame.', window, 'x,y,name', acl='GGG'} | ||
400 | push_lua(L, "@ ( { = $ $ & $ $acl } )", skang, THINGASM, skang, "Cwindow", "Opens our window.", window, "number,number,string", "GGG", 0); | ||
401 | push_lua(L, "@ ( = $ $ & )", skang, THINGASM, skang, "clear", "The current skin is cleared of all widgets.", clear, 0); | ||
402 | push_lua(L, "@ ( = $ $ & )", skang, THINGASM, skang, "widget", "Create a widget.", widget, 0); | ||
403 | push_lua(L, "@ ( = $ $ & )", skang, THINGASM, skang, "action", "Add an action to a widget.", action, 0); | ||
404 | push_lua(L, "@ ( = $ $ & )", skang, THINGASM, skang, "Colour", "Change widget colours.", colour, 0); | ||
405 | push_lua(L, "@ ( = $ $ & )", skang, THINGASM, skang, "loopWindow", "Run our windows main loop.", loopWindow, 0); | ||
406 | push_lua(L, "@ ( = $ $ & )", skang, THINGASM, skang, "quit", "Quit, exit, remove thyself.", quit, 0); | ||
407 | push_lua(L, "@ ( = $ $ & )", skang, THINGASM, skang, "closeWindow", "Closes our window.", closeWindow, 0); | ||
408 | |||
409 | // A test of the array building stuff. | ||
410 | push_lua(L, "@ ( { = $ $ % $widget !required } )", skang, THINGASM, skang, "wibble", "It's wibbly!", 1, "'edit', 'The wibblinator:', 1, 1, 10, 50", 1, 0); | ||
411 | |||
412 | // Makes no difference what we return, but it's expecting something. | ||
413 | return 1; | ||
414 | } | ||
415 | |||
416 | |||
417 | void GuiLuaDo(int argc, char **argv) | ||
418 | { | ||
419 | lua_State *L; | ||
420 | lua_Number i; | ||
421 | |||
422 | L = luaL_newstate(); | ||
423 | if (L) | ||
424 | { | ||
425 | luaL_openlibs(L); | ||
426 | |||
427 | // Pass all our command line arguments to Lua. | ||
428 | i = 1; | ||
429 | lua_newtable(L); | ||
430 | while (--argc > 0 && *++argv != '\0') | ||
431 | { | ||
432 | lua_pushnumber(L, i++); | ||
433 | lua_pushstring(L, *argv); | ||
434 | lua_settable(L, -3); | ||
435 | } | ||
436 | lua_setfield(L, LUA_GLOBALSINDEX, "arg"); | ||
437 | |||
438 | |||
439 | // When we do this, skang will process all the arguments passed to GuiLuaDo(). | ||
440 | // This likely includes a module load, which likely opens a window. | ||
441 | lua_getglobal(L, "require"); | ||
442 | lua_pushstring(L, SKANG); | ||
443 | lua_call(L, 1, 1); | ||
444 | lua_setfield(L, LUA_GLOBALSINDEX, SKANG); | ||
445 | |||
446 | |||
447 | // Run the main loop via a Lua call. | ||
448 | // This does nothing if no module opened a window. | ||
449 | if (0 != luaL_dostring(L, "skang.loopWindow()")) | ||
450 | PEm("Error running - skang.loopWindow()"); | ||
451 | lua_pop(L, closeWindow(L)); | ||
452 | lua_close(L); | ||
453 | } | ||
454 | else | ||
455 | fprintf(stderr, "Failed to start Lua!\n"); | ||
456 | } | ||
diff --git a/src/GuiLua/GuiLua.h b/src/GuiLua/GuiLua.h new file mode 100644 index 0000000..156fa1a --- /dev/null +++ b/src/GuiLua/GuiLua.h | |||
@@ -0,0 +1,35 @@ | |||
1 | |||
2 | #include <stdio.h> | ||
3 | #include <ctype.h> | ||
4 | |||
5 | #include <Elementary.h> | ||
6 | |||
7 | #include <lua.h> | ||
8 | #include <luajit.h> | ||
9 | #include <lualib.h> | ||
10 | #include <lauxlib.h> | ||
11 | |||
12 | #include "LumbrJack.h" | ||
13 | #include "Runnr.h" | ||
14 | |||
15 | typedef struct _globals globals; | ||
16 | |||
17 | |||
18 | #define WIDTH (300) | ||
19 | #define HEIGHT (300) | ||
20 | |||
21 | #define SKANG "skang" | ||
22 | #define MODULEBEGIN "moduleBegin" | ||
23 | #define MODULEEND "moduleEnd" | ||
24 | #define THINGASM "thingasm" | ||
25 | |||
26 | |||
27 | struct _globals | ||
28 | { | ||
29 | Evas_Object *win; // Our Elm window. | ||
30 | int logDom; // Our logging domain. | ||
31 | }; | ||
32 | |||
33 | |||
34 | int luaopen_widget(lua_State *L); | ||
35 | void GuiLuaDo(int argc, char **argv); | ||
diff --git a/src/GuiLua/build.lua b/src/GuiLua/build.lua new file mode 100755 index 0000000..7e1da15 --- /dev/null +++ b/src/GuiLua/build.lua | |||
@@ -0,0 +1,23 @@ | |||
1 | #!/usr/bin/env lua | ||
2 | |||
3 | local dir = ... | ||
4 | |||
5 | if 'nil' == type(dir) then | ||
6 | local build, err = loadfile('../../build.lua') | ||
7 | if build then | ||
8 | setfenv(build, getfenv(2)) | ||
9 | build(2) | ||
10 | else | ||
11 | print("ERROR - " .. err) | ||
12 | end | ||
13 | dir = workingDir | ||
14 | end | ||
15 | |||
16 | LDFLAGS = '-L ' .. dir .. ' ' .. LDFLAGS | ||
17 | |||
18 | removeFiles(dir, {'test_c.so', 'GuiLua.o', '../../libraries/libGuiLua.so', 'skang'}) | ||
19 | |||
20 | runCommand('C modules', dir, 'gcc ' .. CFLAGS .. ' -fPIC -shared -o test_c.so test_c.c') | ||
21 | runCommand(nil, dir, 'gcc ' .. CFLAGS .. ' -fPIC -c GuiLua.c') | ||
22 | runCommand('C libraries', dir, 'gcc ' .. CFLAGS .. ' -shared -Wl,-soname,libGuiLua.so -o ../../libraries/libGuiLua.so GuiLua.o') | ||
23 | runCommand('C apps', dir, 'gcc ' .. CFLAGS .. ' -Wl,-export-dynamic -o skang skang.c ' .. LDFLAGS .. ' -lGuiLua ' .. libs) | ||
diff --git a/src/GuiLua/skang.c b/src/GuiLua/skang.c new file mode 100644 index 0000000..facc239 --- /dev/null +++ b/src/GuiLua/skang.c | |||
@@ -0,0 +1,16 @@ | |||
1 | #include "GuiLua.h" | ||
2 | |||
3 | |||
4 | EAPI_MAIN int elm_main(int argc, char **argv) | ||
5 | { | ||
6 | elm_app_compile_bin_dir_set(PACKAGE_BIN_DIR); | ||
7 | elm_app_compile_data_dir_set(PACKAGE_DATA_DIR); | ||
8 | elm_app_compile_lib_dir_set(PACKAGE_LIB_DIR); | ||
9 | elm_app_info_set(elm_main, "GuiLua", "skang.lua"); | ||
10 | |||
11 | GuiLuaDo(argc, argv); | ||
12 | |||
13 | return 0; | ||
14 | } | ||
15 | |||
16 | ELM_MAIN() | ||
diff --git a/src/GuiLua/skang.lua b/src/GuiLua/skang.lua new file mode 100644 index 0000000..23549c3 --- /dev/null +++ b/src/GuiLua/skang.lua | |||
@@ -0,0 +1,1686 @@ | |||
1 | --[[ TODO - This should be in C, but so far development has been quite rapid doing it in Lua. | ||
2 | |||
3 | C will let us - | ||
4 | Actually do the widget stuff. | ||
5 | Slap meta tables on all value types. | ||
6 | Which lets us put the meta table on the variable, instead of on the table, which I think is cleaner. | ||
7 | Figure out the directory separator. | ||
8 | Network stuff. No need to look at Lua socket stuff, we have Ecore_Con. | ||
9 | Database stuff. No need to look at Lua SQL stuff, we have esskyuehl. Maybe. | ||
10 | |||
11 | Actually, we could have the best of both worlds, since it is currently a C / Lua hybrid. B-) | ||
12 | ]] | ||
13 | |||
14 | |||
15 | --[[ Skang package | ||
16 | |||
17 | In here should live all the internals of matrix-RAD that don't | ||
18 | specifically relate to widgets. This would include the "window" though. | ||
19 | |||
20 | skang.module(Evas) | ||
21 | skang.module(Elementary) | ||
22 | skang.skang('some/skang/file.skang') | ||
23 | |||
24 | This package is also what "apps" that use the system should "inherit" | ||
25 | from, in the same way matrix-RAD apps did. Skang "apps" could be Lua | ||
26 | modules. They could also be C code, like the extantz modules are likely | ||
27 | to be. Skang "apps" would automatically be associated with skang files | ||
28 | of the same name. | ||
29 | |||
30 | For a .skang file, the skang command (written in C) would strip off the | ||
31 | first line, add the two implied lines, then run it as Lua. The | ||
32 | skang.load() command would do the same. So that skang C comand would | ||
33 | just pass the file name to skang.load() in this library. B-) | ||
34 | |||
35 | The old skang argument types are - | ||
36 | |||
37 | {"name", "java.lang.String"}, | ||
38 | {"action", "java.lang.String"}, | ||
39 | {"type", "java.lang.String"}, | ||
40 | {"data", "java.lang.String"}, | ||
41 | {"URL", "java.lang.String"}, | ||
42 | {"file", "java.lang.String"}, | ||
43 | {"method", "java.lang.String"}, | ||
44 | {"lx", "java.lang.String"}, | ||
45 | {"ly", "java.lang.String"}, | ||
46 | {"lw", "java.lang.String"}, | ||
47 | {"lh", "java.lang.String"}, | ||
48 | {"normal", "java.lang.String"}, | ||
49 | {"ghost", "java.lang.String"}, | ||
50 | {"active", "java.lang.String"}, | ||
51 | {"toggle", "java.lang.String"}, | ||
52 | {"boolean","java.lang.Boolean"}, | ||
53 | {"number", "java.lang.Integer"}, | ||
54 | {"int", "java.lang.Integer"}, | ||
55 | {"x", "java.lang.Integer"}, | ||
56 | {"y", "java.lang.Integer"}, | ||
57 | {"w", "java.lang.Integer"}, | ||
58 | {"h", "java.lang.Integer"}, | ||
59 | {"r", "java.lang.Integer"}, | ||
60 | {"g", "java.lang.Integer"}, | ||
61 | {"b", "java.lang.Integer"}, | ||
62 | {"alpha", "java.lang.Integer"}, | ||
63 | {"acl", "net.matrix_rad.security.ACL"}, | ||
64 | ]] | ||
65 | |||
66 | |||
67 | -- Wrapping the entire module in do .. end helps if people just join a bunch of modules together, which apparently is popular. | ||
68 | -- By virtue of the fact we are stuffing our result into package.loaded[], just plain running this works as "loading the module". | ||
69 | do -- Only I'm not gonna indent this. | ||
70 | |||
71 | mainSkin = {} | ||
72 | |||
73 | -- TODO - This needs to be expanded a bit to cover things like 1.42 | ||
74 | local versions = { | ||
75 | '0%.0', 'unwritten', 'Just a stub, no code at all, or completely non-existant.', | ||
76 | '0%.1', 'prototype', 'Stuff that has only been prototyped, or partly written. There is some code, and it may even work, but it is not even close to the finished product.', | ||
77 | '%d%.3', 'written', 'Stuff that has already been written. It may not be perfect, but it is considered an aproximation of the finished product.', | ||
78 | '%d%.5', 'alpha', 'Version being tested by official alpha testers.', | ||
79 | '%d%.9', 'beta', 'Version passed alpha testing, but not ready for final release.', | ||
80 | '1%.0', 'final', 'Version ready for final release, fully tested.', | ||
81 | '3%.0', 'poetry', 'Near perfection has been acheived.', | ||
82 | '5%.0', 'nirvana', 'Perfection has been acheived.', | ||
83 | '9%.0', 'bible', 'This is the Whord of Ghod.', | ||
84 | } | ||
85 | |||
86 | -- Trying to capture best practices here for creating modules, especially since module() is broken and deprecated. | ||
87 | -- TODO - Should parse in license type to. | ||
88 | moduleBegin = function (name, author, copyright, version, timestamp, skin, isLua) | ||
89 | local _M = {} -- This is what we return to require(). | ||
90 | local level = 2 | ||
91 | |||
92 | if 'nil' == type(isLua) then isLua = true end | ||
93 | |||
94 | package.loaded[name] = _M -- Stuff the result into where require() can find it, instead of returning it at the end. | ||
95 | -- Returning it at the end does the same thing. | ||
96 | -- This is so that we can have all the module stuff at the top, in this function. | ||
97 | -- Should do this before any further require(), so that circular references don't blow out. | ||
98 | |||
99 | -- Save the callers environment. | ||
100 | local savedEnvironment | ||
101 | if isLua then | ||
102 | savedEnvironment = getfenv(level) | ||
103 | else | ||
104 | -- While the above works fine for test_c, it doesn't for GuiLua. Odd. | ||
105 | savedEnvironment = getfenv(1) | ||
106 | end | ||
107 | |||
108 | -- Clone the environment into _M, so the module can access everything as usual after the setfenv() below. | ||
109 | --[[ TODO - Check if this also clones _G or _ENV. And see if it leaks stuff in either direction. | ||
110 | local _G = _G -- Only sets a local _G for this function. | ||
111 | _M._G = _G -- This clone loop might do this, but we don't want to be able to access the old _G from outside via this leak. | ||
112 | In Lua 5.1 at least, _G was special. In 5.2, _ENV sorta replaces setfenv(), but no idea if this clone loop stomps on that. | ||
113 | ]] | ||
114 | for k, v in pairs(savedEnvironment) do | ||
115 | _M[k] = v | ||
116 | end | ||
117 | |||
118 | _M._M = _M -- So that references to _M below the setfenv() actually go to the real _M. | ||
119 | _M._NAME = name | ||
120 | _M._PACKAGE = string.gsub(_M._NAME, "[^.]*$", "") -- Strip the name down to the package name. | ||
121 | _M.isLua = isLua | ||
122 | |||
123 | -- Parse in an entire copyright message, and strip that down into bits, to put back together. | ||
124 | local date, owner = string.match(copyright, '[Cc]opyright (%d%d%d%d) (.*)') | ||
125 | _M.AUTHOR = author or owner | ||
126 | _M.COPYRIGHT = 'Copyright ' .. date .. ' ' .. _M.AUTHOR | ||
127 | -- Translate the version number into a version string. | ||
128 | local versionName, versionDesc = ' ', '' | ||
129 | for i = 1, #versions / 3 do | ||
130 | if 1 == string.find(version, versions[i]) then | ||
131 | versionName = ' ' .. versions[i + 1] .. ' ' | ||
132 | versionDesc = versions[i + 2] | ||
133 | break | ||
134 | end | ||
135 | end | ||
136 | _M.VERSION = version .. versionName .. timestamp | ||
137 | _M.VERSION_DESC = versionDesc | ||
138 | -- If there is a .skang file, read that in and override the passed in skin. | ||
139 | local f = io.open(name .. '.skang') | ||
140 | if f then | ||
141 | skin = f:read('*l') | ||
142 | if '#' == string.sub(skin, 1, 1) then skin = '' end | ||
143 | skin = skin .. f:read('*a') | ||
144 | f:close() | ||
145 | end | ||
146 | if skin then | ||
147 | skin = "local skang = require 'skang'\nlocal " .. name .. " = require '" .. name .. "'\n" .. skin | ||
148 | if nil == mainSkin._NAME then mainSkin = _M end | ||
149 | end | ||
150 | _M.DEFAULT_SKANG = skin | ||
151 | |||
152 | --_G[_M._NAME] = _M -- Stuff it into a global of the same name. | ||
153 | -- Not such a good idea to stomp on global name space. | ||
154 | -- It's also redundant coz we get stored in package.loaded[_M._NAME] anyway. | ||
155 | -- This is why module() is broken. | ||
156 | _M.savedEnvironment = savedEnvironment | ||
157 | -- NOTE - setfenv() wont work if the environment it refers to is a C function. Worse, getfenv() returns the global environment, so we can't tell. | ||
158 | if isLua then | ||
159 | -- setfenv() sets the environment for the FUNCTION, stack level deep. | ||
160 | -- The number is the stack level - | ||
161 | -- 0 running thread, 1 current function, 2 function that called this function, etc | ||
162 | setfenv(level, _M) -- Use the result for the modules internal global environment, so they don't need to qualify internal names. | ||
163 | -- Dunno if this causes problems with the do ... end style of joining modules. It does. So we need to restore in moduleEnd(). | ||
164 | -- Next question, does this screw with the environment of the skang module? No it doesn't, coz that's set up at require 'skang' time. | ||
165 | end | ||
166 | |||
167 | print('Loaded module ' .. _M._NAME .. ' version ' .. _M.VERSION .. ', ' .. _M.COPYRIGHT .. '.\n ' .. _M.VERSION_DESC) | ||
168 | |||
169 | return _M | ||
170 | end | ||
171 | |||
172 | |||
173 | --[[ Parse command line parameters. | ||
174 | |||
175 | This is done in two parts. Skang will do an initial scan and tokenise, | ||
176 | then each module gets a chance to pull it's own Things from the result. | ||
177 | |||
178 | Make the command line parameter getting MUCH more intelligent, try to support the common | ||
179 | command line interfaces - | ||
180 | |||
181 | arg value | ||
182 | a value | ||
183 | /arg value | ||
184 | /a value | ||
185 | --arg value | ||
186 | --a value | ||
187 | -a value | ||
188 | -ab ('a' and 'b' are both shortcuts.) | ||
189 | arg=value | ||
190 | a=value | ||
191 | arg1=value1&arg2=value2 | ||
192 | arg1=value1|arg2=value2 | ||
193 | a=value1&a=value2 | ||
194 | +arg/-arg (Can't support this generically.) | ||
195 | |||
196 | Ignore /,-,--,& except as arg introducers. Use = as value introducer. Expect | ||
197 | arg or a. If type is String, expect a value. If type is integer, and next token is | ||
198 | not an integer, increment current value, otherwise expect integer value. If type is | ||
199 | boolean, value beginning with T, t, F, f, etc is true, otherwise value is false, unless | ||
200 | next token starts with an introducer, then value is true. | ||
201 | |||
202 | TODO - Finish supporting all of the above. | ||
203 | These all need deeper parsing, but we dunno if they might have been inside quoted strings from the shell. | ||
204 | arg=value Shell. | ||
205 | arg1=value1&arg2=value2 For URLs. | ||
206 | arg1=value1|arg2=value2 Can't remember why, probably the old skang multivalue syntax. | ||
207 | Test it all. | ||
208 | Skang command line should have standardish stuff, like --version, --help, --help module.thing. | ||
209 | Lua does these already, might be no need to do them ourselves - | ||
210 | -e 'some code'. | ||
211 | -i go interactive after running the script. | ||
212 | -v version. | ||
213 | - read from stdin non interactively. | ||
214 | LuaJIT also has this - | ||
215 | -- stop processing options. | ||
216 | ]] | ||
217 | |||
218 | ARGS = {} | ||
219 | lua = '' | ||
220 | command = '' | ||
221 | |||
222 | |||
223 | -- Do an initial scan and tokenise of the command line arguments. | ||
224 | scanArguments = function (args) | ||
225 | if args then | ||
226 | lua = args[-1] | ||
227 | command = args[0] | ||
228 | for i, v in ipairs(args) do | ||
229 | local pre = '' | ||
230 | if '--' == string.sub(v, 1, 2) then pre = '--'; v = string.sub(v, 3, -1) end | ||
231 | if '-' == string.sub(v, 1, 1) then pre = '-'; v = string.sub(v, 2, -1) end | ||
232 | if '+' == string.sub(v, 1, 1) then pre = '+'; v = string.sub(v, 2, -1) end | ||
233 | -- TODO - Make this the opposite of the directory separator for what ever platform we are running on. | ||
234 | -- Which Lua can't figure out I think. | ||
235 | if '/' == string.sub(v, 1, 1) then pre = '/'; v = string.sub(v, 2, -1) end | ||
236 | if '=' == string.sub(v, 1, 1) then pre = '='; v = string.sub(v, 2, -1) end | ||
237 | if '&' == string.sub(v, 1, 1) then pre = '&'; v = string.sub(v, 2, -1) end | ||
238 | if '|' == string.sub(v, 1, 1) then pre = '|'; v = string.sub(v, 2, -1) end | ||
239 | if '' ~= v then ARGS[i] = {pre, v} end | ||
240 | end | ||
241 | end | ||
242 | end | ||
243 | |||
244 | parseType = function (module, thingy, v, value) | ||
245 | if 'string' == thingy.types[1] then | ||
246 | if value then | ||
247 | module[v[2] ] = value[2] | ||
248 | value[2] = nil -- Mark it as used. | ||
249 | else | ||
250 | print('ERROR - Expected a string value for ' .. thingy.names[1]) | ||
251 | end | ||
252 | end | ||
253 | |||
254 | if 'number' == thingy.types[1] then | ||
255 | if value then | ||
256 | -- If the introducer is '-', then this should be a negative number. | ||
257 | if '-' == value[1] then value[1] = ''; value[2] = '-' .. value[2] end | ||
258 | -- Only parse the next value as a number if it doesn't have an introducer. | ||
259 | if ('' == value[1]) or ('=' == value[1]) then | ||
260 | value[2] = tonumber(value[2]) | ||
261 | if value[2] then | ||
262 | module[v[2] ] = value[2] | ||
263 | value[2] = nil -- Mark it as used. | ||
264 | else | ||
265 | print('ERROR - Expected a number value for ' .. thingy.names[1]) | ||
266 | end | ||
267 | else | ||
268 | module[v[2] ] = module[v[2] ] + 1 | ||
269 | end | ||
270 | else | ||
271 | print('ERROR - Expected a number value for ' .. thingy.names[1]) | ||
272 | end | ||
273 | end | ||
274 | |||
275 | if 'function' == thingy.types[1] then | ||
276 | local args = {} | ||
277 | -- TODO - Should allow more than one argument, but would need to pass in ARGS and i. | ||
278 | if 2 == #thingy.types then | ||
279 | if value then | ||
280 | -- TODO - Should check the type of the arguments. | ||
281 | args[#args + 1] = value[2] | ||
282 | module[v[2] ](args[1]) | ||
283 | value[2] = nil -- Mark it as used. | ||
284 | else | ||
285 | print('ERROR - Expected an argument for ' .. thingy.names[1]) | ||
286 | end | ||
287 | else | ||
288 | module[v[2] ]() | ||
289 | end | ||
290 | end | ||
291 | |||
292 | if 'boolean' == thingy.types[1] then | ||
293 | if value then | ||
294 | -- Only parse the next value as a boolean if it doesn't have an introducer. | ||
295 | if ('' == value[1]) or ('=' == value[1]) then | ||
296 | module[v[2] ] = isBoolean(value[2]) | ||
297 | value[2] = nil -- Mark it as used. | ||
298 | else | ||
299 | module[v[2] ] = true | ||
300 | end | ||
301 | else | ||
302 | print('ERROR - Expected a boolean value for ' .. thingy.names[1]) | ||
303 | end | ||
304 | end | ||
305 | end | ||
306 | |||
307 | pullArguments = function (module) | ||
308 | -- Look for our command line arguments. | ||
309 | local metaMum = getmetatable(module) | ||
310 | if metaMum and metaMum.__self then | ||
311 | for i, v in ipairs(ARGS) do | ||
312 | if v[2] then | ||
313 | local thingy = metaMum.__self.stuff[v[2] ] | ||
314 | -- Did we find one of ours? | ||
315 | if thingy then | ||
316 | parseType(module, thingy, v, ARGS[i + 1]) | ||
317 | v[2] = nil -- Mark it as used. | ||
318 | else | ||
319 | -- Didn't find one directly, check for single letter matches in '-abc'. | ||
320 | for k, w in pairs(metaMum.__self.stuff) do | ||
321 | if 1 == #w.names[1] then | ||
322 | for j = 1, #v[2] do | ||
323 | if string.sub(v[2], j, 1) == w.names[1] then | ||
324 | if 1 == j then | ||
325 | v[2] = string.sub(v[2], 2, -1) | ||
326 | if 'boolean' == w.types[1] then module[v[2] ] = true end | ||
327 | elseif #v[2] == j then | ||
328 | v[2] = string.sub(v[2], 1, j - 1) | ||
329 | -- The one at the end is the only one that could have a following value. | ||
330 | parseType(module, w, v, ARGS[i + 1]) | ||
331 | else | ||
332 | v[2] = string.sub(v[2], 1, j - 1) .. string.sub(v[2], j + 1, -1) | ||
333 | if 'boolean' == w.types[1] then module[v[2] ] = true end | ||
334 | end | ||
335 | if '' == v[2] then v[2] = nil end -- Mark it as used. | ||
336 | end | ||
337 | end | ||
338 | end | ||
339 | end | ||
340 | end | ||
341 | end | ||
342 | end | ||
343 | end | ||
344 | end | ||
345 | |||
346 | -- Restore the environment, and grab paramateres from standard places. | ||
347 | moduleEnd = function (module) | ||
348 | -- See if there is a properties file, and run it in the modules environment. | ||
349 | local properties, err = loadfile(module._NAME .. '.properties') | ||
350 | if properties then | ||
351 | setfenv(properties, getfenv(2)) | ||
352 | properties() | ||
353 | elseif 'cannot open ' ~= string.sub(err, 1, 12) then | ||
354 | print("ERROR - " .. err) | ||
355 | end | ||
356 | |||
357 | pullArguments(module) | ||
358 | |||
359 | -- Run the main skin, which is the first skin that is defined. In theory, the skin from the main module. | ||
360 | if mainSkin == module then | ||
361 | print("RUNNING SKIN FOR " .. module._NAME) | ||
362 | local skin, err = loadstring(module.DEFAULT_SKANG) | ||
363 | if skin then | ||
364 | setfenv(skin, getfenv(2)) | ||
365 | skin() | ||
366 | else | ||
367 | print("ERROR - " .. err) | ||
368 | end | ||
369 | end | ||
370 | |||
371 | if module.isLua then setfenv(2, module.savedEnvironment) end | ||
372 | end | ||
373 | |||
374 | |||
375 | -- Call this now so that from now on, this is like any other module. | ||
376 | local _M = moduleBegin('skang', 'David Seikel', 'Copyright 2014 David Seikel', '0.1', '2014-03-27 02:57:00') | ||
377 | |||
378 | -- This works coz LuaJIT automatically loads the jit module. | ||
379 | if type(jit) == 'table' then | ||
380 | print('Skang is being run by ' .. jit.version .. ' under ' .. jit.os .. ' on a ' .. jit.arch) | ||
381 | else | ||
382 | print('Skang is being run by Lua version ' .. _VERSION) | ||
383 | end | ||
384 | |||
385 | scanArguments(arg) | ||
386 | |||
387 | |||
388 | function printTableStart(table, space, name) | ||
389 | print(space .. name .. ": ") | ||
390 | print(space .. "{") | ||
391 | printTable(table, space .. " ") | ||
392 | print(space .. "}") | ||
393 | if '' == space then print('') end | ||
394 | end | ||
395 | |||
396 | function printTable(table, space) | ||
397 | if nil == table then return end | ||
398 | for k, v in pairs(table) do | ||
399 | if type(v) == "table" then | ||
400 | if v._NAME then | ||
401 | print(space .. "SKANG module " .. v._NAME .. ";") | ||
402 | else | ||
403 | printTableStart(v, space, k) | ||
404 | end | ||
405 | elseif type(v) == "string" then | ||
406 | print(space .. k .. ': "' .. v .. '";') | ||
407 | elseif type(v) == "function" then | ||
408 | print(space .. "function " .. k .. "();") | ||
409 | elseif type(v) == "userdata" then | ||
410 | print(space .. "userdata " .. k .. ";") | ||
411 | elseif type(v) == "boolean" then | ||
412 | if (v) then | ||
413 | print(space .. "boolean " .. k .. " TRUE ;") | ||
414 | else | ||
415 | print(space .. "boolean " .. k .. " FALSE ;") | ||
416 | end | ||
417 | else | ||
418 | print(space .. k .. ": " .. v .. ";") | ||
419 | end | ||
420 | end | ||
421 | end | ||
422 | |||
423 | |||
424 | csv2table = function (csv) | ||
425 | local result = {} | ||
426 | local i = 1 | ||
427 | |||
428 | for v in string.gmatch(csv, ' *([^,]+)') do | ||
429 | result[i] = v | ||
430 | i = i + 1 | ||
431 | end | ||
432 | return result | ||
433 | end | ||
434 | |||
435 | |||
436 | shiftLeft = function (tab) | ||
437 | local result = tab[1] | ||
438 | table.remove(tab, 1) | ||
439 | return result | ||
440 | end | ||
441 | |||
442 | |||
443 | -- My clever boolean check, this is the third language I've written this in. B-) | ||
444 | -- true 1 yes ack ok one positive absolutely affirmative 'ah ha' 'shit yeah' 'why not' | ||
445 | local isTrue = 't1aopswy' | ||
446 | -- false 0 no nack nope zero negative nah 'no way' 'get real' 'uh uh' 'fuck off' 'bugger off' | ||
447 | local isFalse = 'f0bgnuz' | ||
448 | isBoolean = function (aBoolean) | ||
449 | local result = false | ||
450 | |||
451 | if type(aBoolean) ~= 'nil' then | ||
452 | -- The default case, presence of a value means it's true. | ||
453 | result = true | ||
454 | if type(aBoolean) == 'boolean' then result = aBoolean | ||
455 | elseif type(aBoolean) == 'function' then result = aBoolean() | ||
456 | elseif type(aBoolean) == 'number' then result = (aBoolean ~= 0) | ||
457 | elseif type(aBoolean) == 'string' then | ||
458 | if '' == aBoolean then | ||
459 | result = false | ||
460 | else | ||
461 | if 1 == string.find(string.lower(aBoolean), '^[' .. isTrue .. ']') then result = true end | ||
462 | if 1 == string.find(string.lower(aBoolean), '^[' .. isFalse .. ']') then result = false end | ||
463 | end | ||
464 | end | ||
465 | end | ||
466 | return result | ||
467 | end | ||
468 | |||
469 | |||
470 | --[[ Thing Java package | ||
471 | |||
472 | matrix-RAD had Thing as the base class of everything. | ||
473 | |||
474 | Each "users session" (matrix-RAD term that came from Java | ||
475 | applets/servlets) has a ThingSpace, which is a tree that holds | ||
476 | everything else. It holds the class cache, commands, loaded modules, | ||
477 | variables and their values, widgets and their states. In matrix-RAD I | ||
478 | built BonsiaTree and LeafLike, for the old FDO system I built dumbtrees. | ||
479 | |||
480 | Other Thing things are - | ||
481 | get/set The getter and setter. | ||
482 | number No idea how this was useful. | ||
483 | skang The owning object, a Skang (actually got this, called module for now). | ||
484 | owner The owning object, a String (module._NAME). | ||
485 | clas Class of the Thing, a Class. (pointless) | ||
486 | type Class of the Thing, a String. (pointless) | ||
487 | realType Real Class of the Thing, a String. (pointless) | ||
488 | myRoot ThingSpace we are in, a ThingSpace. | ||
489 | |||
490 | Also various functions to wrap checking the security, like canDo, canRead, etc. | ||
491 | ]] | ||
492 | |||
493 | |||
494 | --[[ Stuff Java package | ||
495 | |||
496 | In matrix-RAD Stuff took care of multi value Things, like database rows. | ||
497 | |||
498 | Stuff is an abstract class that gets extended by other classes, like | ||
499 | SquealStuff, which was the only thing extending it. It dealt with the | ||
500 | basic "collection of things" stuff. Each individual thing was called a | ||
501 | stufflet. A final fooStuff would extend SquealStuff, and include an | ||
502 | array of strings called "stufflets" that at least named the stufflets, | ||
503 | but could also include metadata and links to other Stuffs. | ||
504 | |||
505 | There was various infrastructure for reading and writing Stuff, throwing | ||
506 | rows of Stuff into grids, having choices of Stuff, linking stufflets to | ||
507 | individual widgets, having default Stuffs for windows, validating | ||
508 | Stuffs, etc. | ||
509 | |||
510 | In Lua, setting up stuff has been folded into the general Thing stuff. | ||
511 | |||
512 | ]] | ||
513 | |||
514 | |||
515 | --[[ Thing structure - | ||
516 | |||
517 | In the following, meta(table) is short for getmetatable(table). | ||
518 | |||
519 | In Lua everything is supposed to be a first class value, and variables are just places to store values. | ||
520 | All variables are in fact stored in tables, even if it's just the local environment table. | ||
521 | Any variable that has a value of nil, doesn't actually exist. That's the definition. | ||
522 | While non table things can have metatables, Lua can only set metatables on tables, C has no such restriction. | ||
523 | meta(table).__index and __newindex only work on table entries that don't exist. | ||
524 | __index(table, key) is called if table.key is nil. | ||
525 | Though if __index is a table, then try __index[key]. | ||
526 | __newindex(table, key, value) is called if table.key is nil. | ||
527 | Though if __newindex is a table, then try __newindex[key] = value. | ||
528 | Using both __index and __newindex, and keeping the actual values elsewhere, is called a proxy table. | ||
529 | meta(table).__call(table, ...) is called when trying to access table as a function - table(...). | ||
530 | |||
531 | It's worth repeating - | ||
532 | All variables in Lua are in some table somewhere, even if it's just the global environment table. | ||
533 | Metatables are only associated vith values, not variables. | ||
534 | Lua can only attach metatables to values that are tables, but C can attach metatables to any value. | ||
535 | |||
536 | |||
537 | A Thing is a managed variable stored in a parent proxy table, which is usually empty. | ||
538 | So the values stored in this Thing are actually stored in meta(parent)__values[thing]. | ||
539 | parent[thing] -> __index (parent, thing) -> meta(parent).__values[thing] | ||
540 | parent[thing] = value -> __newindex(parent, thing, value) -> meta(parent).__values[thing] = value | ||
541 | |||
542 | |||
543 | Each Thing has a description table that includes - | ||
544 | names - An array of names, the first one is the "official" name. | ||
545 | types - An array of types, the first one is the "official" type. | ||
546 | help - A descriptive text for humans to read. | ||
547 | default - The default value. | ||
548 | widget - A default widget definition. | ||
549 | required - If the Thing is required. | ||
550 | isValid - A function that tests if the Thing is valid. | ||
551 | errors - Any errors related to the Thing. | ||
552 | isKeyed - Is this a parent for Things that are stored under an arbitrary key. | ||
553 | stuff - An array of descriptions for sub Things, so Things that are tables can have their own Things. | ||
554 | and other things that aren't actually used yet. | ||
555 | All things that a description doesn't have should be inherited from the Thing table. | ||
556 | setmetatable(aStuff, {__index = Thing}) | ||
557 | Descriptions should be able to be easily shared by various Things. | ||
558 | |||
559 | |||
560 | A parent's metatable has __self, which is it's own description. | ||
561 | A parent is free to use it's own name space for anything it wants. | ||
562 | Only the variables it specifies as managed Things are dealt with here. | ||
563 | |||
564 | |||
565 | Things that are tables are treated differently, in two different ways even. | ||
566 | Ordinary table Things are basically treated recursively, the table is a parent, and it gets it's own Things. | ||
567 | There is also 'Keyed' type table Things, where the keys to this table are arbitrary, but we still want to store Things in it. | ||
568 | In this case, when a table is assigned to this Keyed Thing, via a new key, a new table Thing is created by copying the parent Thing description. | ||
569 | |||
570 | |||
571 | TODO - | ||
572 | test.foo -> test.__index(test, 'foo') -> test.__values[foo]; if that's nil, and test.stuff[foo], then return an empty table instead? | ||
573 | Do we still need a parent pointer? | ||
574 | Should be in __values I guess. | ||
575 | __values[key].value | ||
576 | __values[key].parent | ||
577 | Weak references might help in here somewhere. | ||
578 | Maybe try looking in the skang table for Things that are not found? | ||
579 | Maybe put Things in the skang table that are unique from modules? | ||
580 | I think this is what matrix-RAD Collisions was all about. | ||
581 | ]] | ||
582 | |||
583 | -- There is no ThingSpace, or Stuff, now it's all just in this meta table. | ||
584 | local Thing = | ||
585 | { | ||
586 | -- Default Thing values. | ||
587 | names = {'unknown'}, | ||
588 | help = 'No description supplied.', -- help text describing this Thing. | ||
589 | default = '', -- The default value. This could be a funcion, making this a command. | ||
590 | types = {}, -- A list of types. The first is the type of the Thing itself, the rest are for multi value Things. Or argument types for commands. | ||
591 | required = false, -- Is this thing is required. TODO - Maybe fold this into types somehow, or acl? | ||
592 | widget = '', -- Default widget command arguments for creating this Thing as a widget. | ||
593 | -- acl = '', -- Access Control List defining security restrains. | ||
594 | -- boss = '', -- The Thing or person that owns this Thing, otherwise it is self owned. | ||
595 | |||
596 | action = 'nada', -- An optional action to perform. | ||
597 | tell = '', -- The skang command that created this Thing. | ||
598 | pattern = '.*', -- A pattern to restrict values. | ||
599 | |||
600 | isKeyed = false, -- Is this thing an arbitrarily Keyed table? | ||
601 | isReadOnly = false, -- Is this Thing read only? | ||
602 | isServer = false, -- Is this Thing server side? | ||
603 | isStub = false, -- Is this Thing a stub? | ||
604 | isStubbed = false, -- Is this Thing stubbed elsewhere? | ||
605 | |||
606 | hasCrashed = 0, -- How many times this Thing has crashed. | ||
607 | |||
608 | append = function (self,data) -- Append to the value of this Thing. | ||
609 | end, | ||
610 | |||
611 | stuff = {}, -- The sub things this Thing has, for modules, tables, and Keyed tables. | ||
612 | errors = {}, -- A list of errors returned by isValid(). | ||
613 | |||
614 | isValid = function (self, parent) -- Check if this Thing is valid, return resulting error messages in errors. | ||
615 | -- Anything that overrides this method, should call this super method first. | ||
616 | local name = self.names[1] | ||
617 | local metaMum = getmetatable(parent) | ||
618 | local value = metaMum.__values[name] | ||
619 | local mum = metaMum.__self.names[1] | ||
620 | |||
621 | local t = type(value) or 'nil' | ||
622 | self.errors = {} | ||
623 | -- TODO - Naturally there should be formatting functions for stuffing Thing stuff into strings, and overridable output functions. | ||
624 | if 'nil' == t then | ||
625 | if self.required then table.insert(self.errors, mum .. '.' .. name .. ' is required!') end | ||
626 | else | ||
627 | if 'widget' == self.types[1] then | ||
628 | -- TODO - Should validate any attached Thing. | ||
629 | elseif self.types[1] ~= t then table.insert(self.errors, mum .. '.' .. name .. ' should be a ' .. self.types[1] .. ', but it is a ' .. t .. '!') | ||
630 | else | ||
631 | if 'number' == t then value = '' .. value end | ||
632 | if ('number' == t) or ('string' == t) then | ||
633 | if 1 ~= string.find(value, '^' .. self.pattern .. '$') then table.insert(self.errors, mum .. '.' .. name .. ' does not match pattern "' .. self.pattern .. '"!') end | ||
634 | end | ||
635 | end | ||
636 | end | ||
637 | |||
638 | for k, v in pairs(self.stuff) do | ||
639 | if not v:isValid(value) then | ||
640 | for i, w in ipairs(v.errors) do | ||
641 | table.insert(self.errors, w) | ||
642 | end | ||
643 | end | ||
644 | end | ||
645 | |||
646 | return #(self.errors) == 0 | ||
647 | end, | ||
648 | |||
649 | remove = function (self) -- Delete this Thing. | ||
650 | end, | ||
651 | } | ||
652 | |||
653 | |||
654 | getStuffed = function (parent, key) | ||
655 | local metaMum = getmetatable(parent) | ||
656 | local thingy | ||
657 | |||
658 | if metaMum and metaMum.__self then | ||
659 | thingy = metaMum.__self.stuff[key] | ||
660 | |||
661 | if not thingy then | ||
662 | -- Deal with getting a table entry. | ||
663 | if metaMum.__values[key] then | ||
664 | thingy = getmetatable(metaMum.__values[key]).__self | ||
665 | end | ||
666 | end | ||
667 | end | ||
668 | return metaMum, thingy | ||
669 | end | ||
670 | |||
671 | local Mum = | ||
672 | { | ||
673 | |||
674 | __index = function (parent, key) | ||
675 | -- This only works for keys that don't exist. By definition a value of nil means it doesn't exist. | ||
676 | |||
677 | -- First see if this is a Thing. | ||
678 | local metaMum, thingy = getStuffed(parent, key) | ||
679 | |||
680 | if thingy then | ||
681 | return metaMum.__values[thingy.names[1] ] or thingy.default | ||
682 | end | ||
683 | |||
684 | -- Then see if we can inherit it from Thing. | ||
685 | return Thing[key] | ||
686 | end, | ||
687 | |||
688 | __newindex = function (parent, key, value) | ||
689 | -- This only works for keys that don't exist. By definition a value of nil means it doesn't exist. | ||
690 | |||
691 | -- First see if this is a Thing. | ||
692 | local metaMum, thingy = getStuffed(parent, key) | ||
693 | |||
694 | if not thingy then | ||
695 | if metaMum.__self.isKeyed then | ||
696 | -- Deal with setting a new Keyed table entry. | ||
697 | local newThing = copy(parent, key) | ||
698 | local newSelf = getmetatable(newThing).__self | ||
699 | rawset(metaMum.__values, key, newThing) | ||
700 | thingy = {} | ||
701 | for k, v in pairs(newSelf) do | ||
702 | thingy[k] = v | ||
703 | end | ||
704 | thingy.names={key} | ||
705 | thingy.types={'table'} | ||
706 | setmetatable(thingy, {__index = Thing}) -- To pick up isValid, pattern, and the other stuff by default. | ||
707 | end | ||
708 | end | ||
709 | |||
710 | if thingy then | ||
711 | local name = thingy.names[1] | ||
712 | local oldMum | ||
713 | |||
714 | if 'table' == type(value) then | ||
715 | -- Coz setting it via metaMum screws with the __index stuff somehow. | ||
716 | local oldValue = metaMum.__values[name] | ||
717 | if 'table' == type(oldValue) then | ||
718 | oldMum = getmetatable(oldValue) | ||
719 | if oldMum then | ||
720 | -- TODO - This SHOULD work, but doesn't - | ||
721 | --setmetatable(value, oldMum) | ||
722 | -- Instead we do this - | ||
723 | -- Clear out any values in the old table. | ||
724 | for k, v in pairs(oldMum.__values) do | ||
725 | oldMum.__values[k] = nil | ||
726 | end | ||
727 | for k, v in pairs(value) do | ||
728 | local newK = oldMum.__self.stuff[k] | ||
729 | if newK then newK = newK.names[1] else newK = k end | ||
730 | oldMum.__values[newK] = v | ||
731 | end | ||
732 | end | ||
733 | end | ||
734 | end | ||
735 | if nil == oldMum then metaMum.__values[name] = value end | ||
736 | -- NOTE - invalid values are still stored, this is by design. | ||
737 | if not thingy:isValid(parent) then | ||
738 | for i, v in ipairs(thingy.errors) do | ||
739 | print('ERROR - ' .. v) | ||
740 | end | ||
741 | end | ||
742 | -- TODO - Go through it's linked things and set them to. | ||
743 | -- Done, don't fall through to the rawset() | ||
744 | return | ||
745 | end | ||
746 | |||
747 | rawset(parent, key, value) -- Stuff it normally. | ||
748 | end, | ||
749 | |||
750 | __call = function (func, ...) | ||
751 | return thingasm(func, ...) | ||
752 | end, | ||
753 | |||
754 | } | ||
755 | |||
756 | newMum = function () | ||
757 | local result = {} | ||
758 | --[[ From an email by Mike Pall - | ||
759 | "Important: create the metatable and its metamethods once and reuse | ||
760 | the _same_ metatable for _every_ instance." | ||
761 | |||
762 | This is for LuaJIT, he's the author, and concerns performance. | ||
763 | |||
764 | TODO - Which is the exact opposite of what we are doing. Perhaps we can fix that? | ||
765 | ]] | ||
766 | for k, v in pairs(Mum) do | ||
767 | result[k] = v | ||
768 | end | ||
769 | result.__self = {stuff={}} | ||
770 | result.__values = {} | ||
771 | return result | ||
772 | end | ||
773 | |||
774 | |||
775 | -- skang.thingasm() Creates a new Thing, or changes an existing one. | ||
776 | --[[ It can be called in many different ways - | ||
777 | |||
778 | It can be called with positional arguments - (names, help, default, types, widget, required, acl, boss) | ||
779 | Or it can be called with a table - {names, help, pattern='.*', acl='rwx'} | ||
780 | |||
781 | The first argument can be another Thing (the parent), or a string list of names (see below). | ||
782 | |||
783 | It can be called by itself, with no parent specified - | ||
784 | thingasm('foo', 'help text) | ||
785 | In this case the surrounding Lua environment becomes the parent of foo. | ||
786 | If the first argument (or first in the table) is a string, then it's this form. | ||
787 | All others include the parent as the first argument, which would be a table. | ||
788 | |||
789 | It can be called by calling the parent as a function - | ||
790 | foo('bar', 'some help', types='table') -- ___call(foo, 'bar', ...) And now foo is the parent. | ||
791 | foo.bar{'baz', types='Keyed'} -- thingasm({foo.bar, 'baz', ...}) | ||
792 | foo.bar.baz{'field0'} -- thingasm({foo.bar.baz, 'field0'}) | ||
793 | foo.bar.baz{'field1'} | ||
794 | ]] | ||
795 | |||
796 | -- names - a comma seperated list of names, aliases, and shortcuts. The first one is the official name. | ||
797 | -- If this is not a new thing, then only the first one is used to look it up. | ||
798 | -- So to change names, use skang.thingasm{'oldName', names='newName,otherNewName'} | ||
799 | thingasm = function (names, ...) | ||
800 | local params = {...} | ||
801 | local new = false | ||
802 | local parent | ||
803 | local set = true | ||
804 | |||
805 | -- Check how we were called, and re arrange stuff to match. | ||
806 | if 0 == #params then | ||
807 | if ('table' == type(names)) then -- thingasm{...} | ||
808 | params = names | ||
809 | names = shiftLeft(params) | ||
810 | if 'table' == type(names) then -- thingasm{parent, 'foo', ...} | ||
811 | parent = names | ||
812 | names = shiftLeft(params) | ||
813 | end | ||
814 | end -- thingasm("foo") otherwise | ||
815 | else | ||
816 | if 'table' == type(names) then | ||
817 | parent = names | ||
818 | if 'string' == type(...) then params = {...} -- C or __call(table, string, ..) | ||
819 | elseif 'table' == type(...) then params = ... -- __call(table, table) | ||
820 | end | ||
821 | names = shiftLeft(params) | ||
822 | end -- thingasm('foo', ...) otherwise | ||
823 | end | ||
824 | |||
825 | -- Break out the names. | ||
826 | names = csv2table(names) | ||
827 | local name = names[1] | ||
828 | local oldNames = {} | ||
829 | |||
830 | -- TODO - Double check this comment - No need to bitch and return if no names, this will crash for us. | ||
831 | |||
832 | -- Grab the environment of the calling function if no parent was passed in. | ||
833 | parent = parent or getfenv(2) | ||
834 | local metaMum = getmetatable(parent) | ||
835 | -- Coz at module creation time, Thing is an empty table, or in case this is for a new parent. | ||
836 | if nil == metaMum then | ||
837 | metaMum = newMum() | ||
838 | metaMum.__self.names = {parent._NAME or 'NoName'} | ||
839 | if parent._NAME then metaMum.__self.types = {'Module'} end | ||
840 | setmetatable(parent, metaMum) | ||
841 | end | ||
842 | |||
843 | local thingy = metaMum.__self.stuff[name] | ||
844 | if not thingy then -- This is a new Thing. | ||
845 | new = true | ||
846 | thingy = {} | ||
847 | thingy.names = names | ||
848 | thingy.stuff = {} | ||
849 | setmetatable(thingy, {__index = Thing}) -- To pick up isValid, pattern, and the other stuff by default. | ||
850 | end | ||
851 | |||
852 | -- Pull out positional arguments. | ||
853 | thingy.help = params[1] or thingy.help | ||
854 | thingy.default = params[2] or thingy.default | ||
855 | local types = params[3] or table.concat(thingy.types or {}, ',') | ||
856 | |||
857 | -- Pull out named arguments. | ||
858 | for k, v in pairs(params) do | ||
859 | if 'string' == type(k) then | ||
860 | if 'types' == k then types = v | ||
861 | elseif 'names' == k then | ||
862 | oldNames = thingy.names | ||
863 | thingy.names = cvs2table(v) | ||
864 | elseif 'required' == k then | ||
865 | if isBoolean(v) then thingy.required = true end | ||
866 | else thingy[k] = v | ||
867 | end | ||
868 | end | ||
869 | end | ||
870 | |||
871 | -- Find type, default to string, then break out the other types. | ||
872 | local typ = type(thingy.default) | ||
873 | if 'nil' == typ then typ = 'string' end | ||
874 | if 'function' == typ then types = typ .. ',' .. types end | ||
875 | if '' == types then types = typ end | ||
876 | thingy.types = csv2table(types) | ||
877 | |||
878 | if 'widget' == thingy.types[1] then | ||
879 | set = false | ||
880 | local args, err = loadstring('return ' .. thingy.widget) | ||
881 | if args then | ||
882 | setfenv(args, parent) | ||
883 | thingy.Cwidget = widget(args()) | ||
884 | print('\nNO IDEA WHY this does isValid() three times on the action, and the first one being a string.') | ||
885 | parent.W[name] = thingy | ||
886 | else | ||
887 | print("ERROR - " .. err) | ||
888 | end | ||
889 | end | ||
890 | |||
891 | -- Deal with Keyed and tables. | ||
892 | if 'Keyed' == thingy.types[1] then | ||
893 | set = false | ||
894 | thingy.types[1] = 'table' | ||
895 | thingy.isKeyed = true | ||
896 | end | ||
897 | if 'table' == thingy.types[1] then | ||
898 | -- Default in this case becomes a parent. | ||
899 | if '' == thingy.default then thingy.default = {} end | ||
900 | local thisMum = newMum() | ||
901 | thisMum.__self = thingy | ||
902 | setmetatable(thingy.default, thisMum) | ||
903 | end | ||
904 | |||
905 | if 'userdata' == thingy.types[1] then | ||
906 | set = false | ||
907 | end | ||
908 | |||
909 | -- Remove old names, then stash the Thing under all of it's new names. | ||
910 | for i, v in ipairs(oldNames) do | ||
911 | metaMum.__self.stuff[v] = nil | ||
912 | end | ||
913 | for i, v in ipairs(thingy.names) do | ||
914 | metaMum.__self.stuff[v] = thingy | ||
915 | end | ||
916 | |||
917 | -- This triggers the Mum.__newindex metamethod above. If nothing else, it triggers thingy.isValid() | ||
918 | if new and set then parent[name] = thingy.default end | ||
919 | end | ||
920 | |||
921 | |||
922 | fixNames = function (module, name) | ||
923 | local stuff = getmetatable(module) | ||
924 | if stuff then | ||
925 | stuff.__self.names[1] = name | ||
926 | for k, v in pairs(stuff.__self.stuff) do | ||
927 | if 'table' == v.types[1] then | ||
928 | local name = v.names[1] | ||
929 | print(name .. ' -> ' .. name) | ||
930 | fixNames(stuff.__values[name], name) | ||
931 | end | ||
932 | end | ||
933 | end | ||
934 | end | ||
935 | |||
936 | |||
937 | copy = function (parent, name) | ||
938 | local result = {} | ||
939 | local stuff = getmetatable(parent).__self.stuff | ||
940 | |||
941 | for k, v in pairs(stuff) do | ||
942 | local temp = {} | ||
943 | for l, w in pairs(v) do | ||
944 | temp[l] = w | ||
945 | end | ||
946 | temp[1] = table.concat(temp.names, ',') | ||
947 | temp.names = nil | ||
948 | temp.types = table.concat(temp.types, ',') | ||
949 | temp.errors = nil | ||
950 | thingasm(result, temp) | ||
951 | end | ||
952 | getmetatable(result).__self.names = {name} | ||
953 | |||
954 | -- TODO - Should we copy values to? | ||
955 | |||
956 | return result | ||
957 | end | ||
958 | |||
959 | module = function (name) | ||
960 | _G[name] = require(name) | ||
961 | return _G[name] | ||
962 | end | ||
963 | |||
964 | stuff = function (aThingy, aStuff) | ||
965 | return getmetatable(aThingy).__self.stuff[aStuff] | ||
966 | end | ||
967 | |||
968 | |||
969 | get = function (stuff, key, name) | ||
970 | local result | ||
971 | if name then | ||
972 | local thingy = getmetatable(stuff) | ||
973 | if thingy then | ||
974 | local this = thingy.__self.stuff[key] | ||
975 | if this then result = this[name] end | ||
976 | end | ||
977 | else | ||
978 | result = stuff[key] | ||
979 | end | ||
980 | return result | ||
981 | end | ||
982 | |||
983 | |||
984 | reset = function (stuff, key, name) | ||
985 | if name then | ||
986 | local thingy = getmetatable(stuff) | ||
987 | if thingy then | ||
988 | local this = thingy.__self.stuff[key] | ||
989 | if this then this[name] = nil end | ||
990 | end | ||
991 | else | ||
992 | stuff[key] = nil | ||
993 | end | ||
994 | end | ||
995 | |||
996 | |||
997 | set = function (stuff, key, name, value) | ||
998 | if 'nil' ~= type(value) then | ||
999 | local thingy = getmetatable(stuff) | ||
1000 | if thingy then | ||
1001 | local this = thingy.__self.stuff[key] | ||
1002 | if this then this[name] = value end | ||
1003 | end | ||
1004 | else | ||
1005 | -- In this case, value isn't there, so we are reusing the third argument as the value. | ||
1006 | stuff[key] = name | ||
1007 | end | ||
1008 | end | ||
1009 | |||
1010 | |||
1011 | -- Get our C functions installed into skang. | ||
1012 | -- This has to be after thingasm is defined. | ||
1013 | package.cpath = package.cpath .. ';../../libraries/lib?.so' | ||
1014 | local GuiLua = require 'GuiLua' | ||
1015 | |||
1016 | |||
1017 | thingasm('module,l', 'Load a module.', module, 'file') | ||
1018 | thingasm('get', 'Get the current value of an existing Thing or metadata.', get, 'thing,key,name') | ||
1019 | thingasm('reset', 'Reset the current value of an existing Thing or metadata.', reset, 'thing,key,name') | ||
1020 | thingasm('set', 'Set the current value of an existing Thing or metadata.', set, 'thing,key,name,data') | ||
1021 | |||
1022 | thingasm('nada', 'Do nothing.', function () --[[ This function intentionally left blank. ]] end) | ||
1023 | |||
1024 | |||
1025 | |||
1026 | -- Widget wrappers. | ||
1027 | -- TODO - Fix this up so we don't need .W | ||
1028 | local widgets = {} | ||
1029 | --thingasm{widgets, 'window', 'The window.', types='userdata'} | ||
1030 | thingasm{widgets, 'W', 'Holds all the widgets', types='Keyed'} | ||
1031 | widgets.W{'Cwidget', 'The widget.', types='userdata'} | ||
1032 | widgets.W{'action,a', 'The action for the widget.', 'nada', types='string'} | ||
1033 | local aIsValid = function (self, parent) | ||
1034 | local result = Thing.isValid(self, parent) | ||
1035 | |||
1036 | if result then | ||
1037 | local value = parent[self.names[1] ] | ||
1038 | print('NEW ACTION - ' .. self.names[1] .. ' = ' .. value .. ' ' .. type(parent.Cwidget)) | ||
1039 | action(parent.Cwidget, value) | ||
1040 | end | ||
1041 | return result | ||
1042 | end | ||
1043 | |||
1044 | widgets.W{'look,l', 'The look of the widget.', types='string'} | ||
1045 | --[[ | ||
1046 | widgets.W{'colour,color,c', 'The colours of the widget.', types='table'} | ||
1047 | widgets.W.c{'red,r', 'The red colour of the widget.', 255, types='number'} | ||
1048 | widgets.W.c{'green,g', 'The green colour of the widget.', 255, types='number'} | ||
1049 | widgets.W.c{'blue,b', 'The blue colour of the widget.', 255, types='number'} | ||
1050 | widgets.W.c{'alpha,a', 'The alpha colour of the widget.', 255, types='number'} | ||
1051 | -- At this point we want to change widgets.W.c() to go to a different __call, coz right now it's going to the Mum.__call, which wraps thingasm. | ||
1052 | -- TODO - Keep an eye on this if we change to a single Mum, instead of the shallow copy we use now. | ||
1053 | local wMum = getmetatable(widgets.W.c) | ||
1054 | wMum.__call = function (func, ...) | ||
1055 | return Colour(func, ...) | ||
1056 | end | ||
1057 | ]] | ||
1058 | |||
1059 | window = function(w, h, title, name) | ||
1060 | name = name or 'myWindow' | ||
1061 | local win = {} | ||
1062 | win = copy(widgets, name) | ||
1063 | local wMum, wThingy = getStuffed(win.W, 'a') | ||
1064 | wThingy.isValid = aIsValid | ||
1065 | win.window = Cwindow(w, h, title, name) | ||
1066 | return win | ||
1067 | end | ||
1068 | |||
1069 | thingasm{'window', 'Specifies the size and title of the application Frame.', window, 'number,number,string', acl="GGG"} | ||
1070 | |||
1071 | |||
1072 | -- TODO - Some function stubs, for now. Fill them up later. | ||
1073 | skang = function (name) | ||
1074 | end | ||
1075 | |||
1076 | thingasm('skang', 'Parse the contents of a skang file or URL.', skang, 'URL') | ||
1077 | |||
1078 | |||
1079 | moduleEnd(_M) | ||
1080 | |||
1081 | end | ||
1082 | |||
1083 | -- Boss is the person that owns a Thing. | ||
1084 | |||
1085 | --[[ The original Skang parameters and commands. | ||
1086 | public final static String MY_USAGE[][] = | ||
1087 | { | ||
1088 | {"skinURL", "skinURL", "Y", "s", null, "URL of skin file.", "", "RI-"}, | ||
1089 | {"debug", "debug", "N", "", "0", "Set debugging level to :\n\t-1 - errors and warnings only (-q)\n\t0 - basic information\n\t1 - advanced information (-v)\n\t2 - trace functions\n\t3 - trace protocol\n\t4 - dump packets + stuff\n\t5 - detail", "", ""}, | ||
1090 | {"browser", "browser", "N", "", "mozilla %f", "Browser to run.", "", ""}, | ||
1091 | {"downloaddir", "downloadDir", "N", "", "download", "Download directory.", "", ""}, | ||
1092 | {"sessionID", "sessionID", "N", "", null, "SessionID from servlet.", "", ""}, | ||
1093 | {"JSESSIONID", "JSESSIONID", "N", "", null, "JSESSIONID from servlet engine.", "", ""}, | ||
1094 | {"servletpath", "servletPath", "N", "", "matrix_rad", "Servlet path.", "", ""}, | ||
1095 | {"servletport", "servletPort", "N", "", null, "Servlet port.", "", ""}, | ||
1096 | {"servletsslport", "servletSSLPort", "N", "", null, "Servlet SSL port.", "", ""}, | ||
1097 | {"HTML", "HTML", "N", "", "false", "Output to HTML?", "", ""}, | ||
1098 | {"PHP", "PHP", "N", "", "false", "Output though the PHP wrapper", "", ""}, | ||
1099 | {"inbrowser", "inBrowser", "N", "", "true", "Run in browser window?", "", ""}, | ||
1100 | {"SSL", "SSL", "N", "", null, "Dummy to avoid a web server bug.", "", ""}, | ||
1101 | {"NOSSL", "NOSSL", "N", "", null, "Dummy to avoid a web server bug.", "", ""}, | ||
1102 | {"corporate", "corporate", "N", "", null, "Are we doing corporate shit?", "", ""}, | ||
1103 | {"", "", "", "", "", "", "", ""} | ||
1104 | }; | ||
1105 | public final static String MY_SKANG[][] = | ||
1106 | { | ||
1107 | -- {"module", "addModule", "file,data", "Load a module.", "", ""}, | ||
1108 | {"append", "appendThing", "name,data", "Append to the current value of a Thing.", "", ""}, | ||
1109 | {"#!java", "bash", "name,name,name,name,name,name,name", "A not so clever unix script compatability hack.", "", ""}, | ||
1110 | {"pending", "pendingDoThing", "action", "Do an action when you are ready.", "", ""}, | ||
1111 | {"applet", "doIfApplet", "action", "Only do this if we are an applet.", "", ""}, | ||
1112 | {"application", "doIfApplication", "action", "Only do this if we are an application.", "", ""}, | ||
1113 | {"corporateshit", "doIfCorporateShit", "action", "Only do this if we are doing corporate shit.", "", ""}, | ||
1114 | {"realworld", "doIfRealWorld", "action", "Only do this if we are in the real world.", "", ""}, | ||
1115 | {"servlet", "doIfServlet", "action", "Only do this if we are a servlet.", "", ""}, | ||
1116 | {"do", "doThing", "action", "Do this action.", "", ""}, | ||
1117 | {"grab", "getFile", "URL", "Grab a file from a URL.", "", ""}, | ||
1118 | -- {"get", "getThing", "name", "Get the current value of an existing thing.", "", ""}, | ||
1119 | {"gimmeskin", "gimmeSkin", "", "Returns the modules default skin.", "", ""}, | ||
1120 | {"help", "helpThing", "file", "Show help page.", "", ""}, | ||
1121 | -- {"nada", "nothing", "data", "Does nothing B-).", "", ""}, | ||
1122 | {"postshow", "postShowThings", "URL,name", "POST the values of all Things to the URL, show the returned content.", "", ""}, | ||
1123 | {"post", "postThings", "URL", "POST the values of all Things to the URL, return the content.", "", ""}, | ||
1124 | {"postparse", "postParseThings", "URL", "POST the values of all Things to the URL, parse the returned content.", "", ""}, | ||
1125 | {"quiet", "quiet", "", "Output errors and warnings only.", "", ""}, | ||
1126 | {"remove", "removeThing", "name", "Remove an existing thing.", "", ""}, | ||
1127 | {"sethelp", "setHelp", "name,data", "Change the help for something.", "", ""}, | ||
1128 | -- {"set", "setThing", "name,data", "Set the current value of an existing Thing.", "", ""}, | ||
1129 | -- {"skang", "skangRead", "URL", "Parse the contents of a skang file or URL.", "", ""}, | ||
1130 | -- {"quit", "startQuit", "", "Quit, exit, remove thyself.", "", ""}, | ||
1131 | {"stopwhinging", "stopWhinging", "", "Clear all messages.", "", ""}, | ||
1132 | {"tell", "tellThing", "name", "Returns details of an existing Thing.", "", ""}, | ||
1133 | {"togglebug", "toggleIgnoreBrowserBug", "", "Toggle ignorance of a certain browser bug.", "", ""}, | ||
1134 | {"verbose", "verbose", "", "Output advanced information.", "", ""}, | ||
1135 | {"", "", "", "", "", ""} | ||
1136 | ]] | ||
1137 | |||
1138 | --[[ The original SkangAWT parameters and commands. | ||
1139 | public final static String MY_USAGE[][] = | ||
1140 | { | ||
1141 | {"", "", "", "", "", "", "", ""} | ||
1142 | }; | ||
1143 | public final static String MY_SKANG[][] = | ||
1144 | { | ||
1145 | {"taction", "tactionWidget", "name,action", "Set the alternative action for a widget.", "", ""}, | ||
1146 | {"action", "actionWidget", "name,action", "Set the action for a widget.", "", ""}, | ||
1147 | {"pane", "addPane", "name,x,y,w,h,data", "Add a pane to the current module.", "", ""}, | ||
1148 | {"widget", "addWidget", "name,type,lx,ly,lw,lh,data,data", "Add a widget to the current skin.", "", ""}, | ||
1149 | {"checkboxgroup", "checkBoxGroup", "number", "Make the next 'number' Checkboxes part of a check box group.", "", ""}, | ||
1150 | -- {"clear", "clearWidgets", "", "The current skin is cleared of all widgets.", "", ""}, | ||
1151 | {"colour", "colourWidget", "name,r,g,b,alpha,r,g,b,alpha", "Set widget's background and foreground colour.", "", "GGG"}, | ||
1152 | {"doaction", "doWidget", "name", "Do a widgets action.", "", "GGG"}, | ||
1153 | {"disable", "disableWidget", "name", "Disable a widget.", "", "GGG"}, | ||
1154 | {"enable", "enableWidget", "name", "Enable a widget.", "", "GGG"}, | ||
1155 | {"hide", "hideWidget", "name", "Hide a widget.", "", "GGG"}, | ||
1156 | {"hideall", "hideAllWidgets", "name,lx,ly,lw,lh", "Hide all widgets.", "", "GGG"}, | ||
1157 | {"look", "lookWidget", "name,normal,ghost,active,toggle", "Set the current look of an existing widget.", "", "GGG"}, | ||
1158 | {"mask", "maskWidget", "name,data", "Set the mask for a widget.", "", ""}, | ||
1159 | {"onmouse", "onMouse", "name,data", "Do something on mouse hover.", "", ""}, | ||
1160 | {"offmouse", "offMouse", "name,data", "Do something off mouse hover.", "", ""}, | ||
1161 | {"popup", "popupWidget", "name,data,data,data,data", "Create a popup.", "", "GGG"}, | ||
1162 | {"readonly", "readOnlyWidget", "name", "Make a widget read only.", "", "GGG"}, | ||
1163 | {"writeonly", "writeOnlyWidget", "name", "Make a widget write only.", "", "GGG"}, | ||
1164 | {"satori", "satori", "x,y", "Give me the developers menu.", "", "GGG"}, | ||
1165 | {"showloginwindow", "showLoginWindow", "", "Show user login window.", "", "GGG"}, | ||
1166 | {"show", "showWidget", "name", "Show a widget.", "", "GGG"}, | ||
1167 | -- {"window", "setSkangFrame", "x,y,name", "Specifies the size and title of the application Frame.", "", "GGG"}, | ||
1168 | {"stuff", "stuffWidget", "name,data", "Set the stuff for a widget's pane.", "", ""}, | ||
1169 | {"stufflet", "stuffWidget", "name,data,data", "Set the stufflet for a widget.", "", ""}, | ||
1170 | {"stufflist", "stuffListWidget", "name,data", "List the stuff in this widget.", "", ""}, | ||
1171 | {"stuffload", "stuffLoadWidget", "name,data,data", "Load the stuff for a widget.", "", ""}, | ||
1172 | {"stuffsave", "stuffSaveWidget", "name,data,data", "Save the stuff for a widget.", "", ""}, | ||
1173 | {"stuffdelete", "stuffDeleteWidget", "name,data,data", "Delete the stuff for a widget.", "", "SSS"}, | ||
1174 | {"stuffclear", "stuffClearWidget", "name,data", "Clear the stuff for a widget.", "", "SSS"}, | ||
1175 | {"rowtowidgets", "rowToWidgets", "name", "Copy Grid row to matching widgets.", "", ""}, | ||
1176 | {"widgetstorow", "widgetsToRow", "name,data", "Copy matching widgets to Grid row.", "", ""}, | ||
1177 | {"clearrow", "clearRow", "name", "Clear Grid row and matching widgets.", "", ""}, | ||
1178 | {"clearrowwidgets", "clearRowWidgets", "name", "Clear only the Grid row matching widgets.", "", ""}, | ||
1179 | {"", "", "", "", "", ""} | ||
1180 | }; | ||
1181 | ]] | ||
1182 | |||
1183 | |||
1184 | --[[ security package | ||
1185 | |||
1186 | Java skang could run as a stand alone applicion, as an applet in a web | ||
1187 | page, or as a servlet on a web server. This was pretty much all | ||
1188 | transparent to the user. The security system reflected that. Lua skang | ||
1189 | wont run in web pages, but can still have client / server behaviour. | ||
1190 | The general idea was, and still is, that the GUI is the client side (in | ||
1191 | web page, in extantz GUI) that sends values back to the server side | ||
1192 | (servlet, actual Lua package running as a separate process, or the world | ||
1193 | server for in world scripts). Client side can request that server side | ||
1194 | runs commands. Serevr side can send values and commands back to the | ||
1195 | client. Mostly it all happenes automatically through the ACLs. | ||
1196 | |||
1197 | Bouncer is the Java skang security manager, it extended the Java | ||
1198 | SecurityManager. Lua has no such thing, though C code running stuff in | ||
1199 | a sandbox does a similar job. Fascist is the Java security supervisor, | ||
1200 | again should go into the C sandbox. | ||
1201 | |||
1202 | Human is used for authenticating a human, Puter for authenticating a | ||
1203 | computer, Suits for corporate style authentication, and they all | ||
1204 | extended Who, the base authentication module. | ||
1205 | |||
1206 | For now, I have no idea how this all translates into Lua, but putting | ||
1207 | this here for a reminder to think about security during the design | ||
1208 | stage. | ||
1209 | |||
1210 | |||
1211 | This is the old Java ACL definition - | ||
1212 | acl - access control list. | ||
1213 | Owner is usually the person running the Thingspace. | ||
1214 | RWX~,---,Rwxgroup1,r--group2,r-xgroup3,rw-group4,--X~user1 | ||
1215 | rwx~ is for the owner. The second one is the default. The rest are per group or per user. | ||
1216 | Capital letters mean that they get access from the network to. | ||
1217 | --- No access at all. | ||
1218 | RWX Full access. | ||
1219 | R-- Read only access. | ||
1220 | r-x Read and execute, but only locally. | ||
1221 | rw- Read and write a field, but don't execute a method. | ||
1222 | -w- A password. | ||
1223 | -a- An append only log file. | ||
1224 | -A- An append only log file on the server. | ||
1225 | Ri- read, but only set from init (ei. skinURL not set from properties or skang files). | ||
1226 | RI- As above, but applet.init() can set it too. | ||
1227 | --x Thing is both method and field, only execution of the method is allowed. | ||
1228 | --p Run as owner (Pretend). | ||
1229 | --P Run across the network as owner (can run in applet triggered by server). | ||
1230 | s-- Read only, but not even visible to applets. | ||
1231 | sss Only visible to servlets and applications. | ||
1232 | --S Send to servlet to execute if applet, otherwise execute normally. | ||
1233 | S-- Read only, but ignore local version and get it from server. | ||
1234 | ggg GUI Thing, only visible to Applets and applications. | ||
1235 | GGG GUI Thing, but servlets can access them across the net. | ||
1236 | |||
1237 | For servlet only modules from an applet, the applet only loads the skanglet class, using it for all | ||
1238 | access to the module. | ||
1239 | |||
1240 | |||
1241 | Lua Security best practices - | ||
1242 | |||
1243 | From an email by Mike Pall - | ||
1244 | |||
1245 | "The only reasonably safe way to run untrusted/malicious Lua scripts is | ||
1246 | to sandbox it at the process level. Everything else has far too many | ||
1247 | loopholes." | ||
1248 | |||
1249 | http://lua-users.org/lists/lua-l/2011-02/msg01595.html | ||
1250 | http://lua-users.org/lists/lua-l/2011-02/msg01609.html | ||
1251 | http://lua-users.org/lists/lua-l/2011-02/msg01097.html | ||
1252 | http://lua-users.org/lists/lua-l/2011-02/msg01106.html | ||
1253 | |||
1254 | So that's processes, not threads like LuaProc does. B-( | ||
1255 | |||
1256 | However, security in depth is good, so still worthwhile looking at it from other levels as well. | ||
1257 | |||
1258 | General solution is to create a new environment, which we are doing | ||
1259 | anyway, then whitelist the safe stuff into it, instead of blacklisting | ||
1260 | unsafe stuff. Coz you never know if new unsafe stuff might exist. | ||
1261 | |||
1262 | Different between 5.1 (setfenv) and 5.2 (_ENV) | ||
1263 | |||
1264 | http://lua-users.org/wiki/SandBoxes - | ||
1265 | |||
1266 | ------------------------------------------------------ | ||
1267 | -- make environment | ||
1268 | local env = -- add functions you know are safe here | ||
1269 | { | ||
1270 | ipairs = ipairs, | ||
1271 | next = next, | ||
1272 | pairs = pairs, | ||
1273 | pcall = pcall, | ||
1274 | tonumber = tonumber, | ||
1275 | tostring = tostring, | ||
1276 | type = type, | ||
1277 | unpack = unpack, | ||
1278 | coroutine = { create = coroutine.create, resume = coroutine.resume, | ||
1279 | running = coroutine.running, status = coroutine.status, | ||
1280 | wrap = coroutine.wrap }, | ||
1281 | string = { byte = string.byte, char = string.char, find = string.find, | ||
1282 | format = string.format, gmatch = string.gmatch, gsub = string.gsub, | ||
1283 | len = string.len, lower = string.lower, match = string.match, | ||
1284 | rep = string.rep, reverse = string.reverse, sub = string.sub, | ||
1285 | upper = string.upper }, | ||
1286 | table = { insert = table.insert, maxn = table.maxn, remove = table.remove, | ||
1287 | sort = table.sort }, | ||
1288 | math = { abs = math.abs, acos = math.acos, asin = math.asin, | ||
1289 | atan = math.atan, atan2 = math.atan2, ceil = math.ceil, cos = math.cos, | ||
1290 | cosh = math.cosh, deg = math.deg, exp = math.exp, floor = math.floor, | ||
1291 | fmod = math.fmod, frexp = math.frexp, huge = math.huge, | ||
1292 | ldexp = math.ldexp, log = math.log, log10 = math.log10, max = math.max, | ||
1293 | min = math.min, modf = math.modf, pi = math.pi, pow = math.pow, | ||
1294 | rad = math.rad, random = math.random, sin = math.sin, sinh = math.sinh, | ||
1295 | sqrt = math.sqrt, tan = math.tan, tanh = math.tanh }, | ||
1296 | os = { clock = os.clock, difftime = os.difftime, time = os.time }, | ||
1297 | } | ||
1298 | |||
1299 | -- run code under environment [Lua 5.1] | ||
1300 | local function run(untrusted_code) | ||
1301 | if untrusted_code:byte(1) == 27 then return nil, "binary bytecode prohibited" end | ||
1302 | local untrusted_function, message = loadstring(untrusted_code) | ||
1303 | if not untrusted_function then return nil, message end | ||
1304 | setfenv(untrusted_function, env) | ||
1305 | return pcall(untrusted_function) | ||
1306 | end | ||
1307 | |||
1308 | -- run code under environment [Lua 5.2] | ||
1309 | local function run(untrusted_code) | ||
1310 | local untrusted_function, message = load(untrusted_code, nil, 't', env) | ||
1311 | if not untrusted_function then return nil, message end | ||
1312 | return pcall(untrusted_function) | ||
1313 | end | ||
1314 | ------------------------------------------------------ | ||
1315 | |||
1316 | Also includes a table of safe / unsafe stuff. | ||
1317 | |||
1318 | |||
1319 | While whitelisting stuff, could also wrap unsafe stuff to make them more safe. | ||
1320 | |||
1321 | print() -> should reroute to our output widgets. | ||
1322 | rawget/set() -> don't bypass the metatables, but gets tricky and recursive. | ||
1323 | require -> don't allow bypassing the sandbox to get access to restricted modules | ||
1324 | package.loaded -> ditto | ||
1325 | |||
1326 | |||
1327 | Other things to do - | ||
1328 | |||
1329 | debug.sethook() can be used to call a hook every X lines, which can help with endless loops and such. | ||
1330 | Have a custom memory allocater, like edje_lua2 does. | ||
1331 | |||
1332 | ------------------------------------------------------ | ||
1333 | ------------------------------------------------------ | ||
1334 | |||
1335 | The plan - | ||
1336 | |||
1337 | Process level - | ||
1338 | Have a Lua script runner C program / library. | ||
1339 | It does the LuaProc thing, and the edje_lua2 memory allocater thing. | ||
1340 | Other code feeds scripts to it via a pipe. | ||
1341 | Unless they are using this as a library. | ||
1342 | It can be chrooted, ulimited, LXC containered, etc. | ||
1343 | It has an internal watchdog thread. | ||
1344 | The truly paranoid can have a watchdog timer process watch it. | ||
1345 | Watches for a "new Lua state pulled off the queue" signal. | ||
1346 | This could be done from the App that spawned it. | ||
1347 | |||
1348 | It runs a bunch of worker threads, with a queue of ready Lua states. | ||
1349 | Each Lua state being run has lua_sethook() set to run each X lines, AND a watchdog timer set. | ||
1350 | If either is hit, then the Lua state is put back on the queue. | ||
1351 | (Currently LuaProc states go back an the queue when waiting for a "channel message", which does a lua_yeild().) | ||
1352 | NOTE - apparently "compiled code" bypasses hooks, though there's an undocumented LuaJIT compile switch for that. http://lua-users.org/lists/lua-l/2011-02/msg01106.html | ||
1353 | EFL is event based. | ||
1354 | LSL is event based. | ||
1355 | LuaSL is event based. | ||
1356 | Java skang is event based, with anything that has long running stuff overriding runBit(). | ||
1357 | Coz Java AWT is event based, the "events" are over ridden methods in each widget class. | ||
1358 | Should all work if we keep this as event based. | ||
1359 | An "event" is a bit of Lua script in a string, at Input trust level usually. | ||
1360 | Configurable for this script runner is - | ||
1361 | IP address & port, or pipe name. | ||
1362 | Security level to run at, defaults to Network. | ||
1363 | Number of worker threads, defaults to number of CPUs. | ||
1364 | Memory limit per Lua state. | ||
1365 | Lines & time per tick for Lua states. | ||
1366 | |||
1367 | Different levels of script trust - | ||
1368 | System - the local skang and similar stuff. | ||
1369 | -> No security at all. | ||
1370 | App - Lua scripts and C from the running application. | ||
1371 | -> Current module level security. | ||
1372 | Each has it's own environment, with no cross environment leakage. | ||
1373 | Runs in the Apps process, though that might be the script runner as a library. | ||
1374 | Or could be skang's main loop. | ||
1375 | Local - Lua scripts and skang files sent from the client apps running on the same system. | ||
1376 | -> As per App. | ||
1377 | Runs in a script runner, maybe? Or just the Apps script runner. | ||
1378 | Limit OS and file stuff, the client app can do those itself. | ||
1379 | Network - Lua scripts and skang files sent from the network. | ||
1380 | -> Runs in a script runner. | ||
1381 | Option to chroot it, ulimit it, etc. | ||
1382 | Heavily Lua sandboxed via environment. | ||
1383 | It can access nails, but via network derived credentials. | ||
1384 | Can have very limited local storage, not direct file access though, but via some sort of security layer. | ||
1385 | Can have network access. | ||
1386 | Can have GUI access, but only to it's own window. | ||
1387 | Config - Lua scripts run as configuration files. | ||
1388 | -> Almost empty local environment, can really only do math and set Things. | ||
1389 | Input - Lua scripts run as a result of hitting skang widgets on the other end of a socket. | ||
1390 | -> As per Config, but can include function calls. | ||
1391 | Also would need sanitizing, as this can come from the network. | ||
1392 | Microsoft - Not to be trusted at all. | ||
1393 | Apple - Don't expect them to trust us. | ||
1394 | |||
1395 | NOTE - edje_lua2 would be roughly Local trust level. | ||
1396 | |||
1397 | So we need to be able to pass Lua between processes - | ||
1398 | Have to be able to do it from C, from Lua, and from Lua embedded in C. | ||
1399 | edje_lua2 - uses Edje messages / signals. | ||
1400 | LuaSL - uses Ecore_Con, in this case a TCP port, even though it's only local. | ||
1401 | LuaSL mainloop for it's scripts is to basically wait for these messages from LuaProc. | ||
1402 | Which yield's until we get one. | ||
1403 | Eventually gets Lua code as a string -> loadstring() -> setfenv() -> pcall() | ||
1404 | The pcall returns a string that we send back as a LuaProc message. | ||
1405 | Extantz - we want to use stdin/stdout for the pipe, but otherwise LuaSL style semantics. | ||
1406 | |||
1407 | Hmm, Extantz could run external skang modules in two ways - | ||
1408 | Run the module as a separate app, with stdin/stdout. | ||
1409 | Require the module, and run it internally. | ||
1410 | Stuff like the in world editor and radar would be better as module, since they are useless outside Extantz? | ||
1411 | Radar is useless outside Extantz, editor could be used to edit a local file. | ||
1412 | Stuff like woMan would be better off as a separate application, so it can start and stop extantz. | ||
1413 | |||
1414 | ]] | ||
1415 | |||
1416 | |||
1417 | --[[ | ||
1418 | The main loop. | ||
1419 | A Lua skang module is basically a table, with skang managed fields. | ||
1420 | Once it calls moduleEnd(), it's expected to have finished. | ||
1421 | test.lua is currently an exception, it runs test stuff afterwards. | ||
1422 | So their code just registers Things and local variables. | ||
1423 | Some of those Things might be functions for manipulating internal module state. | ||
1424 | A C module has it's variables managed outside of it by Lua. | ||
1425 | So would have to go via LUA_GLOBALSINDEX to get to them. | ||
1426 | Unless they are userdata, which are C things anyway. | ||
1427 | Though it's functions are obviously inside the C module. | ||
1428 | Normal Lua module semantics expect them to return to the require call after setting themselves up. | ||
1429 | So test.lua is really an aberation. | ||
1430 | |||
1431 | Soooo, where does the main loop go? | ||
1432 | The skang module itself could define the main loop. | ||
1433 | Should not actually call it though, coz skang is itself a module. | ||
1434 | |||
1435 | In Java we had different entry points depending on how it was called. | ||
1436 | If it was called as an applet or application, it got it's own thread with a mainloop. | ||
1437 | That main loop just slept and called runBit() on all registered modules. | ||
1438 | If it was loaded as a module, it bypassed that stuff. | ||
1439 | I don't think Lua provides any such mechanism. | ||
1440 | In theory, the first call to moduleBegin would be the one that was started as an application. | ||
1441 | So we could capture that, and test against it in moduleEnd to find when THAT module finally got to the end. | ||
1442 | THEN we can start the main loop (test.lua being the exception that fucks this up). | ||
1443 | Though test.lua could include a runBits() that quits the main loop, then starts the main loop at the very end once more? | ||
1444 | The problem with this is applications that require skang type modules. | ||
1445 | The first one they require() isn't going to return. | ||
1446 | Eventually skang itself will get loaded. It looks at the "arg" global, which SHOULD be the command line. | ||
1447 | Including the name of the application, which we could use to double check. | ||
1448 | Extantz / script runner would set this arg global itself. | ||
1449 | |||
1450 | Skang applications have one main loop per app. | ||
1451 | Skang modules use the main loop of what ever app called them. | ||
1452 | Non skang apps have the option of calling skangs main loop. | ||
1453 | Extantz starts many external skang apps, which each get their own main loop. | ||
1454 | Extantz has it's own Ecore main loop. | ||
1455 | LuaSL still has one main loop per script. | ||
1456 | LSL scripts get their own main loop, but LSL is stupid and doesn't have any real "module" type stuff. | ||
1457 | |||
1458 | What does skang main loop actually do? | ||
1459 | In Java it just slept for a bit, then called runBit() from each module, and the only module that had one was watch. | ||
1460 | Actually better off using Ecore timers for that sort of thing. | ||
1461 | Skang main loop has nothing to do? lol | ||
1462 | Well, except for the "wait for LuaProc channel messages" thing. | ||
1463 | Widget main loop would be Ecore's main loop. | ||
1464 | |||
1465 | Extantz loads a skang module. | ||
1466 | Module runs in extantz script runner. | ||
1467 | Module runs luaproc message main loop from skang. | ||
1468 | Evas / Elm widgets send signals, call C callbacks. | ||
1469 | Extantz sends Lua input scripts via luaproc messages to module. | ||
1470 | Extantz runs separate skang module. | ||
1471 | Module runs in it's own process. | ||
1472 | Module runs it's own message loop on the end of stdin. | ||
1473 | Evas / Elm widgets send signals, call C callbacks. | ||
1474 | Extantz sends Lua Input scripts to module via stdout. | ||
1475 | Module runs stand alone. | ||
1476 | Module runs in it's own process. | ||
1477 | Module has to have widget start Ecore's main loop. | ||
1478 | Module runs it's own message loop, waiting for widget to send it messages. | ||
1479 | Evas / Elm widgets send signals, call C callbacks. | ||
1480 | Widget sends Lua Input scripts to module. | ||
1481 | |||
1482 | Alternate plan - | ||
1483 | Skang has no main loop, modules are just tables. | ||
1484 | OK, so sometimes skang has to start up an Ecore main loop. | ||
1485 | With either Ecore_Con, or Evas and Elm. | ||
1486 | skang.message(string) | ||
1487 | Call it to pass a bit of Lua to skang, which is likely Input. | ||
1488 | Widget twiddles happen in Ecore's main loop, via signals and call backs. | ||
1489 | Eventually these call backs hit the widgets action string. | ||
1490 | Send that action string to skang.message(). | ||
1491 | |||
1492 | Extantz loads a skang module. | ||
1493 | Extantz has a skang Lua state. | ||
1494 | Module is just another table in skang. | ||
1495 | Widget -> callback -> action string -> skang.message() | ||
1496 | Extantz runs separate skang module. | ||
1497 | Skang module C code runs an Ecore main loop. | ||
1498 | Module is just another table in skang. | ||
1499 | Skang C uses Ecore_Con to get messages from Extantz' Ecore_Con. | ||
1500 | Widget -> callback -> action string -> Ecore_Con -> skang.message() | ||
1501 | OOORRRR .... | ||
1502 | Skang runs a main loop reading stdin. | ||
1503 | Widget -> callback -> action string -> stdout -> skang.message() | ||
1504 | Module runs stand alone. | ||
1505 | Skang module C code runs an Ecore main loop. | ||
1506 | Module is just another table in skang. | ||
1507 | Widget -> callback -> action string -> skang.message() | ||
1508 | Extantz loads a skang module from the network. | ||
1509 | Skang module runs on the server with it's own Ecore main loop somehow. | ||
1510 | Module is just another table in skang. | ||
1511 | Extantz uses Ecore_Con to get messages from Extantz' Ecore_Con, via TCP. | ||
1512 | Widget -> callback -> action string -> Ecore_Con -> skang.message() | ||
1513 | OOORRRR .... | ||
1514 | Remember, these are untrusted, so that's what the script runner is for. | ||
1515 | Skang module runs in the script runner, with some sort of luaproc interface. | ||
1516 | Widget -> callback -> action string -> Ecore_Con -> luaproc -> skang.message() | ||
1517 | |||
1518 | Skang running as a web server. | ||
1519 | Skang runs an Ecore main loop. | ||
1520 | Skang has an Ecore_Con server attached to port 80. | ||
1521 | Skang loads modules as usual. | ||
1522 | Skang responds to specific URLs with HTML forms generated from Skang files. | ||
1523 | Skang gets post data back, which kinda looks like Input. B-) | ||
1524 | |||
1525 | Extantz runs a LuaSL / LSL script from the network. | ||
1526 | Send this one to the LuaSL script runner. | ||
1527 | Coz LSL scripts tend to have lots of copies with the same name in different objects. | ||
1528 | Could get too huge to deal with via "just another table". | ||
1529 | In this case, compiling the LSL might be needed to. | ||
1530 | |||
1531 | ]] | ||
1532 | |||
1533 | |||
1534 | --[[ TODO | ||
1535 | NOTE that skang.thingasm{} doesn't care what other names you pass in, they all get assigned to the Thing. | ||
1536 | |||
1537 | |||
1538 | Widget - | ||
1539 | Should include functions for actually dealing with widgets, plus a way | ||
1540 | of creating widgets via introspection. Should also allow access to | ||
1541 | widget internals via table access. Lua code could look like this - | ||
1542 | |||
1543 | foo = widget('label', 0, "0.1", 0.5, 0, 'Text goes here :") | ||
1544 | -- Method style. | ||
1545 | foo:colour(255, 255, 255, 0, 0, 100, 255, 0) | ||
1546 | foo:hide() | ||
1547 | foo:action("skang.load(some/skang/file.skang)") | ||
1548 | -- Table style. | ||
1549 | foo.action = "skang.load('some/skang/file.skang')" | ||
1550 | foo.colour.r = 123 | ||
1551 | foo.look('some/edje/file/somewhere.edj') | ||
1552 | foo.help = 'This is a widget for labelling some foo.' | ||
1553 | |||
1554 | Widgets get a type as well, which would be label, button, edit, grid, etc. | ||
1555 | A grid could even have sub types - grid,number,string,button,date. | ||
1556 | A required widget might mean that the window HAS to have one. | ||
1557 | Default for a widget could be the default creation arguments - '"Press me", 1, 1, 10, 50'. | ||
1558 | |||
1559 | skang.thingasm{'myButton', types='Button', rectangle={1, 1, 10, 50}, title='Press me', ...} | ||
1560 | |||
1561 | skang.thingasm('foo,s,fooAlias', 'Foo is a bar, not the drinking type.', function () print('foo') end, '', '"button", "The foo :"' 1, 1, 10, 50') | ||
1562 | myButton = skang.widget('foo') -- Gets the default widget creation arguments. | ||
1563 | myButton:colour(1, 2, 3, 4) | ||
1564 | -- Use generic positional / named arguments for widget to, then we can do - | ||
1565 | myEditor = skang.widget{'foo', "edit", "Edit foo :", 5, 15, 10, 100, look='edit.edj', colour={blue=20}, action='...'} | ||
1566 | -- Using the Thing alias stuff, maybe we can do the "first stage tokenise" step after all - | ||
1567 | myEditor = skang.widget{'foo', "edit", "Edit foo :", 5, 15, 10, 100, l='edit.edj', c={b=20}, a='...'} | ||
1568 | myEditor:colour(1, 2, 3, 4, 5, 6, 7, 8) | ||
1569 | myButton = 'Not default' -- myEditor and foo change to. Though now foo is a command, not a parameter, so maybe don't change that. | ||
1570 | -- Though the 'quit' Thing could have a function that does quitting, this is just an example of NOT linking to a Thing. | ||
1571 | -- If we had linked to this theoretical 'quit' Thing, then pushing that Quit button would invoke it's Thing function. | ||
1572 | quitter = skang.widget(nil, 'button', 'Quit', 0.5, 0.5, 0.5, 0.5) | ||
1573 | quitter:action('quit') | ||
1574 | |||
1575 | For widgets with "rows", which was handled by Stuff in skang, we could | ||
1576 | maybe use the Lua concat operator via metatable. I think that works by | ||
1577 | having the widget (a table) on one side of the concat or the other, and | ||
1578 | the metatable function gets passed left and right sides, then must | ||
1579 | return the result. Needs some experimentation, but it might look like | ||
1580 | this - | ||
1581 | |||
1582 | this.bar = this.bar .. 'new choice' | ||
1583 | this.bar = 'new first choice' .. this.bar | ||
1584 | |||
1585 | |||
1586 | coordinates and sizes - | ||
1587 | |||
1588 | Originally skang differentiated between pixels and character cells, | ||
1589 | using plain integers to represent pixels, and _123 to represent | ||
1590 | character cells. The skang TODO wanted to expand that to percentages | ||
1591 | and relative numbers. We can't use _123 in Lua, so some other method | ||
1592 | needs to be used. Should include those TODO items in this new design. | ||
1593 | |||
1594 | Specifying character cells should be done as strings - "123" | ||
1595 | |||
1596 | Percentages can be done as small floating point numbers between 0 and 1, | ||
1597 | which is similar to Edje. Since Lua only has a floating point number | ||
1598 | type, both 0 and 1 should still represent pixels / character cells - | ||
1599 | |||
1600 | 0.1, 0.5, "0.2", "0.9" | ||
1601 | |||
1602 | Relative numbers could be done as strings, with the widget to be | ||
1603 | relative to, a + or -, then the number. This still leaves the problem | ||
1604 | of telling if the number is pixels or character cells. Also, relative | ||
1605 | to what part of the other widget? Some more thought needs to be put | ||
1606 | into this. | ||
1607 | |||
1608 | Another idea for relative numbers could be to have a coord object with | ||
1609 | various methods, so we could have something like - | ||
1610 | |||
1611 | button:bottom(-10):right(5) -- 10 pixels below the bottom of button, 5 pixels to the right of the right edge of button. | ||
1612 | button:width("12") -- 12 characters longer than the width of button. | ||
1613 | |||
1614 | But then how do we store that so that things move when the thing they are | ||
1615 | relative to moves? | ||
1616 | |||
1617 | |||
1618 | Squeal - | ||
1619 | Squeal was the database driver interface for SquealStuff, the database | ||
1620 | version of Stuff. Maybe we could wrap esskyuehl? Not really in need of | ||
1621 | database stuff for now, but should keep it in mind. | ||
1622 | For SquealStuff, the metadata would be read from the SQL database automatically. | ||
1623 | |||
1624 | squeal.database('db', 'host', 'someDb', 'user', 'password') -> Should return a Squeal Thing. | ||
1625 | local db = require 'someDbThing' -> Same as above, only the database details are encodode in the someDbThing source, OR come from someDbThing.properties. | ||
1626 | db:getTable('stuff', 'someTable') -> Grabs the metadata, but not the rows. | ||
1627 | db:read('stuff', 'select * from someTable'} -> Fills stuff up with several rows, including setting up the metadata for the columns. | ||
1628 | stuff[1].field1 -> Is a Thing, with a proper metatable, that was created automatically from the database meta data. | ||
1629 | stuff:read('someIndex') -> Grabs a single row that has the key 'someIndex', or perhaps multiple rows since this might have SQL under it. | ||
1630 | stuff = db:read('stuff', 'select * from someTable where key='someIndex') | ||
1631 | |||
1632 | stuff:write() -> Write all rows to the database table. | ||
1633 | stuff:write(1) -> Write one row to the database table. | ||
1634 | stuff:stuff('field1').isValid = someFunction -- Should work, all stuff[key] share the same Thing description. | ||
1635 | stuff:isValid(db) -> Validate the entire Thing against it's metadata at least. | ||
1636 | window.stuff = stuff -> Window gets stuff as it's default 'Keyed' table, any widgets with same names as the table fields get linked. | ||
1637 | grid.stuff = stuff -> Replace contents of this grid widget with data from all the rows in stuff. | ||
1638 | choice.stuff = stuff -> As in grid, but only using the keys. | ||
1639 | widget.stuff = stuff:stuff('field1') -> This widget gets a particular stufflet. | ||
1640 | widget would have to look up getmetatable(window.stuff).parent. Or maybe this should work some other way? | ||
1641 | |||
1642 | In all these cases above, stuff is a 'Keyed' table that has a Thing metatable, so it has sub Things. | ||
1643 | Should include some way of specifyings details like key name, where string, etc. | ||
1644 | getmetatable(stuff).__keyName | ||
1645 | getmetatable(stuff).__squeal.where | ||
1646 | And a way to link this database table to others, via the key of the other, as a field in this Stuff. | ||
1647 | stuff:stuff('field0').__link = {parent, key, index} | ||
1648 | In Java we had this - | ||
1649 | |||
1650 | public class PersonStuff extends SquealStuff | ||
1651 | { | ||
1652 | |||
1653 | ... | ||
1654 | |||
1655 | public final static String FULLNAME = "fullname"; | ||
1656 | |||
1657 | public static final String keyField = "ID"; // Name of key field/s. | ||
1658 | public static final String where = keyField + "='%k'"; | ||
1659 | public static final String listName = "last"; | ||
1660 | public static final String tables = "PEOPLE"; | ||
1661 | public static final String select = null; | ||
1662 | public static final String stufflets[] = | ||
1663 | { | ||
1664 | keyField, | ||
1665 | "PASSWD_ID|net.matrix_rad.squeal.PasswdStuff|,OTHER", | ||
1666 | "QUALIFICATION_IDS|net.matrix_rad.people.QualificationStuff|,OTHER", | ||
1667 | "INTERESTING_IDS|net.matrix_rad.people.InterestingStuff|,OTHER", | ||
1668 | "title", | ||
1669 | "first", | ||
1670 | "middle", | ||
1671 | "last", | ||
1672 | "suffix", | ||
1673 | |||
1674 | ... | ||
1675 | |||
1676 | FULLNAME + "||,VARCHAR,512" | ||
1677 | }; | ||
1678 | } | ||
1679 | |||
1680 | ]] | ||
1681 | |||
1682 | |||
1683 | -- Gotta check out this _ENV thing, 5.2 only. Seems to replace the need for setfenv(). Seems like setfenv should do what we want, and is more backward compatible. | ||
1684 | -- "_ENV is not supported directly in 5.1, so its use can prevent a module from remaining compatible with 5.1. | ||
1685 | -- Maybe you can simulate _ENV with setfenv and trapping gets/sets to it via __index/__newindex metamethods, or just avoid _ENV." | ||
1686 | -- LuaJIT doesn't support _ENV anyway. | ||
diff --git a/src/GuiLua/test.lua b/src/GuiLua/test.lua new file mode 100644 index 0000000..705f7ad --- /dev/null +++ b/src/GuiLua/test.lua | |||
@@ -0,0 +1,211 @@ | |||
1 | -- Wrapping the entire module in do .. end helps if people just join a bunch of modules together, which apparently is popular. | ||
2 | -- By virtue of the fact we are stuffing our result into package.loaded[], just plain running this works as "loading the module". | ||
3 | do -- Only I'm not gonna indent this. | ||
4 | |||
5 | local skang = require 'skang' | ||
6 | local _M = skang.moduleBegin('test', nil, 'Copyright 2014 David Seikel', '0.1', '2014-03-27 03:57:00', [[ | ||
7 | local win = skang.window(500, 500, "G'day planet.", 'testWindow') | ||
8 | skang.thingasm{win, 'quitter', 'Quits the skang window', types = 'widget', widget='"button", "Quit", 10, 10, 100, 30'} | ||
9 | win.W.quitter.action = 'skang.quit()' -- TODO Should look it up in ThingSpace.commands, and translat 'quit' into the Lua 'skang.quit()'? | ||
10 | ]]) | ||
11 | |||
12 | print('code') | ||
13 | |||
14 | -- A variable that is private to this module. | ||
15 | local fool = 22 | ||
16 | |||
17 | -- TODO - Could have a table of tables, and ipair through the top level, passing the inner ones to skang.thingasm{}. | ||
18 | |||
19 | skang.thingasm{'fooble,f', 'Help text goes here', 1, widget='"edit", "The fooble:", 1, 1, 10, 50', required=true} | ||
20 | skang.thingasm('bar', 'Help text', "Default") | ||
21 | skang.thingasm('foo') | ||
22 | |||
23 | -- We can use inline functions if we don't need the function internally. | ||
24 | skang.thingasm('ffunc', 'Help Text', function (arg1, arg2) | ||
25 | print('Inside test.ffunc(' .. arg1 .. ', ' .. arg2 .. ')') | ||
26 | end, 'number,string') | ||
27 | |||
28 | print('Ending soon') | ||
29 | skang.moduleEnd(_M) | ||
30 | |||
31 | end | ||
32 | |||
33 | |||
34 | -- Test it. | ||
35 | local skang = require 'skang' | ||
36 | local test = require 'test' | ||
37 | local test_c = require 'test_c' | ||
38 | local copy = skang.copy(test, 'copy') | ||
39 | |||
40 | print('End ' .. test.bar .. ' ' .. test.VERSION .. ' ' .. skang.get(test, 'ffunc', 'help') .. ' ->> ' .. skang.get(test, 'f', 'action')) | ||
41 | |||
42 | print('') | ||
43 | |||
44 | print('foo = ' .. test.foo .. ' ->> ' .. skang.get(test, 'foo', 'help')) | ||
45 | print('fooble = ' .. test.fooble) | ||
46 | print('cfooble = ' .. test_c.cfooble .. ' ->> ' .. skang.get(test_c, 'cfooble', 'help') .. ' [' .. skang.get(test_c, 'cfooble', 'widget') .. ']') | ||
47 | print('cfunc ->> ' .. skang.get(test_c, 'cfunc', 'help')) | ||
48 | test.ffunc('one', 2) | ||
49 | test_c.cfunc(0, 'zero') | ||
50 | print('') | ||
51 | |||
52 | test.f = 42 | ||
53 | print('f is now ' .. test.fooble .. ' ' .. test.f) | ||
54 | print('copy_f is now ' .. copy.fooble .. ' ' .. copy.f) | ||
55 | copy.f = 24 | ||
56 | print('f is now ' .. test.fooble .. ' ' .. test.f) | ||
57 | print('copy_f is now ' .. copy.fooble .. ' ' .. copy.f) | ||
58 | test.f = nil | ||
59 | print('f is now ' .. test.fooble .. ' ' .. test.f) | ||
60 | test.fooble = 42 | ||
61 | print('f is now ' .. test.fooble .. ' ' .. test.f) | ||
62 | test.fooble = nil | ||
63 | print('f is now ' .. test.fooble .. ' ' .. test.f) | ||
64 | print('') | ||
65 | |||
66 | print(skang.isBoolean(true)) | ||
67 | print(skang.isBoolean(1)) | ||
68 | print(skang.isBoolean('1')) | ||
69 | print(skang.isBoolean('true')) | ||
70 | print(skang.isBoolean('Yep')) | ||
71 | print(skang.isBoolean('?')) | ||
72 | print(skang.isBoolean(test)) | ||
73 | print(skang.isBoolean(function (a) return true end)) | ||
74 | print('') | ||
75 | print(skang.isBoolean(false)) | ||
76 | print(skang.isBoolean(nil)) | ||
77 | print(skang.isBoolean(0)) | ||
78 | print(skang.isBoolean('')) | ||
79 | print(skang.isBoolean('0')) | ||
80 | print(skang.isBoolean('false')) | ||
81 | print(skang.isBoolean('Nope')) | ||
82 | print(skang.isBoolean(function (a) return false end)) | ||
83 | print('') | ||
84 | |||
85 | -- Make it required, even though it was anyway. | ||
86 | skang.set(test, 'f', 'required', true) | ||
87 | -- Disable the default value, so we see "is required" errors. | ||
88 | skang.reset(test, 'f', 'default') | ||
89 | test.fooble = 42 | ||
90 | test.fooble = 'Should fail.' | ||
91 | test.fooble = 42 | ||
92 | test.fooble = nil | ||
93 | test.fooble = 42 | ||
94 | test.fooble = true | ||
95 | test.f = 42 | ||
96 | test.f = nil | ||
97 | test.bar = 123 | ||
98 | print('') | ||
99 | |||
100 | skang.set(test, 'f', 'required', false) | ||
101 | test.f = 42 | ||
102 | test.f = nil | ||
103 | skang.set(test, 'f', 'default', 999) | ||
104 | test.f = 42 | ||
105 | test.f = nil | ||
106 | print(test.fooble .. ' ' .. test.f) | ||
107 | print(skang.get(test, 'f', 'default')) | ||
108 | print('') | ||
109 | |||
110 | local stuff = {} | ||
111 | stuff.t = {} | ||
112 | |||
113 | skang.thingasm{stuff, 'a', 'A test stufflet'} | ||
114 | skang.thingasm{stuff.t, 'b', 'A sub stufflet'} | ||
115 | skang.thingasm{stuff.t, 'c', 'Another sub stufflet'} | ||
116 | skang.thingasm{stuff, 's', 'A Stuff', types='table'} | ||
117 | stuff.s{'sa,a', 'A stufflet in a Stuff'} | ||
118 | stuff.s{'sb,b', 'Another stufflet in a Stuff'} | ||
119 | skang.thingasm{stuff, 'S', 'A database table of Stuff', types='Keyed'} | ||
120 | stuff.S{'field0', 'The first field of the db table.'} | ||
121 | stuff.S{'field1', 'The second field of the db table.'} | ||
122 | |||
123 | print('*********************************') | ||
124 | skang.fixNames(skang, 'skang') | ||
125 | skang.fixNames(test, 'test') | ||
126 | skang.fixNames(test_c, 'test_c') | ||
127 | skang.fixNames(stuff, 'stuff') | ||
128 | print('*********************************') | ||
129 | |||
130 | print(skang.get(stuff, 'a', 'help')) | ||
131 | print(skang.get(stuff.t, 'b', 'help')) | ||
132 | print(skang.get(stuff.t, 'c', 'help')) | ||
133 | print(skang.get(stuff, 's', 'help')) | ||
134 | print(skang.get(stuff.s, 'sa', 'help')) | ||
135 | print(skang.get(stuff.s, 'sb', 'help')) | ||
136 | print(skang.get(stuff.S, 'field0', 'help')) | ||
137 | print(skang.get(stuff.S, 'field1', 'help')) | ||
138 | skang.thingasm{test, 'baz,b', 'A test stufflet for test'} | ||
139 | print(skang.get(test, 'b', 'help')) | ||
140 | print(skang.get(test, 'f', 'help')) | ||
141 | -- Should fail isValid() | ||
142 | stuff.a = 1 | ||
143 | stuff.t.b = '2' | ||
144 | stuff.t.c = '3' | ||
145 | test.b = '422222' | ||
146 | test.f = 5 | ||
147 | test_c.cbar = '666' | ||
148 | -- This one doesn't actually exist. | ||
149 | test_c.bar = '7' | ||
150 | stuff.s.sa = true | ||
151 | stuff.s.sb = 22 | ||
152 | stuff.s.b = 33 | ||
153 | print('') | ||
154 | -- TODO - This triggers isValid() twice for each table element. | ||
155 | stuff.s = {a=8, sb='9'} | ||
156 | print('') | ||
157 | stuff.s.sb = 99 | ||
158 | -- NOTE - Yet this doesn't trigger isValid() twice. | ||
159 | stuff.S['record0'] = {field0=0, field1='zero'} | ||
160 | stuff.S['record1'] = {field0='1', field1='one'} | ||
161 | stuff.S['record2'] = {field0='2', field1='two'} | ||
162 | |||
163 | print('') | ||
164 | |||
165 | print(skang.get(stuff, 'a')) | ||
166 | print(skang.get(stuff.t, 'b')) | ||
167 | print(skang.get(stuff.t, 'c')) | ||
168 | print(skang.get(test, 'b')) | ||
169 | print(skang.get(test, 'baz')) | ||
170 | print(skang.get(test, 'f')) | ||
171 | print(skang.get(test, 'fooble')) | ||
172 | print(skang.get(test_c, 'cbar')) | ||
173 | print(skang.get(test_c, 'bar')) | ||
174 | print(type(skang.get(stuff, 's'))) | ||
175 | print(skang.get(stuff.s, 'sa')) | ||
176 | print(skang.get(stuff.s, 'sb')) | ||
177 | print('') | ||
178 | |||
179 | print(stuff.a) | ||
180 | print(stuff.t.b) | ||
181 | print(stuff.t.c) | ||
182 | print(test.b) | ||
183 | print(test.baz) | ||
184 | print(test.f) | ||
185 | print(test.fooble) | ||
186 | print(test_c.cbar) | ||
187 | print(test_c.bar) | ||
188 | print(test_c.c) | ||
189 | print(test_c.cfooble) | ||
190 | print(stuff.s.sa) | ||
191 | print(stuff.s.sb) | ||
192 | print('') | ||
193 | |||
194 | --skang.printTableStart(stuff.s, '', 'stuff.s') | ||
195 | --skang.printTableStart(stuff.S, '', 'stuff.S') | ||
196 | --skang.printTableStart(getmetatable(stuff.S), '', 'stuff.S metatable') | ||
197 | |||
198 | print(stuff.S['record0'].field1) | ||
199 | print(stuff.S['record1'].field0) | ||
200 | print(stuff.S['record2'].field1) | ||
201 | |||
202 | --skang.printTableStart(stuff.S['record0'], '', 'stuff.S[record0]') | ||
203 | --skang.printTableStart(getmetatable(stuff.S['record0']), '', 'metatable stuff.S[record0]') | ||
204 | --skang.printTableStart(getmetatable(stuff.S['record1']), '', 'metatable stuff.S[record1]') | ||
205 | --skang.printTableStart(getmetatable(stuff.S['record2']), '', 'metatable stuff.S[record2]') | ||
206 | |||
207 | --skang.printTableStart(getmetatable(stuff.s), '', 'stuff.s metatable') | ||
208 | --skang.printTableStart(getmetatable(stuff), '', 'stuff metatable') | ||
209 | --skang.printTableStart(getmetatable(stuff.S), '', 'stuff.S metatable') | ||
210 | |||
211 | --skang.printTableStart(getmetatable(test), '', 'test metatable') | ||
diff --git a/src/GuiLua/test.properties b/src/GuiLua/test.properties new file mode 100644 index 0000000..71a371d --- /dev/null +++ b/src/GuiLua/test.properties | |||
@@ -0,0 +1 @@ | |||
fooble = 'forty two' | |||
diff --git a/src/GuiLua/test.sh b/src/GuiLua/test.sh new file mode 100755 index 0000000..1effdb3 --- /dev/null +++ b/src/GuiLua/test.sh | |||
@@ -0,0 +1,3 @@ | |||
1 | #! /bin/bash | ||
2 | |||
3 | ./skang -l test -foo "argy bargy" | ||
diff --git a/src/GuiLua/test.skang b/src/GuiLua/test.skang new file mode 100644 index 0000000..10382b5 --- /dev/null +++ b/src/GuiLua/test.skang | |||
@@ -0,0 +1,14 @@ | |||
1 | #!/usr/bin/env skang -l test -- Lua allows this shell hack. | ||
2 | |||
3 | -- There's an implied local skang = require 'skang' | ||
4 | -- There's an implied local test = require 'test' | ||
5 | |||
6 | -- This is a bit more verbose than I wanted. lol | ||
7 | local win = skang.window(500, 500, "G'day planet.", 'testWindow') | ||
8 | skang.thingasm{win, 'quitter', 'Quits the skang window', types = 'widget', widget='"button", "Quit", 10, 10, 100, 30'} | ||
9 | win.W.quitter.action = 'skang.quit()' -- TODO Should look it up in ThingSpace.commands, and translate 'quit' into the Lua 'skang.quit()'? | ||
10 | |||
11 | skang.thingasm{win, 'ffuncer', 'Calls ffunc', types = 'widget', widget='"button", "ffunc()", 10, 40, 100, 30', action='test.ffunc(3, 4)'} | ||
12 | |||
13 | test.bar = 'things' | ||
14 | test.ffunc(1, 'two') | ||
diff --git a/src/GuiLua/test_c.c b/src/GuiLua/test_c.c new file mode 100644 index 0000000..5328bda --- /dev/null +++ b/src/GuiLua/test_c.c | |||
@@ -0,0 +1,88 @@ | |||
1 | /* Should be a Lua skang module, roughly the same as test.lua | ||
2 | |||
3 | Seems to be several problems with linking in various OSes, here's some | ||
4 | possibly helpful links - | ||
5 | |||
6 | http://lua.2524044.n2.nabble.com/C-Lua-modules-not-compatible-with-every-Lua-interpreter-td7647522.html | ||
7 | http://lua-users.org/wiki/LuaProxyDllFour | ||
8 | http://stackoverflow.com/questions/11492194/how-do-you-create-a-lua-plugin-that-calls-the-c-lua-api?rq=1 | ||
9 | http://lua-users.org/lists/lua-l/2008-01/msg00671.html | ||
10 | */ | ||
11 | |||
12 | |||
13 | #include "GuiLua.h" | ||
14 | |||
15 | |||
16 | static const char *ourName = "test_c"; | ||
17 | int skang, _M; | ||
18 | |||
19 | static int cfunc (lua_State *L) | ||
20 | { | ||
21 | double arg1 = luaL_checknumber(L, 1); | ||
22 | const char *arg2 = luaL_checkstring(L, 2); | ||
23 | |||
24 | printf("Inside %s.cfunc(%f, %s)\n", ourName, arg1, arg2); | ||
25 | return 0; | ||
26 | } | ||
27 | |||
28 | /* local test_c = require 'test_c' | ||
29 | |||
30 | Lua's require() function will strip any stuff from the front of the name | ||
31 | separated by a hyphen, so 'ClientHamr-GuiLua-test_c' -> 'test_c'. Then | ||
32 | it will search through a path, and eventually find this test_c.so (or | ||
33 | test_c.dll or whatever), then call luaopen_test_c(), which should return | ||
34 | a table. The argument (only thing on the stack) for this function will | ||
35 | be 'test_c'. | ||
36 | |||
37 | Normally luaL_register() creates a table of functions, that is the table | ||
38 | returned, but we want to do something different with skang. | ||
39 | */ | ||
40 | int luaopen_test_c(lua_State *L) | ||
41 | { | ||
42 | // In theory, the only thing on the stack now is 'test_c' from the require() call. | ||
43 | |||
44 | // pseudo-indices, special tables that can be accessed like the stack - | ||
45 | // LUA_GLOBALSINDEX - thread environment, where globals are | ||
46 | // LUA_ENVIRONINDEX - C function environment, in this case luaopen_test_c() is the C function | ||
47 | // LUA_REGISTRYINDEX - C registry, global, for unique keys use the module name as a string, or a lightuserdata address to a C object in our module. | ||
48 | // lua_upvalueindex(n) - C function upvalues | ||
49 | |||
50 | // The only locals we care about are skang and _M. | ||
51 | // All modules go into package.loaded[name] as well. | ||
52 | // skang is essentially a global anyway. | ||
53 | // _M we pass back as the result, and our functions get added to it by skang.thingasm() | ||
54 | // Not entirely true, _M is a proxy table, getmetatable(_M).__values[cfunc] would be our function. | ||
55 | |||
56 | // local skang = require 'skang' | ||
57 | lua_getglobal(L, "require"); | ||
58 | lua_pushstring(L, SKANG); | ||
59 | lua_call(L, 1, 1); | ||
60 | lua_setfield(L, LUA_REGISTRYINDEX, SKANG); | ||
61 | lua_getfield(L, LUA_REGISTRYINDEX, SKANG); | ||
62 | skang = lua_gettop(L); | ||
63 | |||
64 | // local _M = skang.moduleBegin('test_c', nil, 'Copyright 2014 David Seikel', '0.1', '2014-03-27 03:57:00', nil, false) | ||
65 | push_lua(L, "@ ( $ ~ $ $ $ ~ ! )", skang, MODULEBEGIN, ourName, "Copyright 2014 David Seikel", "0.1", "2014-03-27 03:57:00", 0, 1); | ||
66 | lua_setfield(L, LUA_REGISTRYINDEX, ourName); | ||
67 | lua_getfield(L, LUA_REGISTRYINDEX, ourName); | ||
68 | _M = lua_gettop(L); | ||
69 | |||
70 | // This uses function{} style. | ||
71 | // skang.thingasm{_M, 'cfooble,c', 'cfooble help text', 1, widget=\"'edit', 'The cfooble:', 1, 1, 10, 50\", required=true} | ||
72 | push_lua(L, "@ ( { = $ $ % $widget !required } )", skang, THINGASM, _M, "cfooble,c", "cfooble help text", 1, "'edit', 'The cfooble:', 1, 1, 10, 50", 1, 0); | ||
73 | |||
74 | // skang.thing(_M, 'cbar', 'Help text', 'Default') | ||
75 | push_lua(L, "@ ( = $ $ $ )", skang, THINGASM, _M, "cbar", "Help text", "Default", 0); | ||
76 | |||
77 | // skang.thingasm(_M, 'cfoo') | ||
78 | push_lua(L, "@ ( = $ )", skang, THINGASM, _M, "cfoo", 0); | ||
79 | |||
80 | // skang.thingasm(_M, 'cfunc', 'cfunc does nothing really', cfunc, 'number,string') | ||
81 | push_lua(L, "@ ( = $ $ & $ )", skang, THINGASM, _M, "cfunc", "cfunc does nothing really", cfunc, "number,string", 0); | ||
82 | |||
83 | // skang.moduleEnd(_M) | ||
84 | push_lua(L, "@ ( = )", skang, MODULEEND, _M, 0); | ||
85 | |||
86 | // Return _M, the table itself, not the index. | ||
87 | return 1; | ||
88 | } | ||
diff --git a/src/LuaSL/LuaSL.edc b/src/LuaSL/LuaSL.edc new file mode 100644 index 0000000..844fc8e --- /dev/null +++ b/src/LuaSL/LuaSL.edc | |||
@@ -0,0 +1,205 @@ | |||
1 | color_classes { | ||
2 | color_class { name: "test_colour"; color: 255 255 255 255; } | ||
3 | } | ||
4 | |||
5 | //fonts { | ||
6 | // font: "Vera.ttf" "default"; | ||
7 | //} | ||
8 | |||
9 | images { | ||
10 | image: "bubble.png" COMP; | ||
11 | image: "test.png" COMP; | ||
12 | } | ||
13 | |||
14 | collections { | ||
15 | group { | ||
16 | name: "main"; | ||
17 | lua_script_only: 1; | ||
18 | lua_script { | ||
19 | --// stick object private/local vars here | ||
20 | local D; | ||
21 | local text_geom; | ||
22 | |||
23 | --// init object here | ||
24 | D = {}; --// data is empty table to start | ||
25 | |||
26 | --// send some random edje message | ||
27 | edje.messagesend(7, "none" ); | ||
28 | edje.messagesend(7, "sig", "signal", "source"); | ||
29 | edje.messagesend(7, "str", "hello world"); | ||
30 | edje.messagesend(7, "int", 987); | ||
31 | edje.messagesend(7, "float", 987.321); | ||
32 | edje.messagesend(7, "strset", {"hello", "there", "world"}); | ||
33 | edje.messagesend(7, "intset", {1, 2, 3}); | ||
34 | edje.messagesend(7, "floatset", {1.1, 2.2, 3.3}); | ||
35 | edje.messagesend(7, "strint", "hello world", 7); | ||
36 | edje.messagesend(7, "strfloat", "hello world", 7.654); | ||
37 | edje.messagesend(7, "strintset","hello world", {1, 2, 3}); | ||
38 | |||
39 | D.edje = edje.edje(); | ||
40 | D.edje:file("plain/edje/group"); | ||
41 | D.edje:show(); | ||
42 | |||
43 | D.text = edje.text(); | ||
44 | D.text:geom (50, 5, 150, 50); | ||
45 | D.text:color (255, 0, 0, 255); | ||
46 | D.text:font("Sans:style=Bold", 32); | ||
47 | D.text:text("Lua rocks!"); | ||
48 | text_geom = D.text:geom(); | ||
49 | --// print(D.text:text()); | ||
50 | D.text:show(); | ||
51 | |||
52 | |||
53 | --// shutdown func - generally empty or not there. everything garbage collected for you | ||
54 | function shutdown () | ||
55 | --// print("lua::shutdown"); | ||
56 | end | ||
57 | |||
58 | function show () | ||
59 | --// print("lua::show"); | ||
60 | end | ||
61 | |||
62 | function hide () | ||
63 | --// print("lua::hide"); | ||
64 | end | ||
65 | |||
66 | function move (x, y) | ||
67 | --// print("lua::move x=" .. x .. " x=" .. y); | ||
68 | D.edje:move(0, 0); | ||
69 | end | ||
70 | |||
71 | function resize (w, h) | ||
72 | --// print("lua::resize w=" .. w .. " h=" .. h); | ||
73 | D.text:move((w - text_geom.w) / 2, (h - text_geom.h) / 8); | ||
74 | D.edje:resize(w, h); | ||
75 | end | ||
76 | |||
77 | function message (id, type, ...) | ||
78 | print("lua::message id=" .. id .. " type=" .. type); | ||
79 | --// handle your message type here. check id + type then use the | ||
80 | --// vararg appropriately. they are the same as the params passed | ||
81 | --// to edje:messagesend() (if any are passed at all). Any array | ||
82 | --// arguments are passed as a single table. | ||
83 | |||
84 | if ("none" == type) then | ||
85 | print("lua::message no args"); | ||
86 | elseif ("strset" == type) then | ||
87 | strs = ... ; | ||
88 | print_table_start(strs, "", "lua::message strings"); | ||
89 | elseif ("intset" == type) then | ||
90 | ints = ... ; | ||
91 | print_table_start(ints, "", "lua::message ints"); | ||
92 | elseif ("floatset" == type) then | ||
93 | floats = ... ; | ||
94 | print_table_start(floats, "", "lua::message floats"); | ||
95 | elseif ("strintset" == type) then | ||
96 | str, ints = ... ; | ||
97 | print("lua::message " .. str); | ||
98 | print_table_start(ints, "", "lua::message ints"); | ||
99 | elseif ("strfloatset" == type) then | ||
100 | str, floats = ... ; | ||
101 | print("lua::message " .. str); | ||
102 | print_table_start(floats, "", "lua::message floats"); | ||
103 | else | ||
104 | print("lua::message " .. ... ); | ||
105 | end | ||
106 | end | ||
107 | |||
108 | function signal (sig, src) | ||
109 | print("lua::signal sig= " .. sig .. " src= " .. src); | ||
110 | end | ||
111 | } | ||
112 | } | ||
113 | |||
114 | // The group name NEEDS a / in it, | ||
115 | // or the part below that tries to swallow it won't work. | ||
116 | // Leaving just the lua part visible. | ||
117 | group { | ||
118 | name: "bubbles/lua"; | ||
119 | lua_script_only: 1; | ||
120 | lua_script { | ||
121 | local bubbles = { }; | ||
122 | local bubbleCols = 8; | ||
123 | local bubbleRows = 6; | ||
124 | |||
125 | for i = 1, bubbleRows do | ||
126 | row = { }; | ||
127 | for j = 1, bubbleCols do | ||
128 | image = edje.image(); | ||
129 | image:image("bubble.png"); | ||
130 | image:show(); | ||
131 | table.insert(row, image); | ||
132 | end | ||
133 | table.insert(bubbles, row); | ||
134 | end | ||
135 | |||
136 | function resize (w, h) | ||
137 | for i = 1, bubbleRows do | ||
138 | for j = 1, bubbleCols do | ||
139 | w1 = w / bubbleCols; | ||
140 | h1 = h / bubbleRows; | ||
141 | bubbles[i][j]:geom((j - 1) * w1, (i - 1) * h1, w1, h1); | ||
142 | if ((1 == i) or (1 == j) or (bubbleRows == i) or (bubbleCols == j)) then | ||
143 | bubbles[i][j]:color(0, 255, 0, 200); | ||
144 | else | ||
145 | bubbles[i][j]:color(math.random(200) + 55, 0, math.random(255) + 55, 200); | ||
146 | end | ||
147 | end | ||
148 | end | ||
149 | end | ||
150 | } | ||
151 | } | ||
152 | |||
153 | group { | ||
154 | name: "plain/edje/group"; | ||
155 | parts { | ||
156 | part { | ||
157 | name: "background"; | ||
158 | type: RECT; | ||
159 | mouse_events: 0; | ||
160 | description { | ||
161 | state: "default" 0.0; | ||
162 | color: 0 0 0 255; | ||
163 | } | ||
164 | } | ||
165 | |||
166 | // A lua group embedded in an edje group. | ||
167 | part { | ||
168 | name: "bubbles_lua"; | ||
169 | type: GROUP; | ||
170 | source: "bubbles/lua"; | ||
171 | mouse_events: 0; | ||
172 | description { state: "default" 0.0; } | ||
173 | } | ||
174 | |||
175 | part { | ||
176 | name: "background_image"; | ||
177 | type: IMAGE; | ||
178 | mouse_events: 0; | ||
179 | description { | ||
180 | state: "default" 0.0; | ||
181 | aspect_preference: HORIZONTAL; | ||
182 | color_class: "test_colour"; | ||
183 | image { normal: "test.png"; } | ||
184 | } | ||
185 | } | ||
186 | |||
187 | part { | ||
188 | name: "some_text"; | ||
189 | type: TEXT; | ||
190 | mouse_events: 0; | ||
191 | description { | ||
192 | state: "default" 0; | ||
193 | text | ||
194 | { | ||
195 | text: "This is test text."; | ||
196 | text_class: "test_text_class"; | ||
197 | } | ||
198 | } | ||
199 | } | ||
200 | |||
201 | } | ||
202 | } | ||
203 | |||
204 | } | ||
205 | |||
diff --git a/src/LuaSL/LuaSL.h b/src/LuaSL/LuaSL.h new file mode 100644 index 0000000..d1f448e --- /dev/null +++ b/src/LuaSL/LuaSL.h | |||
@@ -0,0 +1,82 @@ | |||
1 | #ifdef HAVE_CONFIG_H | ||
2 | #include "config.h" | ||
3 | #else | ||
4 | //#define PACKAGE_EXAMPLES_DIR "." | ||
5 | #define __UNUSED__ | ||
6 | #endif | ||
7 | |||
8 | #include <Eet.h> | ||
9 | #include <Ecore.h> | ||
10 | #include <Ecore_Con.h> | ||
11 | #include <Ecore_Evas.h> | ||
12 | #include <Ecore_File.h> | ||
13 | #include <Edje.h> | ||
14 | #include <stdio.h> | ||
15 | #include <ctype.h> | ||
16 | |||
17 | #include <lua.h> | ||
18 | #include <luajit.h> | ||
19 | #include <lualib.h> | ||
20 | #include <lauxlib.h> | ||
21 | |||
22 | typedef struct _script script; // Define this here, so LuaSL_threads.h can use it. | ||
23 | typedef struct _gameGlobals gameGlobals; // Define this here, so LuaSL_threads.h can use it. | ||
24 | |||
25 | #include "LuaSL_threads.h" | ||
26 | #include "LumbrJack.h" | ||
27 | |||
28 | |||
29 | #define WIDTH (512) | ||
30 | #define HEIGHT (384) | ||
31 | |||
32 | |||
33 | #define TABLE_WIDTH 7 | ||
34 | #define TABLE_HEIGHT 42 | ||
35 | |||
36 | |||
37 | struct _gameGlobals | ||
38 | { | ||
39 | Ecore_Evas *ee; // Our window. | ||
40 | Evas *canvas; // The canvas for drawing directly onto. | ||
41 | Evas_Object *bg; // Our background edje, also the game specific stuff. | ||
42 | Evas_Object *edje; // The edje of the background. | ||
43 | Ecore_Con_Server *server; | ||
44 | Eina_Hash *scripts, *names; | ||
45 | int logDom; | ||
46 | const char *address; | ||
47 | int port; | ||
48 | boolean ui; // Wether we actually start up the UI. | ||
49 | }; | ||
50 | |||
51 | struct _script | ||
52 | { | ||
53 | Eina_Clist node; | ||
54 | gameGlobals *game; | ||
55 | char SID[PATH_MAX]; | ||
56 | char fileName[PATH_MAX]; | ||
57 | lua_State *L; | ||
58 | struct timeval startTime; | ||
59 | float compileTime, timerTime; | ||
60 | int bugs, warnings; | ||
61 | boolean running; | ||
62 | int status; | ||
63 | int args; | ||
64 | Eina_Clist messages; | ||
65 | Ecore_Con_Client *client; | ||
66 | Ecore_Timer *timer; | ||
67 | }; | ||
68 | |||
69 | typedef struct | ||
70 | { | ||
71 | Eina_Clist node; | ||
72 | script *script; | ||
73 | const char message[PATH_MAX]; | ||
74 | } scriptMessage; | ||
75 | |||
76 | |||
77 | void scriptSendBack(void * data); | ||
78 | void sendBack(gameGlobals *ourGlobals, Ecore_Con_Client *client, const char *SID, const char *message, ...); | ||
79 | void sendForth(gameGlobals *ourGlobals, const char *SID, const char *message, ...); | ||
80 | float timeDiff(struct timeval *now, struct timeval *then); | ||
81 | |||
82 | #include "LuaSL_LSL_tree.h" | ||
diff --git a/src/LuaSL/LuaSL_LSL_tree.h b/src/LuaSL/LuaSL_LSL_tree.h new file mode 100644 index 0000000..5415228 --- /dev/null +++ b/src/LuaSL/LuaSL_LSL_tree.h | |||
@@ -0,0 +1,443 @@ | |||
1 | |||
2 | #ifndef __LUASL_TREE_H__ | ||
3 | #define __LUASL_TREE_H__ | ||
4 | |||
5 | #define LUASL_DEBUG 0 | ||
6 | #define LUASL_DIFF_CHECK 0 | ||
7 | |||
8 | |||
9 | #include <stddef.h> // So we can have NULL defined. | ||
10 | #include <sys/types.h> | ||
11 | #include <sys/stat.h> | ||
12 | #include <fcntl.h> | ||
13 | |||
14 | #include "assert.h" | ||
15 | #include <unistd.h> | ||
16 | #include <stdlib.h> | ||
17 | #include <stdio.h> | ||
18 | #include <limits.h> // For PATH_MAX. | ||
19 | |||
20 | #include "LuaSL_lemon_yaccer.h" | ||
21 | |||
22 | #define YYERRCODE 256 | ||
23 | #define YYDEBUG 1 | ||
24 | |||
25 | |||
26 | // http://w-hat.com/stackdepth is a useful discussion about some aspects of the LL parser. | ||
27 | |||
28 | |||
29 | typedef struct _allowedTypes allowedTypes; | ||
30 | typedef struct _LSL_Token LSL_Token; | ||
31 | typedef struct _LSL_Text LSL_Text; | ||
32 | typedef struct _LSL_Leaf LSL_Leaf; | ||
33 | typedef struct _LSL_Numby LSL_Numby; | ||
34 | typedef struct _LSL_Parenthesis LSL_Parenthesis; | ||
35 | typedef struct _LSL_Identifier LSL_Identifier; | ||
36 | typedef struct _LSL_Statement LSL_Statement; | ||
37 | typedef struct _LSL_Block LSL_Block; | ||
38 | typedef struct _LSL_Function LSL_Function; | ||
39 | typedef struct _LSL_FunctionCall LSL_FunctionCall; | ||
40 | typedef struct _LSL_State LSL_State; | ||
41 | typedef struct _LSL_Script LSL_Script; | ||
42 | |||
43 | extern LSL_Token **tokens; | ||
44 | extern int lowestToken; | ||
45 | |||
46 | typedef int LSL_Type; | ||
47 | |||
48 | typedef enum | ||
49 | { | ||
50 | OM_LSL, | ||
51 | OM_LUA | ||
52 | } outputMode; | ||
53 | |||
54 | typedef void (*outputToken) (FILE *file, outputMode mode, LSL_Leaf *content); | ||
55 | |||
56 | //#ifndef FALSE | ||
57 | //typedef enum | ||
58 | //{ | ||
59 | // FALSE = 0, | ||
60 | // TRUE = 1 | ||
61 | //} boolean; | ||
62 | //#endif | ||
63 | |||
64 | typedef enum | ||
65 | { | ||
66 | LSL_NONE = 0, | ||
67 | LSL_LEFT2RIGHT = 1, | ||
68 | LSL_RIGHT2LEFT = 2, | ||
69 | LSL_INNER2OUTER = 4, | ||
70 | LSL_UNARY = 8, | ||
71 | LSL_ASSIGNMENT = 16, | ||
72 | LSL_CREATION = 32, | ||
73 | LSL_NOIGNORE = 64, | ||
74 | LSL_TYPE = 128 | ||
75 | } LSL_Flags; | ||
76 | |||
77 | |||
78 | // VERY IMPORTANT to keep this in sync with allowedTypes allowed[] from LuaSL_compile.c! | ||
79 | typedef enum | ||
80 | { | ||
81 | OT_nothing, | ||
82 | |||
83 | OT_bool, | ||
84 | OT_integer, | ||
85 | OT_float, | ||
86 | OT_key, | ||
87 | OT_list, | ||
88 | OT_rotation, | ||
89 | OT_string, | ||
90 | OT_vector, | ||
91 | OT_other, | ||
92 | |||
93 | OT_boolBool, | ||
94 | OT_intBool, | ||
95 | OT_intInt, | ||
96 | OT_intFloat, | ||
97 | OT_floatInt, | ||
98 | OT_floatFloat, | ||
99 | OT_keyKey, | ||
100 | OT_keyString, | ||
101 | OT_stringKey, | ||
102 | OT_stringString, | ||
103 | OT_listList, | ||
104 | OT_listBool, | ||
105 | OT_listInt, | ||
106 | OT_listFloat, | ||
107 | OT_listString, | ||
108 | OT_intList, | ||
109 | OT_floatList, | ||
110 | OT_listOther, | ||
111 | OT_vectorVector, | ||
112 | OT_vectorFloat, | ||
113 | OT_vectorRotation, | ||
114 | OT_rotationRotation, | ||
115 | OT_otherOther, | ||
116 | OT_undeclared, | ||
117 | OT_invalid | ||
118 | } opType; | ||
119 | |||
120 | typedef enum | ||
121 | { | ||
122 | ST_NONE = 0, | ||
123 | ST_ASSIGNMENT = 1, // -= *= /= | ||
124 | ST_BIT_NOT = 2, // ~ | ||
125 | ST_BOOL_NOT = 4, // ! | ||
126 | ST_BITWISE = 8, // & | ^ << >> | ||
127 | ST_BOOLEAN = 16, // && !! | ||
128 | ST_COMPARISON = 32, // < > <= >= | ||
129 | ST_CONCATENATION = 64, // = += | ||
130 | ST_EQUALITY = 128, // == != | ||
131 | ST_ADD = 512, // + | ||
132 | ST_SUBTRACT = 1024, // - | ||
133 | ST_NEGATE = 2048, // - | ||
134 | ST_MULTIPLY = 4096, // * / | ||
135 | ST_MODULO = 8192 // % %= | ||
136 | } opSubType; | ||
137 | |||
138 | typedef enum | ||
139 | { | ||
140 | MF_NONE = 0, | ||
141 | MF_LOCAL = 1, | ||
142 | MF_NOASSIGN = 2, // These two are for completely different purposes. This one is for variable definitions with no assignment. | ||
143 | MF_ASSIGNEXP = 4, // These two are for completely different purposes. This one is for assignments being used as expressions. | ||
144 | MF_WRAPFUNC = 8, | ||
145 | MF_PREDEC = 16, | ||
146 | MF_PREINC = 32, | ||
147 | MF_POSTDEC = 64, | ||
148 | MF_POSTINC = 128, | ||
149 | MF_LSLCONST = 256, | ||
150 | MF_TYPECAST = 512 | ||
151 | } miscFlags; | ||
152 | |||
153 | struct _allowedTypes | ||
154 | { | ||
155 | opType result; | ||
156 | const char *name; | ||
157 | int subTypes; | ||
158 | }; | ||
159 | |||
160 | struct _LSL_Token | ||
161 | { | ||
162 | LSL_Type type; | ||
163 | opSubType subType; | ||
164 | const char *toKen; | ||
165 | LSL_Flags flags; | ||
166 | outputToken output; | ||
167 | }; | ||
168 | |||
169 | struct _LSL_Text | ||
170 | { | ||
171 | const char *text; | ||
172 | #if LUASL_DIFF_CHECK | ||
173 | Eina_Strbuf *ignorable; | ||
174 | #endif | ||
175 | }; | ||
176 | |||
177 | struct _LSL_Leaf | ||
178 | { | ||
179 | LSL_Leaf *left; | ||
180 | LSL_Leaf *right; | ||
181 | LSL_Token *toKen; | ||
182 | #if LUASL_DIFF_CHECK | ||
183 | Eina_Strbuf *ignorable; | ||
184 | #endif | ||
185 | int line, column, len; | ||
186 | opType basicType; | ||
187 | miscFlags flags; | ||
188 | union | ||
189 | { | ||
190 | float floatValue; | ||
191 | float vectorValue[3]; | ||
192 | float rotationValue[4]; | ||
193 | int integerValue; | ||
194 | LSL_Numby *numbyValue; | ||
195 | LSL_Leaf *listValue; | ||
196 | const char *stringValue; | ||
197 | opType operationValue; | ||
198 | LSL_Parenthesis *parenthesis; | ||
199 | LSL_Identifier *identifierValue; | ||
200 | LSL_Statement *statementValue; | ||
201 | LSL_Block *blockValue; | ||
202 | LSL_Function *functionValue; | ||
203 | LSL_FunctionCall *functionCallValue; | ||
204 | LSL_State *stateValue; | ||
205 | LSL_Script *scriptValue; | ||
206 | } value; | ||
207 | }; | ||
208 | |||
209 | struct _LSL_Numby | ||
210 | { | ||
211 | LSL_Text text; | ||
212 | LSL_Type type; | ||
213 | union | ||
214 | { | ||
215 | float floatValue; | ||
216 | int integerValue; | ||
217 | } value; | ||
218 | }; | ||
219 | |||
220 | struct _LSL_Parenthesis | ||
221 | { | ||
222 | LSL_Leaf *contents; | ||
223 | #if LUASL_DIFF_CHECK | ||
224 | Eina_Strbuf *rightIgnorable; | ||
225 | #endif | ||
226 | LSL_Type type; | ||
227 | miscFlags flags; | ||
228 | }; | ||
229 | |||
230 | struct _LSL_Identifier // For variables and function parameters. | ||
231 | { | ||
232 | LSL_Text name; | ||
233 | LSL_Statement *definition; | ||
234 | Eina_Strbuf *ignorable; | ||
235 | const char *sub; | ||
236 | LSL_Leaf value; | ||
237 | miscFlags flags; | ||
238 | }; | ||
239 | |||
240 | struct _LSL_Statement | ||
241 | { | ||
242 | Eina_Clist statement; // For block statement lists, this is the entry. | ||
243 | LSL_Text identifier; | ||
244 | LSL_Parenthesis *parenthesis; | ||
245 | LSL_Leaf *expressions; // A for statement will have three expressions, and two semicolons, everything else has zero or one. | ||
246 | LSL_Block *block; | ||
247 | LSL_Statement *single; // For single statement "blocks". | ||
248 | LSL_Statement *elseBlock; | ||
249 | LSL_Type type; // Expression type. | ||
250 | #if LUASL_DIFF_CHECK | ||
251 | Eina_Strbuf **ignorable; // Can be up to five of these I think. | ||
252 | #endif | ||
253 | miscFlags flags; | ||
254 | }; | ||
255 | |||
256 | struct _LSL_Block | ||
257 | { | ||
258 | LSL_Block *outerBlock; | ||
259 | Eina_Clist statements; // For statement lists, this is the HEAD. | ||
260 | Eina_Hash *variables; // Those variables in this scope. | ||
261 | LSL_Function *function; // A pointer to the function if this block is a function. | ||
262 | #if LUASL_DIFF_CHECK | ||
263 | Eina_Strbuf *openIgnorable; | ||
264 | Eina_Strbuf *closeIgnorable; | ||
265 | #endif | ||
266 | }; | ||
267 | |||
268 | struct _LSL_Function | ||
269 | { | ||
270 | LSL_Text name; | ||
271 | LSL_Text type; | ||
272 | const char *state; | ||
273 | #if LUASL_DIFF_CHECK | ||
274 | // LSL_Leaf *params; // So we store the parenthesis, and their ignorables. | ||
275 | // This points to the params leaf, which is a function, pointing to this structure. The actual params are in vars. | ||
276 | #endif | ||
277 | Eina_Inarray vars; // Eina Inarray has not been released yet (Eina 1.2). | ||
278 | LSL_Block *block; | ||
279 | miscFlags flags; | ||
280 | }; | ||
281 | |||
282 | struct _LSL_FunctionCall | ||
283 | { | ||
284 | LSL_Function *function; | ||
285 | Eina_Inarray params; // Eina Inarray has not been released yet (Eina 1.2). | ||
286 | Eina_Clist dangler; // Entry for function calls used before the function is defined. | ||
287 | LSL_Leaf *call; // This is to stash the details for dangling ones, to search later. | ||
288 | // The line and column details are needed for bitching, so we need the leaf. | ||
289 | // Also need the stringValue for the search. | ||
290 | // On top of all that, the leaf is still used in expressions, so need to keep it around and update it when resolving danglers. | ||
291 | }; | ||
292 | |||
293 | struct _LSL_State | ||
294 | { | ||
295 | LSL_Text name; | ||
296 | LSL_Text state; | ||
297 | LSL_Block *block; | ||
298 | Eina_Hash *handlers; | ||
299 | }; | ||
300 | |||
301 | struct _LSL_Script | ||
302 | { | ||
303 | const char *name; | ||
304 | Eina_Hash *functions; | ||
305 | Eina_Hash *states; | ||
306 | Eina_Hash *variables; | ||
307 | int bugCount, warningCount; | ||
308 | }; | ||
309 | |||
310 | /* Tracking variables. | ||
311 | |||
312 | There are global variables, block local variables, and function parameters. | ||
313 | |||
314 | For outputting Lua, which is the ultimate goal - | ||
315 | track order, name, and type. | ||
316 | |||
317 | For looking them up during the compile - | ||
318 | quick access from name. | ||
319 | |||
320 | For validating them during compile - | ||
321 | track type. | ||
322 | |||
323 | For outputting LSL to double check - | ||
324 | track order, name, type, and white space. | ||
325 | |||
326 | For executing directly from the AST - | ||
327 | track order, name, type, and value. | ||
328 | In this case, order is only important for functions. | ||
329 | |||
330 | We can assume that names are stringshared. This means we only have to | ||
331 | compare pointers. It also means the same name stored at diffferent | ||
332 | scopes, must be stored in separate structures, coz the pointers are the | ||
333 | same. | ||
334 | |||
335 | Order is taken care of by the AST anyway, but for somethings we want to | ||
336 | condense the AST down to something more efficient. | ||
337 | |||
338 | On the other hand, no need to micro optimise it just yet, we should be | ||
339 | able to try out other data structures at a later date, then benchmark | ||
340 | them with typical scripts. | ||
341 | |||
342 | Right now I see nothing wrong with the current use of hash for script | ||
343 | and block variables. The same for script states and functions, as well | ||
344 | as state functions. Though in the near future, they will have similar | ||
345 | problems to functions I think - the need to track order and white | ||
346 | space. | ||
347 | |||
348 | Function params got unwieldy. Cleaned that up now. | ||
349 | |||
350 | */ | ||
351 | |||
352 | /* General design. | ||
353 | |||
354 | NOTE We can remove the white space tracking at compile time, as it's | ||
355 | only a debugging aid. Will be a performance and memory gain for | ||
356 | productidon use. Tracking values on the other hand will still be useful | ||
357 | for constants. | ||
358 | |||
359 | The compile process starts with turning tokens into AST nodes connected | ||
360 | in a tree. During that process the parser wants to condense nodes down | ||
361 | to more efficient data structures. This is a good idea, as we will | ||
362 | spend a fair amount of time looking up names, no matter which part of | ||
363 | the process is important at the time. | ||
364 | |||
365 | Once the parser has condensed things down, it only deals with the | ||
366 | condensed nodes. So we can get rid of some of the AST parts at this | ||
367 | time, so long as we keep the relevant information. This is what the | ||
368 | other data structures above are for. Lemon tries to free the no longer | ||
369 | needed AST nodes itself, even if we are still using them internally. | ||
370 | Need to do something about that. | ||
371 | |||
372 | */ | ||
373 | |||
374 | // Define the type for flex and lemon. | ||
375 | #define YYSTYPE LSL_Leaf | ||
376 | |||
377 | typedef struct | ||
378 | { | ||
379 | gameGlobals *game; | ||
380 | Ecore_Con_Client *client; | ||
381 | void *scanner; // This should be of type yyscan_t, which is typedef to void * anyway, but that does not get defined until LuaSL_lexer.h, which depends on this struct being defined first. | ||
382 | int argc; | ||
383 | char **argv; | ||
384 | char SID[37]; | ||
385 | char fileName[PATH_MAX]; | ||
386 | FILE *file; | ||
387 | LSL_Leaf *ast; | ||
388 | LSL_Script script; | ||
389 | LSL_State state; | ||
390 | #if LUASL_DIFF_CHECK | ||
391 | Eina_Strbuf *ignorable; | ||
392 | #endif | ||
393 | LSL_Leaf *lval; | ||
394 | LSL_Block *currentBlock; | ||
395 | LSL_Function *currentFunction; | ||
396 | Eina_Clist danglingCalls; // HEAD for function calls used before the function is defined. | ||
397 | int column, line; | ||
398 | int undeclared; | ||
399 | boolean inState; | ||
400 | } LuaSL_compiler; | ||
401 | |||
402 | |||
403 | #ifndef excludeLexer | ||
404 | #include "LuaSL_lexer.h" | ||
405 | #endif | ||
406 | |||
407 | |||
408 | |||
409 | boolean compilerSetup(gameGlobals *ourGlobals); | ||
410 | boolean compileLSL(gameGlobals *ourGlobals, Ecore_Con_Client *client, char *SID, char *script, boolean doConstants); | ||
411 | void burnLeaf(void *data); | ||
412 | |||
413 | LSL_Leaf *addBlock(LuaSL_compiler *compiler, LSL_Leaf *left, LSL_Leaf *lval, LSL_Leaf *right); | ||
414 | LSL_Leaf *addCrement(LuaSL_compiler *compiler, LSL_Leaf *variable, LSL_Leaf *crement, LSL_Type type); | ||
415 | LSL_Leaf *addFor(LuaSL_compiler *compiler, LSL_Leaf *lval, LSL_Leaf *flow, LSL_Leaf *left, LSL_Leaf *expr0, LSL_Leaf *stat0, LSL_Leaf *expr1, LSL_Leaf *stat1, LSL_Leaf *expr2, LSL_Leaf *right, LSL_Leaf *block); | ||
416 | LSL_Leaf *addFunction(LuaSL_compiler *compiler, LSL_Leaf *type, LSL_Leaf *identifier, LSL_Leaf *open, LSL_Leaf *params, LSL_Leaf *close); | ||
417 | LSL_Leaf *addFunctionBody(LuaSL_compiler *compiler, LSL_Leaf *function, LSL_Leaf *block); | ||
418 | LSL_Leaf *addFunctionCall(LuaSL_compiler *compiler, LSL_Leaf *identifier, LSL_Leaf *open, LSL_Leaf *params, LSL_Leaf *close); | ||
419 | LSL_Leaf *addIfElse(LuaSL_compiler *compiler, LSL_Leaf *ifBlock, LSL_Leaf *elseBlock); | ||
420 | LSL_Leaf *addList(LSL_Leaf *left, LSL_Leaf *list, LSL_Leaf *right); | ||
421 | LSL_Leaf *addNumby(LSL_Leaf *numby); | ||
422 | LSL_Leaf *addOperation(LuaSL_compiler *compiler, LSL_Leaf *left, LSL_Leaf *lval, LSL_Leaf *right); | ||
423 | LSL_Leaf *addParameter(LuaSL_compiler *compiler, LSL_Leaf *type, LSL_Leaf *newParam); | ||
424 | LSL_Leaf *addParenthesis(LSL_Leaf *lval, LSL_Leaf *expr, LSL_Type type, LSL_Leaf *rval); | ||
425 | LSL_Leaf *addRotVec(LSL_Leaf *left, LSL_Leaf *list, LSL_Leaf *right); | ||
426 | LSL_Leaf *addState(LuaSL_compiler *compiler, LSL_Leaf *state, LSL_Leaf *identifier, LSL_Leaf *block); | ||
427 | LSL_Leaf *addStatement(LuaSL_compiler *compiler, LSL_Leaf *lval, LSL_Leaf *flow, LSL_Leaf *left, LSL_Leaf *expr, LSL_Leaf *right, LSL_Leaf *block, LSL_Leaf *identifier); | ||
428 | LSL_Leaf *addTypecast(LSL_Leaf *lval, LSL_Leaf *type, LSL_Leaf *rval, LSL_Leaf *expr); | ||
429 | LSL_Leaf *addVariable(LuaSL_compiler *compiler, LSL_Leaf *type, LSL_Leaf *identifier, LSL_Leaf *assignment, LSL_Leaf *expr); | ||
430 | |||
431 | LSL_Leaf *beginBlock(LuaSL_compiler *compiler, LSL_Leaf *block); | ||
432 | LSL_Leaf *checkVariable(LuaSL_compiler *compiler, LSL_Leaf *identifier, LSL_Leaf *dot, LSL_Leaf *sub); | ||
433 | LSL_Leaf *collectArguments(LuaSL_compiler *compiler, LSL_Leaf *list, LSL_Leaf *comma, LSL_Leaf *arg); | ||
434 | LSL_Leaf *collectParameters(LuaSL_compiler *compiler, LSL_Leaf *list, LSL_Leaf *comma, LSL_Leaf *newParam); | ||
435 | LSL_Leaf *collectStatements(LuaSL_compiler *compiler, LSL_Leaf *list, LSL_Leaf *newStatement); | ||
436 | |||
437 | void *ParseAlloc(void *(*mallocProc)(size_t)); | ||
438 | void ParseTrace(FILE *TraceFILE, char *zTracePrompt); | ||
439 | void Parse(void *yyp, int yymajor, LSL_Leaf *yyminor, LuaSL_compiler *compiler); | ||
440 | void ParseFree(void *p, void (*freeProc)(void*)); | ||
441 | |||
442 | |||
443 | #endif // __LUASL_LSL_TREE_H__ | ||
diff --git a/src/LuaSL/LuaSL_compile.c b/src/LuaSL/LuaSL_compile.c new file mode 100644 index 0000000..771888e --- /dev/null +++ b/src/LuaSL/LuaSL_compile.c | |||
@@ -0,0 +1,2345 @@ | |||
1 | |||
2 | #include "LuaSL.h" | ||
3 | |||
4 | /* TODO - problem de jour | ||
5 | */ | ||
6 | |||
7 | |||
8 | static void outputBitOp(FILE *file, outputMode mode, LSL_Leaf *leaf); | ||
9 | static void outputBlockToken(FILE *file, outputMode mode, LSL_Leaf *content); | ||
10 | static void outputCrementsToken(FILE *file, outputMode mode, LSL_Leaf *content); | ||
11 | static void outputFloatToken(FILE *file, outputMode mode, LSL_Leaf *content); | ||
12 | static void outputFunctionToken(FILE *file, outputMode mode, LSL_Leaf *content); | ||
13 | static void outputFunctionCallToken(FILE *file, outputMode mode, LSL_Leaf *content); | ||
14 | static void outputIntegerToken(FILE *file, outputMode mode, LSL_Leaf *content); | ||
15 | static void outputIdentifierToken(FILE *file, outputMode mode, LSL_Leaf *content); | ||
16 | static void outputListToken(FILE *file, outputMode mode, LSL_Leaf *content); | ||
17 | static void outputParameterListToken(FILE *file, outputMode mode, LSL_Leaf *content); | ||
18 | static void outputParenthesisToken(FILE *file, outputMode mode, LSL_Leaf *content); | ||
19 | static void outputStateToken(FILE *file, outputMode mode, LSL_Leaf *content); | ||
20 | static void outputStatementToken(FILE *file, outputMode mode, LSL_Leaf *content); | ||
21 | static void outputStringToken(FILE *file, outputMode mode, LSL_Leaf *content); | ||
22 | |||
23 | LSL_Token LSL_Tokens[] = | ||
24 | { | ||
25 | // Various forms of "space". | ||
26 | {LSL_COMMENT, ST_NONE, "/*", LSL_NONE, NULL}, | ||
27 | {LSL_COMMENT_LINE, ST_NONE, "//", LSL_NONE, NULL}, | ||
28 | {LSL_SPACE, ST_NONE, " ", LSL_NONE, NULL}, | ||
29 | |||
30 | // Operators, in order of precedence, low to high | ||
31 | // Left to right, unless otherwise stated. | ||
32 | // According to http://wiki.secondlife.com/wiki/Category:LSL_Operators, which was obsoleted by http://wiki.secondlife.com/wiki/LSL_Operators but that has less info. | ||
33 | |||
34 | {LSL_ASSIGNMENT_CONCATENATE,ST_CONCATENATION, "+=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, | ||
35 | {LSL_ASSIGNMENT_ADD, ST_CONCATENATION, "+=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, | ||
36 | {LSL_ASSIGNMENT_SUBTRACT, ST_ASSIGNMENT, "-=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, | ||
37 | {LSL_ASSIGNMENT_MULTIPLY, ST_ASSIGNMENT, "*=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, | ||
38 | {LSL_ASSIGNMENT_MODULO, ST_MODULO, "%=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, | ||
39 | {LSL_ASSIGNMENT_DIVIDE, ST_ASSIGNMENT, "/=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, | ||
40 | {LSL_ASSIGNMENT_PLAIN, ST_CONCATENATION, "=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, | ||
41 | |||
42 | {LSL_BOOL_AND, ST_BOOLEAN, "&&", LSL_RIGHT2LEFT, NULL}, | ||
43 | // QUIRK - Seems to be some disagreement about BOOL_AND/BOOL_OR precedence. Either they are equal, or OR is higher. | ||
44 | // QUIRK - No boolean short circuiting. | ||
45 | // LUA - Short circiuts boolean operations, and goes left to right. | ||
46 | // LUA - "and" returns its first argument if it is false, otherwise, it returns its second argument. "or" returns its first argument if it is not false, otherwise it returns its second argument. | ||
47 | // Note that the above means that "and/or" can return any type. | ||
48 | {LSL_BOOL_OR, ST_BOOLEAN, "||", LSL_RIGHT2LEFT, NULL}, | ||
49 | {LSL_BIT_OR, ST_BITWISE, "|", LSL_LEFT2RIGHT, outputBitOp}, | ||
50 | {LSL_BIT_XOR, ST_BITWISE, "^", LSL_LEFT2RIGHT, outputBitOp}, | ||
51 | {LSL_BIT_AND, ST_BITWISE, "&", LSL_LEFT2RIGHT, outputBitOp}, | ||
52 | // QUIRK - Booleans and conditionals are executed right to left. Or maybe not, depending on who you believe. | ||
53 | {LSL_NOT_EQUAL, ST_EQUALITY, "!=", LSL_RIGHT2LEFT, NULL}, | ||
54 | {LSL_EQUAL, ST_EQUALITY, "==", LSL_RIGHT2LEFT, NULL}, | ||
55 | {LSL_GREATER_EQUAL, ST_COMPARISON, ">=", LSL_RIGHT2LEFT, NULL}, | ||
56 | {LSL_LESS_EQUAL, ST_COMPARISON, "<=", LSL_RIGHT2LEFT, NULL}, | ||
57 | {LSL_GREATER_THAN, ST_COMPARISON, ">", LSL_RIGHT2LEFT, NULL}, | ||
58 | {LSL_LESS_THAN, ST_COMPARISON, "<", LSL_RIGHT2LEFT, NULL}, | ||
59 | // LUA - comparisons are always false if they are different types. Tables, userdata, and functions are compared by reference. Strings compare in alphabetical order, depending on current locale. | ||
60 | // LUA - really only has three conditionals, as it translates a ~= b to not (a == b), a > b to b < a, and a >= b to b <= a. | ||
61 | {LSL_RIGHT_SHIFT, ST_BITWISE, ">>", LSL_LEFT2RIGHT, outputBitOp}, | ||
62 | {LSL_LEFT_SHIFT, ST_BITWISE, "<<", LSL_LEFT2RIGHT, outputBitOp}, | ||
63 | {LSL_CONCATENATE, ST_ADD, "+", LSL_LEFT2RIGHT, NULL}, | ||
64 | {LSL_ADD, ST_ADD, "+", LSL_LEFT2RIGHT, NULL}, | ||
65 | {LSL_SUBTRACT, ST_SUBTRACT, "-", LSL_LEFT2RIGHT, NULL}, | ||
66 | {LSL_CROSS_PRODUCT, ST_NONE, "%", LSL_LEFT2RIGHT, NULL}, | ||
67 | {LSL_DOT_PRODUCT, ST_NONE, "*", LSL_LEFT2RIGHT, NULL}, | ||
68 | {LSL_MULTIPLY, ST_MULTIPLY, "*", LSL_LEFT2RIGHT, NULL}, | ||
69 | {LSL_MODULO, ST_MODULO, "%", LSL_LEFT2RIGHT, NULL}, | ||
70 | {LSL_DIVIDE, ST_MULTIPLY, "/", LSL_LEFT2RIGHT, NULL}, | ||
71 | {LSL_NEGATION, ST_NEGATE, "-", LSL_RIGHT2LEFT | LSL_UNARY, NULL}, | ||
72 | {LSL_BOOL_NOT, ST_BOOL_NOT, "!", LSL_RIGHT2LEFT | LSL_UNARY, NULL}, | ||
73 | {LSL_BIT_NOT, ST_BIT_NOT, "~", LSL_RIGHT2LEFT | LSL_UNARY, outputBitOp}, | ||
74 | |||
75 | // LUA precedence - (it has no bit operators, at least not until 5.2, but LuaJIT has them as table functions.) | ||
76 | // or | ||
77 | // and | ||
78 | // < > <= >= ~= == | ||
79 | // .. | ||
80 | // + - | ||
81 | // * / | ||
82 | // not negate | ||
83 | // exponentiation (^) | ||
84 | |||
85 | {LSL_TYPECAST_CLOSE, ST_NONE, ")", LSL_RIGHT2LEFT | LSL_UNARY, NULL}, | ||
86 | {LSL_TYPECAST_OPEN, ST_NONE, "(", LSL_RIGHT2LEFT | LSL_UNARY, outputParenthesisToken}, | ||
87 | {LSL_ANGLE_CLOSE, ST_NONE, ">", LSL_LEFT2RIGHT | LSL_CREATION, NULL}, | ||
88 | {LSL_ANGLE_OPEN, ST_NONE, "<", LSL_LEFT2RIGHT | LSL_CREATION, NULL}, | ||
89 | {LSL_BRACKET_CLOSE, ST_NONE, "]", LSL_INNER2OUTER | LSL_CREATION, NULL}, | ||
90 | {LSL_BRACKET_OPEN, ST_NONE, "[", LSL_INNER2OUTER | LSL_CREATION, NULL}, | ||
91 | {LSL_PARENTHESIS_CLOSE, ST_NONE, ")", LSL_INNER2OUTER, NULL}, | ||
92 | {LSL_PARENTHESIS_OPEN, ST_NONE, "(", LSL_INNER2OUTER, outputParenthesisToken}, | ||
93 | {LSL_DOT, ST_NONE, ".", LSL_RIGHT2LEFT, NULL}, | ||
94 | {LSL_DECREMENT_POST, ST_NONE, "--", LSL_RIGHT2LEFT | LSL_UNARY, outputCrementsToken}, | ||
95 | {LSL_DECREMENT_PRE, ST_NONE, "--", LSL_RIGHT2LEFT | LSL_UNARY, outputCrementsToken}, | ||
96 | {LSL_INCREMENT_POST, ST_NONE, "++", LSL_RIGHT2LEFT | LSL_UNARY, outputCrementsToken}, | ||
97 | {LSL_INCREMENT_PRE, ST_NONE, "++", LSL_RIGHT2LEFT | LSL_UNARY, outputCrementsToken}, | ||
98 | {LSL_COMMA, ST_NONE, ",", LSL_LEFT2RIGHT, NULL}, | ||
99 | |||
100 | {LSL_EXPRESSION, ST_NONE, "expression", LSL_NONE , NULL}, | ||
101 | |||
102 | // Types. | ||
103 | {LSL_FLOAT, ST_NONE, "float", LSL_NONE, outputFloatToken}, | ||
104 | {LSL_INTEGER, ST_NONE, "integer", LSL_NONE, outputIntegerToken}, | ||
105 | {LSL_KEY, ST_NONE, "key", LSL_NONE, outputStringToken}, | ||
106 | {LSL_LIST, ST_NONE, "list", LSL_NONE, outputListToken}, | ||
107 | {LSL_ROTATION, ST_NONE, "rotation", LSL_NONE, outputListToken}, | ||
108 | {LSL_STRING, ST_NONE, "string", LSL_NONE, outputStringToken}, | ||
109 | {LSL_VECTOR, ST_NONE, "vector", LSL_NONE, outputListToken}, | ||
110 | |||
111 | // Types names. | ||
112 | {LSL_TYPE_FLOAT, ST_NONE, "float", LSL_TYPE, NULL}, | ||
113 | {LSL_TYPE_INTEGER, ST_NONE, "integer", LSL_TYPE, NULL}, | ||
114 | {LSL_TYPE_KEY, ST_NONE, "key", LSL_TYPE, NULL}, | ||
115 | {LSL_TYPE_LIST, ST_NONE, "list", LSL_TYPE, NULL}, | ||
116 | {LSL_TYPE_ROTATION, ST_NONE, "rotation", LSL_TYPE, NULL}, | ||
117 | {LSL_TYPE_STRING, ST_NONE, "string", LSL_TYPE, NULL}, | ||
118 | {LSL_TYPE_VECTOR, ST_NONE, "vector", LSL_TYPE, NULL}, | ||
119 | |||
120 | // Then the rest of the syntax tokens. | ||
121 | {LSL_FUNCTION_CALL, ST_NONE, "funccall", LSL_NONE, outputFunctionCallToken}, | ||
122 | {LSL_IDENTIFIER, ST_NONE, "identifier", LSL_NONE, outputIdentifierToken}, | ||
123 | {LSL_VARIABLE, ST_NONE, "variable", LSL_NONE, outputIdentifierToken}, | ||
124 | |||
125 | {LSL_LABEL, ST_NONE, "@", LSL_NONE, NULL}, | ||
126 | |||
127 | {LSL_DO, ST_NONE, "do", LSL_NONE, NULL}, | ||
128 | {LSL_FOR, ST_NONE, "for", LSL_NONE, NULL}, | ||
129 | {LSL_ELSE, ST_NONE, "else", LSL_NONE, NULL}, | ||
130 | {LSL_ELSEIF, ST_NONE, "elseif", LSL_NONE, NULL}, | ||
131 | {LSL_IF, ST_NONE, "if", LSL_NONE, NULL}, | ||
132 | {LSL_JUMP, ST_NONE, "jump", LSL_NONE, NULL}, | ||
133 | {LSL_RETURN, ST_NONE, "return", LSL_NONE, NULL}, | ||
134 | {LSL_STATE_CHANGE, ST_NONE, "state", LSL_NONE, NULL}, | ||
135 | {LSL_WHILE, ST_NONE, "while", LSL_NONE, NULL}, | ||
136 | {LSL_STATEMENT, ST_NONE, ";", LSL_NOIGNORE, outputStatementToken}, | ||
137 | |||
138 | {LSL_BLOCK_CLOSE, ST_NONE, "}", LSL_NONE, NULL}, | ||
139 | {LSL_BLOCK_OPEN, ST_NONE, "{", LSL_NONE, outputBlockToken}, | ||
140 | {LSL_PARAMETER, ST_NONE, "parameter", LSL_NONE, outputIdentifierToken}, | ||
141 | {LSL_PARAMETER_LIST, ST_NONE, "plist", LSL_NONE, outputParameterListToken}, | ||
142 | {LSL_FUNCTION, ST_NONE, "function", LSL_NONE, outputFunctionToken}, | ||
143 | {LSL_DEFAULT, ST_NONE, "default", LSL_NONE, outputStateToken}, | ||
144 | {LSL_STATE, ST_NONE, "state", LSL_NONE, outputStateToken}, | ||
145 | {LSL_SCRIPT, ST_NONE, "", LSL_NONE, NULL}, | ||
146 | |||
147 | {LSL_UNKNOWN, ST_NONE, "unknown", LSL_NONE, NULL}, | ||
148 | |||
149 | // A sentinal. | ||
150 | {999999, ST_NONE, NULL, LSL_NONE, NULL} | ||
151 | }; | ||
152 | |||
153 | // VERY IMPORTANT to keep this in sync with enum opType from LuaSL_LSL_tree.h! | ||
154 | allowedTypes allowed[] = | ||
155 | { | ||
156 | {OT_nothing, "nothing", (ST_NONE)}, // | ||
157 | |||
158 | {OT_bool, "boolean", (ST_BOOL_NOT)}, // bool ! | ||
159 | {OT_integer, "integer", (ST_BOOL_NOT | ST_BIT_NOT | ST_NEGATE)}, // int ! - ~ | ||
160 | {OT_float, "float", (ST_BOOL_NOT | ST_NEGATE)}, // float ! - | ||
161 | {OT_key, "key", (ST_BOOL_NOT)}, // key ! | ||
162 | {OT_list, "list", (ST_NONE)}, // | ||
163 | {OT_rotation, "rotation", (ST_NONE)}, // | ||
164 | {OT_string, "string", (ST_BOOL_NOT)}, // string ! | ||
165 | {OT_vector, "vector", (ST_NONE)}, // | ||
166 | {OT_other, "other", (ST_NONE)}, // | ||
167 | |||
168 | {OT_bool, "boolean", (ST_BOOLEAN | ST_EQUALITY)}, // bool bool == != = && || | ||
169 | |||
170 | {OT_integer, "integer", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT | ST_MODULO | ST_BITWISE)}, // int boolean * / + - % == != < > <= >= = += -= *= /= %= & | ^ << >> | ||
171 | {OT_integer, "integer", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT | ST_MODULO | ST_BITWISE)}, // int int * / + - % == != < > <= >= = += -= *= /= %= & | ^ << >> | ||
172 | {OT_float, "float", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT)}, // int float cast to float float | ||
173 | {OT_float, "float", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT)}, // float int cast to float float | ||
174 | {OT_float, "float", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT)}, // float float * / + - == != < > <= >= = += -= *= /= | ||
175 | |||
176 | {OT_string, "string", (ST_ADD | ST_EQUALITY | ST_CONCATENATION)}, // key key cast to string string | ||
177 | {OT_string, "string", (ST_ADD | ST_EQUALITY | ST_CONCATENATION)}, // key string cast to string string | ||
178 | {OT_string, "string", (ST_ADD | ST_EQUALITY | ST_CONCATENATION)}, // string key cast to string string | ||
179 | {OT_string, "string", (ST_ADD | ST_EQUALITY | ST_CONCATENATION)}, // string string + == != = += | ||
180 | |||
181 | {OT_list, "list", (ST_ADD | ST_EQUALITY | ST_CONCATENATION | ST_ASSIGNMENT )}, // list list + == != = += | ||
182 | {OT_list, "list", (ST_ADD | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT )}, // list boolean + < > <= >= = += | ||
183 | {OT_list, "list", (ST_ADD | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT )}, // list integer + < > <= >= = += | ||
184 | {OT_list, "list", (ST_ADD | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT )}, // list float + < > <= >= = += | ||
185 | {OT_list, "list", (ST_ADD | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT )}, // list string + < > <= >= = += | ||
186 | {OT_integer, "integer", (ST_ADD | ST_COMPARISON)}, // integer list + < > <= >= | ||
187 | {OT_float, "float", (ST_ADD | ST_COMPARISON)}, // float list + < > <= >= | ||
188 | {OT_list, "list", (ST_ADD | ST_CONCATENATION)}, // list other + = += | ||
189 | |||
190 | {OT_vector, "vector", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_CONCATENATION | ST_ASSIGNMENT | ST_MODULO)}, // vector vector * / + - % == != = += -= *= /= %= | ||
191 | {OT_vector, "vector", (ST_MULTIPLY | ST_ASSIGNMENT)}, // vector float * / *= /= | ||
192 | {OT_vector, "vector", (ST_MULTIPLY)}, // vector rotation * / | ||
193 | |||
194 | {OT_rotation, "rotation", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_CONCATENATION | ST_ASSIGNMENT)}, // rotation rotation * / + - == != = += -= *= /= | ||
195 | |||
196 | {OT_other, "other", (ST_NONE)}, // | ||
197 | {OT_undeclared, "undeclared", (ST_NONE)}, // | ||
198 | {OT_invalid, "invalid", (ST_NONE)} // | ||
199 | }; | ||
200 | |||
201 | opType opExpr[][10] = | ||
202 | { | ||
203 | {OT_nothing, OT_bool, OT_integer, OT_float, OT_key, OT_list, OT_rotation, OT_string, OT_vector, OT_other}, | ||
204 | {OT_bool, OT_boolBool, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid}, | ||
205 | {OT_integer, OT_intBool, OT_intInt, OT_intFloat, OT_invalid, OT_intList, OT_invalid, OT_invalid, OT_invalid, OT_invalid}, | ||
206 | {OT_float, OT_invalid, OT_floatInt, OT_floatFloat, OT_invalid, OT_floatList, OT_invalid, OT_invalid, OT_invalid, OT_invalid}, | ||
207 | {OT_key, OT_invalid, OT_invalid, OT_invalid, OT_keyKey, OT_invalid, OT_invalid, OT_keyString, OT_invalid, OT_invalid}, | ||
208 | {OT_list, OT_listBool, OT_listInt, OT_listFloat, OT_invalid, OT_listList, OT_invalid, OT_listString, OT_invalid, OT_listOther}, | ||
209 | {OT_rotation, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_rotationRotation, OT_invalid, OT_invalid, OT_invalid}, | ||
210 | {OT_string, OT_invalid, OT_invalid, OT_invalid, OT_stringKey, OT_invalid, OT_invalid, OT_stringString, OT_invalid, OT_invalid}, | ||
211 | {OT_vector, OT_invalid, OT_invalid, OT_vectorFloat, OT_invalid, OT_invalid, OT_vectorRotation, OT_invalid, OT_vectorVector, OT_invalid}, | ||
212 | {OT_other, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_otherOther} | ||
213 | }; | ||
214 | |||
215 | |||
216 | LSL_Token **tokens = NULL; | ||
217 | LSL_Script constants; | ||
218 | int lowestToken = 999999; | ||
219 | |||
220 | |||
221 | static LSL_Leaf *newLeaf(LSL_Type type, LSL_Leaf *left, LSL_Leaf *right) | ||
222 | { | ||
223 | LSL_Leaf *leaf = calloc(1, sizeof(LSL_Leaf)); | ||
224 | |||
225 | if (leaf) | ||
226 | { | ||
227 | leaf->left = left; | ||
228 | leaf->right = right; | ||
229 | leaf->toKen = tokens[type - lowestToken]; | ||
230 | } | ||
231 | |||
232 | return leaf; | ||
233 | } | ||
234 | |||
235 | void burnLeaf(void *data) | ||
236 | { | ||
237 | LSL_Leaf *leaf = data; | ||
238 | |||
239 | if (leaf) | ||
240 | { | ||
241 | // TODO - the problem here is that lemon wants to free these after a reduce, but we might want to keep them around. Should ref count them or something. | ||
242 | // burnLeaf(leaf->left); | ||
243 | // burnLeaf(leaf->right); | ||
244 | // TODO - Should free up the value to. | ||
245 | //#if LUASL_DIFF_CHECK | ||
246 | // eina_strbuf_free(leaf->ignorable); | ||
247 | //#endif | ||
248 | // free(leaf); | ||
249 | } | ||
250 | } | ||
251 | |||
252 | static LSL_Leaf *findFunction(LuaSL_compiler *compiler, const char *name) | ||
253 | { | ||
254 | LSL_Leaf *func = NULL; | ||
255 | |||
256 | if (name) | ||
257 | { | ||
258 | if (NULL == func) | ||
259 | func = eina_hash_find(constants.functions, name); | ||
260 | if (NULL != func) | ||
261 | { | ||
262 | func->flags |= MF_LSLCONST; | ||
263 | func->value.functionValue->flags |= MF_LSLCONST; | ||
264 | } | ||
265 | else | ||
266 | func = eina_hash_find(compiler->script.functions, name); | ||
267 | |||
268 | } | ||
269 | |||
270 | return func; | ||
271 | } | ||
272 | |||
273 | static LSL_Leaf *findVariable(LuaSL_compiler *compiler, const char *name) | ||
274 | { | ||
275 | LSL_Leaf *var = NULL; | ||
276 | |||
277 | if (name) | ||
278 | { | ||
279 | LSL_Block *block = compiler->currentBlock; | ||
280 | |||
281 | while ((block) && (NULL == var)) | ||
282 | { | ||
283 | if (block->function) | ||
284 | { | ||
285 | LSL_Leaf *param = NULL; | ||
286 | EINA_INARRAY_FOREACH((&(block->function->vars)), param) | ||
287 | { | ||
288 | if ((param) && (LSL_PARAMETER == param->toKen->type)) | ||
289 | { | ||
290 | // if (name == param->value.identifierValue->name.text) // Assuming they are stringshares. | ||
291 | if (0 == strcmp(name, param->value.identifierValue->name.text)) // Not assuming they are stringeshares. They should be. | ||
292 | var = param; | ||
293 | } | ||
294 | } | ||
295 | } | ||
296 | if ((NULL == var) && block->variables) | ||
297 | var = eina_hash_find(block->variables, name); | ||
298 | block = block->outerBlock; | ||
299 | } | ||
300 | |||
301 | if (NULL == var) | ||
302 | { | ||
303 | var = eina_hash_find(constants.variables, name); | ||
304 | if (var) | ||
305 | { | ||
306 | var->flags |= MF_LSLCONST; | ||
307 | var->value.identifierValue->flags |= MF_LSLCONST; | ||
308 | } | ||
309 | } | ||
310 | if (NULL == var) | ||
311 | var = eina_hash_find(compiler->script.variables, name); | ||
312 | } | ||
313 | |||
314 | return var; | ||
315 | } | ||
316 | |||
317 | LSL_Leaf *checkVariable(LuaSL_compiler *compiler, LSL_Leaf *identifier, LSL_Leaf *dot, LSL_Leaf *sub) | ||
318 | { | ||
319 | gameGlobals *ourGlobals = compiler->game; | ||
320 | const char *search; | ||
321 | |||
322 | if (dot) | ||
323 | search = identifier->value.identifierValue->name.text; | ||
324 | else | ||
325 | search = identifier->value.stringValue; | ||
326 | |||
327 | if (identifier) | ||
328 | { | ||
329 | LSL_Leaf *var = findVariable(compiler, search); | ||
330 | |||
331 | if (var) | ||
332 | { | ||
333 | if (LUASL_DEBUG) | ||
334 | PI("Found %s!", identifier->value.stringValue); | ||
335 | identifier->value.identifierValue = var->value.identifierValue; | ||
336 | identifier->basicType = var->basicType; | ||
337 | if ((dot) && (sub)) | ||
338 | { | ||
339 | LSL_Identifier *id = calloc(1, sizeof(LSL_Identifier)); | ||
340 | |||
341 | if (id) | ||
342 | { | ||
343 | memcpy(id, var->value.identifierValue, sizeof(LSL_Identifier)); | ||
344 | identifier->value.identifierValue = id; | ||
345 | if (LSL_ROTATION == var->toKen->type) | ||
346 | { | ||
347 | // TODO - check if it's one of x, y, z, or s. | ||
348 | } | ||
349 | if (LSL_VECTOR == var->toKen->type) | ||
350 | { | ||
351 | // TODO - check if it's one of x, y, or z. | ||
352 | } | ||
353 | identifier->value.identifierValue->sub = sub->value.stringValue; | ||
354 | identifier->basicType = OT_float; | ||
355 | } | ||
356 | } | ||
357 | } | ||
358 | else | ||
359 | { | ||
360 | compiler->script.bugCount++; | ||
361 | sendBack(ourGlobals, compiler->client, compiler->SID, "compilerError(%d,%d,NOT found %s)", identifier->line, identifier->column, identifier->value.stringValue); | ||
362 | } | ||
363 | } | ||
364 | |||
365 | return identifier; | ||
366 | } | ||
367 | |||
368 | LSL_Leaf *addOperation(LuaSL_compiler *compiler, LSL_Leaf *left, LSL_Leaf *lval, LSL_Leaf *right) | ||
369 | { | ||
370 | gameGlobals *ourGlobals = compiler->game; | ||
371 | |||
372 | if (lval) | ||
373 | { | ||
374 | opType lType, rType; | ||
375 | |||
376 | lval->left = left; | ||
377 | lval->right = right; | ||
378 | |||
379 | // Convert subtract to negate if needed. | ||
380 | if ((NULL == left) && (LSL_SUBTRACT == lval->toKen->type)) | ||
381 | lval->toKen = tokens[LSL_NEGATION - lowestToken]; | ||
382 | |||
383 | // Try to figure out what type of operation this is. | ||
384 | if (NULL == left) | ||
385 | lType = OT_nothing; | ||
386 | else | ||
387 | { | ||
388 | if ((left->toKen) && (LSL_IDENTIFIER == left->toKen->type) && (left->value.identifierValue)) | ||
389 | { | ||
390 | LSL_Leaf *var = findVariable(compiler, left->value.identifierValue->name.text); | ||
391 | |||
392 | if (var) | ||
393 | lType = var->basicType; | ||
394 | if (left->value.identifierValue->sub) | ||
395 | { | ||
396 | // TODO - keep an eye on this, but I think all the sub types are floats. | ||
397 | lType = OT_float; | ||
398 | } | ||
399 | } | ||
400 | else | ||
401 | lType = left->basicType; | ||
402 | if (OT_undeclared == lType) | ||
403 | { | ||
404 | compiler->script.warningCount++; | ||
405 | sendBack(ourGlobals, compiler->client, compiler->SID, "compilerWarning(%d,%d,Undeclared identifier issue, deferring this until the second pass)", lval->line, lval->column); | ||
406 | lval->basicType = OT_undeclared; | ||
407 | return lval; | ||
408 | } | ||
409 | if (OT_vector < lType) | ||
410 | lType = allowed[lType].result; | ||
411 | } | ||
412 | if (NULL == right) | ||
413 | rType = OT_nothing; | ||
414 | else | ||
415 | { | ||
416 | if ((right->toKen) && (LSL_IDENTIFIER == right->toKen->type) && (right->value.identifierValue)) | ||
417 | { | ||
418 | LSL_Leaf *var = findVariable(compiler, right->value.identifierValue->name.text); | ||
419 | |||
420 | if (var) | ||
421 | rType = var->basicType; | ||
422 | if (right->value.identifierValue->sub) | ||
423 | { | ||
424 | // TODO - keep an eye on this, but I think all the sub types are floats. | ||
425 | rType = OT_float; | ||
426 | } | ||
427 | } | ||
428 | else | ||
429 | rType = right->basicType; | ||
430 | if (OT_undeclared == rType) | ||
431 | { | ||
432 | compiler->script.warningCount++; | ||
433 | sendBack(ourGlobals, compiler->client, compiler->SID, "compilerWarning(%d,%d,Undeclared identifier issue, deferring this until the second pass)", lval->line, lval->column); | ||
434 | lval->basicType = OT_undeclared; | ||
435 | return lval; | ||
436 | } | ||
437 | if (OT_vector < rType) | ||
438 | rType = allowed[rType].result; | ||
439 | } | ||
440 | |||
441 | // Convert add to concatenate if needed. | ||
442 | if ((LSL_ADD == lval->toKen->type) && (OT_string == lType) && (OT_string == rType)) | ||
443 | lval->toKen = tokens[LSL_CONCATENATE - lowestToken]; | ||
444 | |||
445 | switch (lval->toKen->subType) | ||
446 | { | ||
447 | case ST_BOOLEAN : | ||
448 | case ST_COMPARISON : | ||
449 | case ST_EQUALITY : | ||
450 | lval->basicType = OT_bool; | ||
451 | break; | ||
452 | default : | ||
453 | // The basic lookup. | ||
454 | lval->basicType = opExpr[lType][rType]; | ||
455 | if (OT_invalid != lval->basicType) | ||
456 | { | ||
457 | // Check if it's an allowed operation. | ||
458 | if (0 == (lval->toKen->subType & allowed[lval->basicType].subTypes)) | ||
459 | lval->basicType = OT_invalid; | ||
460 | else | ||
461 | { | ||
462 | // Double check the corner cases. | ||
463 | switch (lval->toKen->subType) | ||
464 | { | ||
465 | case ST_MULTIPLY : | ||
466 | if (OT_vectorVector == lval->basicType) | ||
467 | { | ||
468 | if (LSL_MULTIPLY == lval->toKen->type) | ||
469 | { | ||
470 | lval->basicType = OT_float; | ||
471 | lval->toKen = tokens[LSL_DOT_PRODUCT - lowestToken]; | ||
472 | } | ||
473 | else | ||
474 | lval->basicType = OT_vector; | ||
475 | } | ||
476 | break; | ||
477 | default : | ||
478 | break; | ||
479 | } | ||
480 | } | ||
481 | } | ||
482 | break; | ||
483 | } | ||
484 | |||
485 | /* Flag assignments for the "assignments are statements, which can't happen inside expressions" test. | ||
486 | * | ||
487 | * Assignments in the middle of expressions are legal in LSL, but not in Lua. | ||
488 | * The big complication is that they often happen in the conditionals of flow control statements. That's a big bitch. | ||
489 | * | ||
490 | * So things like - | ||
491 | * | ||
492 | * while ((x = doSomething()) == foo) | ||
493 | * { | ||
494 | * buggerAround(); | ||
495 | * } | ||
496 | * | ||
497 | * Turns into - | ||
498 | * | ||
499 | * x = doSomething(); | ||
500 | * while (x == foo) | ||
501 | * { | ||
502 | * buggerAround(); | ||
503 | * x = doSomething(); | ||
504 | * } | ||
505 | * | ||
506 | * http://lua-users.org/wiki/StatementsInExpressions was helpful. Which suggests something like this - | ||
507 | * | ||
508 | * while ( (function() x = doSomething(); return x; end)() == foo) | ||
509 | * { | ||
510 | * buggerAround(); | ||
511 | * } | ||
512 | * | ||
513 | * The remaining problem is when to recognise the need to do that. | ||
514 | * That's what this code and the matching code in addParenthesis() does. | ||
515 | */ | ||
516 | // TODO - Only got one of these in my test scripts, so leave all this debugging shit in until it's been tested more. | ||
517 | if (left) | ||
518 | { | ||
519 | if (left->flags & MF_ASSIGNEXP) | ||
520 | { | ||
521 | //if ((left) && (right)) | ||
522 | // printf("%s %s %s\n", left->toKen->toKen, lval->toKen->toKen, right->toKen->toKen); | ||
523 | //else if (left) | ||
524 | // printf("%s %s NORIGHT\n", left->toKen->toKen, lval->toKen->toKen); | ||
525 | //else if (right) | ||
526 | // printf("NOLEFT %s %s\n", lval->toKen->toKen, right->toKen->toKen); | ||
527 | //else | ||
528 | // printf("NOLEFT %s NORIGHT\n", lval->toKen->toKen); | ||
529 | // printf("############################################################################## left\n"); | ||
530 | left->flags |= MF_WRAPFUNC; | ||
531 | if (LSL_PARENTHESIS_OPEN == left->toKen->type) | ||
532 | left->value.parenthesis->flags |= MF_WRAPFUNC; | ||
533 | } | ||
534 | } | ||
535 | if (lval) | ||
536 | { | ||
537 | // if (lval->flags & MF_ASSIGNEXP) | ||
538 | // printf("############################################################################## lval %s %s\n", left->toKen->toKen, right->toKen->toKen); | ||
539 | if (LSL_ASSIGNMENT & lval->toKen->flags) | ||
540 | { | ||
541 | lval->flags |= MF_ASSIGNEXP; | ||
542 | //// printf("******************* lval %s %s\n", left->toKen->toKen, right->toKen->toKen); | ||
543 | if (LSL_IDENTIFIER == left->toKen->type) // It always should be. | ||
544 | { | ||
545 | left->flags |= MF_ASSIGNEXP; | ||
546 | //// printf("$$$$$$$$$$$$$$$$$ lval\n"); | ||
547 | } | ||
548 | } | ||
549 | } | ||
550 | // TODO - Don't think I have to do this on the right. | ||
551 | if (right) | ||
552 | { | ||
553 | if (right->flags & MF_ASSIGNEXP) | ||
554 | { | ||
555 | if ((left) && (right)) | ||
556 | printf("%s %s %s\n", left->toKen->toKen, lval->toKen->toKen, right->toKen->toKen); | ||
557 | else if (left) | ||
558 | printf("%s %s NORIGHT\n", left->toKen->toKen, lval->toKen->toKen); | ||
559 | else if (right) | ||
560 | printf("NOLEFT %s %s\n", lval->toKen->toKen, right->toKen->toKen); | ||
561 | else | ||
562 | printf("NOLEFT %s NORIGHT\n", lval->toKen->toKen); | ||
563 | printf("############################################################################## right\n"); | ||
564 | right->flags |= MF_WRAPFUNC; | ||
565 | } | ||
566 | } | ||
567 | |||
568 | if (OT_invalid == lval->basicType) | ||
569 | { | ||
570 | const char *leftType = "", *rightType = "", *leftToken = "", *rightToken = ""; | ||
571 | |||
572 | if (left) | ||
573 | { | ||
574 | if (left->toKen) | ||
575 | leftToken = left->toKen->toKen; | ||
576 | else | ||
577 | PE("BROKEN LEFT TOKEN!!!!!!!!!!!!!!!!!!"); | ||
578 | leftType = allowed[left->basicType].name; | ||
579 | } | ||
580 | if (right) | ||
581 | { | ||
582 | if (right->toKen) | ||
583 | rightToken = right->toKen->toKen; | ||
584 | else | ||
585 | PE("BROKEN RIGHT TOKEN!!!!!!!!!!!!!!!!!!"); | ||
586 | rightType = allowed[right->basicType].name; | ||
587 | } | ||
588 | |||
589 | compiler->script.bugCount++; | ||
590 | sendBack(ourGlobals, compiler->client, compiler->SID, "compilerError(%d,%d,Invalid operation [%s(%s) %s %s(%s)])", lval->line, lval->column, leftType, leftToken, lval->toKen->toKen, rightType, rightToken); | ||
591 | } | ||
592 | } | ||
593 | |||
594 | return lval; | ||
595 | } | ||
596 | |||
597 | LSL_Leaf *addBlock(LuaSL_compiler *compiler, LSL_Leaf *left, LSL_Leaf *lval, LSL_Leaf *right) | ||
598 | { | ||
599 | // Damn, look ahead. The } symbol is getting read (and thus endBlock called) before the last statement in the block is reduced (which actually calls the add*() functions). | ||
600 | compiler->currentBlock = compiler->currentBlock->outerBlock; | ||
601 | #if LUASL_DIFF_CHECK | ||
602 | if ((left) && (right)) | ||
603 | { | ||
604 | left->value.blockValue->closeIgnorable = right->ignorable; | ||
605 | right->ignorable = NULL; | ||
606 | } | ||
607 | #endif | ||
608 | return lval; | ||
609 | } | ||
610 | |||
611 | LSL_Leaf *addCrement(LuaSL_compiler *compiler, LSL_Leaf *variable, LSL_Leaf *crement, LSL_Type type) | ||
612 | { | ||
613 | if ((variable) && (crement)) | ||
614 | { | ||
615 | crement->value.identifierValue = variable->value.identifierValue; | ||
616 | #if LUASL_DIFF_CHECK | ||
617 | crement->value.identifierValue->ignorable = variable->ignorable; | ||
618 | variable->ignorable = NULL; | ||
619 | #endif | ||
620 | crement->basicType = variable->basicType; | ||
621 | crement->toKen = tokens[type - lowestToken]; | ||
622 | switch (crement->toKen->type) | ||
623 | { | ||
624 | case LSL_DECREMENT_PRE : variable->value.identifierValue->flags |= MF_PREDEC; break; | ||
625 | case LSL_INCREMENT_PRE : variable->value.identifierValue->flags |= MF_PREINC; break; | ||
626 | case LSL_DECREMENT_POST : variable->value.identifierValue->flags |= MF_POSTDEC; break; | ||
627 | case LSL_INCREMENT_POST : variable->value.identifierValue->flags |= MF_POSTINC; break; | ||
628 | } | ||
629 | variable->value.identifierValue->definition->flags = variable->value.identifierValue->flags; | ||
630 | } | ||
631 | |||
632 | return crement; | ||
633 | } | ||
634 | |||
635 | LSL_Leaf *addParameter(LuaSL_compiler *compiler, LSL_Leaf *type, LSL_Leaf *identifier) | ||
636 | { | ||
637 | LSL_Identifier *result = calloc(1, sizeof(LSL_Identifier)); | ||
638 | |||
639 | if ( (identifier) && (result)) | ||
640 | { | ||
641 | result->name.text = identifier->value.stringValue; | ||
642 | #if LUASL_DIFF_CHECK | ||
643 | result->name.ignorable = identifier->ignorable; | ||
644 | identifier->ignorable = NULL; | ||
645 | #endif | ||
646 | result->value.toKen = tokens[LSL_UNKNOWN - lowestToken]; | ||
647 | identifier->value.identifierValue = result; | ||
648 | identifier->toKen = tokens[LSL_PARAMETER - lowestToken]; | ||
649 | identifier->left = type; | ||
650 | if (type) | ||
651 | { | ||
652 | identifier->basicType = type->basicType; | ||
653 | result->value.basicType = type->basicType; | ||
654 | result->value.toKen = type->toKen; // This is the LSL_TYPE_* toKen instead of the LSL_* toKen. Not sure if that's a problem. | ||
655 | } | ||
656 | } | ||
657 | return identifier; | ||
658 | } | ||
659 | |||
660 | LSL_Leaf *collectParameters(LuaSL_compiler *compiler, LSL_Leaf *list, LSL_Leaf *comma, LSL_Leaf *newParam) | ||
661 | { | ||
662 | LSL_Function *func = NULL; | ||
663 | |||
664 | if (NULL == list) | ||
665 | list = newLeaf(LSL_FUNCTION, NULL, NULL); | ||
666 | |||
667 | if (list) | ||
668 | { | ||
669 | func = list->value.functionValue; | ||
670 | if (NULL == func) | ||
671 | { | ||
672 | func = calloc(1, sizeof(LSL_Function)); | ||
673 | if (func) | ||
674 | { | ||
675 | list->value.functionValue = func; | ||
676 | eina_inarray_step_set(&(func->vars), sizeof(Eina_Inarray), sizeof(LSL_Leaf), 3); | ||
677 | } | ||
678 | } | ||
679 | |||
680 | if (func) | ||
681 | { | ||
682 | if (newParam) | ||
683 | { | ||
684 | if (LUASL_DIFF_CHECK) | ||
685 | { | ||
686 | // Stash the comma for diff later. | ||
687 | if (comma) | ||
688 | eina_inarray_push(&(func->vars), comma); | ||
689 | } | ||
690 | eina_inarray_push(&(func->vars), newParam); | ||
691 | // At this point, pointers to newParams are not pointing to the one in func->vars, AND newParam is no longer needed. | ||
692 | } | ||
693 | } | ||
694 | } | ||
695 | return list; | ||
696 | } | ||
697 | |||
698 | LSL_Leaf *addFunction(LuaSL_compiler *compiler, LSL_Leaf *type, LSL_Leaf *identifier, LSL_Leaf *open, LSL_Leaf *params, LSL_Leaf *close) | ||
699 | { | ||
700 | LSL_Function *func = NULL; | ||
701 | |||
702 | if (params) | ||
703 | { | ||
704 | func = params->value.functionValue; | ||
705 | // At this point, params is no longer needed, except if we are doing diff. | ||
706 | // open and close are not needed either if we are not doing diff. | ||
707 | if (func) | ||
708 | { | ||
709 | if (identifier) | ||
710 | { | ||
711 | func->name.text = identifier->value.stringValue; | ||
712 | #if LUASL_DIFF_CHECK | ||
713 | func->name.ignorable = identifier->ignorable; | ||
714 | identifier->ignorable = NULL; | ||
715 | #endif | ||
716 | identifier->toKen = tokens[LSL_FUNCTION - lowestToken]; | ||
717 | identifier->value.functionValue = func; | ||
718 | if (type) | ||
719 | { | ||
720 | func->type.text = type->toKen->toKen; | ||
721 | #if LUASL_DIFF_CHECK | ||
722 | func->type.ignorable = type->ignorable; | ||
723 | type->ignorable = NULL; | ||
724 | #endif | ||
725 | identifier->basicType = type->basicType; | ||
726 | } | ||
727 | else | ||
728 | identifier->basicType = OT_nothing; | ||
729 | if (compiler->inState) | ||
730 | eina_hash_add(compiler->state.handlers, func->name.text, func); | ||
731 | else | ||
732 | eina_hash_add(compiler->script.functions, func->name.text, identifier); | ||
733 | #if LUASL_DIFF_CHECK | ||
734 | // func->params = addParenthesis(open, params, LSL_PARAMETER_LIST, close); | ||
735 | #endif | ||
736 | } | ||
737 | compiler->currentFunction = func; | ||
738 | } | ||
739 | } | ||
740 | |||
741 | return identifier; | ||
742 | } | ||
743 | |||
744 | LSL_Leaf *addFunctionBody(LuaSL_compiler *compiler, LSL_Leaf *function, LSL_Leaf *block) | ||
745 | { | ||
746 | LSL_Leaf *statement = NULL; | ||
747 | |||
748 | if (function) | ||
749 | { | ||
750 | function->value.functionValue->block = block->value.blockValue; | ||
751 | statement = addStatement(compiler, NULL, function, NULL, function, NULL, NULL, NULL); | ||
752 | } | ||
753 | |||
754 | return statement; | ||
755 | } | ||
756 | |||
757 | LSL_Leaf *collectArguments(LuaSL_compiler *compiler, LSL_Leaf *list, LSL_Leaf *comma, LSL_Leaf *arg) | ||
758 | { | ||
759 | LSL_FunctionCall *call = NULL; | ||
760 | |||
761 | if (NULL == list) | ||
762 | list = newLeaf(LSL_FUNCTION_CALL, NULL, NULL); | ||
763 | |||
764 | if (list) | ||
765 | { | ||
766 | call = list->value.functionCallValue; | ||
767 | if (NULL == call) | ||
768 | { | ||
769 | call = calloc(1, sizeof(LSL_FunctionCall)); | ||
770 | if (call) | ||
771 | { | ||
772 | list->value.functionCallValue = call; | ||
773 | eina_inarray_step_set(&(call->params), sizeof(Eina_Inarray), sizeof(LSL_Leaf), 3); | ||
774 | } | ||
775 | } | ||
776 | |||
777 | if (call) | ||
778 | { | ||
779 | if (arg) | ||
780 | { | ||
781 | if (LUASL_DIFF_CHECK) | ||
782 | { | ||
783 | // Stash the comma for diff later. | ||
784 | if (comma) | ||
785 | eina_inarray_push(&(call->params), comma); | ||
786 | } | ||
787 | eina_inarray_push(&(call->params), arg); | ||
788 | // At this point, pointers to arg are not pointing to the one in call->params, AND arg is no longer needed. | ||
789 | } | ||
790 | } | ||
791 | } | ||
792 | return list; | ||
793 | } | ||
794 | |||
795 | LSL_Leaf *addFunctionCall(LuaSL_compiler *compiler, LSL_Leaf *identifier, LSL_Leaf *open, LSL_Leaf *params, LSL_Leaf *close) | ||
796 | { | ||
797 | LSL_Leaf *func = findFunction(compiler, identifier->value.stringValue); | ||
798 | LSL_FunctionCall *call = NULL; | ||
799 | |||
800 | if (params) | ||
801 | { | ||
802 | call = params->value.functionCallValue; | ||
803 | } | ||
804 | else | ||
805 | call = calloc(1, sizeof(LSL_FunctionCall)); | ||
806 | |||
807 | if (func) | ||
808 | { | ||
809 | if (call) | ||
810 | { | ||
811 | call->function = func->value.functionValue; | ||
812 | eina_clist_element_init(&(call->dangler)); | ||
813 | } | ||
814 | identifier->value.functionCallValue = call; | ||
815 | identifier->toKen = tokens[LSL_FUNCTION_CALL - lowestToken]; | ||
816 | identifier->basicType = func->basicType; | ||
817 | } | ||
818 | else | ||
819 | { | ||
820 | // It may be declared later, so store it and check later. | ||
821 | if (call) | ||
822 | { | ||
823 | eina_clist_add_tail(&(compiler->danglingCalls), &(call->dangler)); | ||
824 | call->call = identifier; | ||
825 | } | ||
826 | // Here the identifier stringValue needs to be kept for later searching. | ||
827 | identifier->toKen = tokens[LSL_UNKNOWN - lowestToken]; | ||
828 | identifier->basicType = OT_undeclared; | ||
829 | compiler->undeclared = TRUE; | ||
830 | } | ||
831 | |||
832 | return identifier; | ||
833 | } | ||
834 | |||
835 | LSL_Leaf *addList(LSL_Leaf *left, LSL_Leaf *list, LSL_Leaf *right) | ||
836 | { | ||
837 | left = addParenthesis(left, list, LSL_LIST, right); | ||
838 | left->toKen = tokens[LSL_LIST - lowestToken]; | ||
839 | left->basicType = OT_list; | ||
840 | return left; | ||
841 | } | ||
842 | |||
843 | LSL_Leaf *addRotVec(LSL_Leaf *left, LSL_Leaf *list, LSL_Leaf *right) | ||
844 | { | ||
845 | LSL_Type type = LSL_ROTATION; | ||
846 | opType otype = OT_rotation; | ||
847 | |||
848 | // TODO - count the members of list to see if it's a vector. | ||
849 | left = addParenthesis(left, list, type, right); | ||
850 | left->toKen = tokens[type - lowestToken]; | ||
851 | left->basicType = otype; | ||
852 | return left; | ||
853 | } | ||
854 | |||
855 | LSL_Leaf *addNumby(LSL_Leaf *numby) | ||
856 | { | ||
857 | LSL_Numby *num = calloc(1, sizeof(LSL_Numby)); | ||
858 | |||
859 | if ((numby) && (num)) | ||
860 | { | ||
861 | num->text.text = numby->value.stringValue; | ||
862 | #if LUASL_DIFF_CHECK | ||
863 | num->text.ignorable = numby->ignorable; | ||
864 | numby->ignorable = NULL; | ||
865 | #endif | ||
866 | switch (numby->toKen->type) | ||
867 | { | ||
868 | case LSL_FLOAT : | ||
869 | { | ||
870 | num->value.floatValue = atof(num->text.text); | ||
871 | numby->basicType = OT_float; | ||
872 | break; | ||
873 | } | ||
874 | case LSL_INTEGER : | ||
875 | { | ||
876 | num->value.integerValue = atoi(num->text.text); | ||
877 | numby->basicType = OT_integer; | ||
878 | break; | ||
879 | } | ||
880 | default: | ||
881 | break; | ||
882 | } | ||
883 | numby->value.numbyValue = num; | ||
884 | num->type = numby->basicType; | ||
885 | } | ||
886 | return numby; | ||
887 | } | ||
888 | |||
889 | LSL_Leaf *addParenthesis(LSL_Leaf *lval, LSL_Leaf *expr, LSL_Type type, LSL_Leaf *rval) | ||
890 | { | ||
891 | LSL_Parenthesis *parens = calloc(1, sizeof(LSL_Parenthesis)); | ||
892 | |||
893 | if (parens) | ||
894 | { | ||
895 | parens->contents = expr; | ||
896 | parens->type = type; | ||
897 | #if LUASL_DIFF_CHECK | ||
898 | parens->rightIgnorable = rval->ignorable; | ||
899 | // Actualy, at this point, rval is no longer needed. | ||
900 | rval->ignorable = NULL; | ||
901 | #endif | ||
902 | if (lval) | ||
903 | { | ||
904 | lval->value.parenthesis = parens; | ||
905 | if (expr) | ||
906 | { | ||
907 | lval->basicType = expr->basicType; | ||
908 | // Propagate these flags inwards and outwards. | ||
909 | if (MF_ASSIGNEXP & expr->flags) | ||
910 | lval->flags |= MF_ASSIGNEXP; | ||
911 | if (MF_WRAPFUNC & expr->flags) | ||
912 | parens->flags |= MF_WRAPFUNC; | ||
913 | } | ||
914 | } | ||
915 | } | ||
916 | return lval; | ||
917 | } | ||
918 | |||
919 | LSL_Leaf *addState(LuaSL_compiler *compiler, LSL_Leaf *state, LSL_Leaf *identifier, LSL_Leaf *block) | ||
920 | { | ||
921 | LSL_State *result = calloc(1, sizeof(LSL_State)); | ||
922 | |||
923 | if ((identifier) && (result)) | ||
924 | { | ||
925 | Eina_Iterator *handlers; | ||
926 | LSL_Function *func; | ||
927 | |||
928 | memcpy(result, &(compiler->state), sizeof(LSL_State)); | ||
929 | compiler->state.block = NULL; | ||
930 | compiler->state.handlers = NULL; | ||
931 | result->name.text = identifier->value.stringValue; | ||
932 | #if LUASL_DIFF_CHECK | ||
933 | result->name.ignorable = identifier->ignorable; | ||
934 | identifier->ignorable = NULL; | ||
935 | #endif | ||
936 | handlers = eina_hash_iterator_data_new(result->handlers); | ||
937 | while(eina_iterator_next(handlers, (void **) &func)) | ||
938 | { | ||
939 | func->state = result->name.text; | ||
940 | } | ||
941 | result->block = block->value.blockValue; | ||
942 | if (state) | ||
943 | { | ||
944 | result->state.text = state->toKen->toKen; | ||
945 | #if LUASL_DIFF_CHECK | ||
946 | result->state.ignorable = state->ignorable; | ||
947 | state->ignorable = NULL; | ||
948 | #endif | ||
949 | } | ||
950 | identifier->value.stateValue = result; | ||
951 | identifier->toKen = tokens[LSL_STATE - lowestToken]; | ||
952 | eina_hash_add(compiler->script.states, result->name.text, identifier); | ||
953 | compiler->inState = FALSE; | ||
954 | } | ||
955 | |||
956 | return identifier; | ||
957 | } | ||
958 | |||
959 | LSL_Leaf *addIfElse(LuaSL_compiler *compiler, LSL_Leaf *ifBlock, LSL_Leaf *elseBlock) | ||
960 | { | ||
961 | if (ifBlock->value.statementValue->elseBlock) | ||
962 | { | ||
963 | LSL_Statement *oldElseIf = ifBlock->value.statementValue->elseBlock; | ||
964 | |||
965 | while (oldElseIf->elseBlock) | ||
966 | oldElseIf = oldElseIf->elseBlock; | ||
967 | |||
968 | oldElseIf->elseBlock = elseBlock->value.statementValue; | ||
969 | } | ||
970 | else | ||
971 | ifBlock->value.statementValue->elseBlock = elseBlock->value.statementValue; | ||
972 | return ifBlock; | ||
973 | } | ||
974 | |||
975 | LSL_Leaf *addFor(LuaSL_compiler *compiler, LSL_Leaf *lval, LSL_Leaf *flow, LSL_Leaf *left, LSL_Leaf *expr0, LSL_Leaf *stat0, LSL_Leaf *expr1, LSL_Leaf *stat1, LSL_Leaf *expr2, LSL_Leaf *right, LSL_Leaf *block) | ||
976 | { | ||
977 | LSL_Leaf **exprs = calloc(5, sizeof(LSL_Leaf *)); | ||
978 | |||
979 | if (exprs) | ||
980 | { | ||
981 | lval = addStatement(compiler, lval, flow, left, expr0, right, block, NULL); | ||
982 | exprs[0] = expr0; | ||
983 | exprs[1] = stat0; | ||
984 | exprs[2] = expr1; | ||
985 | exprs[3] = stat1; | ||
986 | exprs[4] = expr2; | ||
987 | lval->value.statementValue->expressions = (LSL_Leaf *) exprs; | ||
988 | } | ||
989 | return lval; | ||
990 | } | ||
991 | |||
992 | LSL_Leaf *addStatement(LuaSL_compiler *compiler, LSL_Leaf *lval, LSL_Leaf *flow, LSL_Leaf *left, LSL_Leaf *expr, LSL_Leaf *right, LSL_Leaf *block, LSL_Leaf *identifier) | ||
993 | { | ||
994 | gameGlobals *ourGlobals = compiler->game; | ||
995 | LSL_Statement *stat = calloc(1, sizeof(LSL_Statement)); | ||
996 | boolean justOne = FALSE; | ||
997 | |||
998 | if (NULL == lval) | ||
999 | lval = newLeaf(LSL_STATEMENT, NULL, NULL); | ||
1000 | |||
1001 | if (stat) | ||
1002 | { | ||
1003 | stat->type = flow->toKen->type; | ||
1004 | stat->expressions = expr; | ||
1005 | if (block) | ||
1006 | { | ||
1007 | if (LSL_BLOCK_OPEN == block->toKen->type) | ||
1008 | stat->block = block->value.blockValue; | ||
1009 | else | ||
1010 | stat->single = block->value.statementValue; | ||
1011 | } | ||
1012 | eina_clist_element_init(&(stat->statement)); | ||
1013 | if (identifier) | ||
1014 | { | ||
1015 | stat->identifier.text = identifier->value.stringValue; | ||
1016 | #if LUASL_DIFF_CHECK | ||
1017 | stat->identifier.ignorable = identifier->ignorable; | ||
1018 | identifier->ignorable = NULL; | ||
1019 | #endif | ||
1020 | } | ||
1021 | if (left) | ||
1022 | { | ||
1023 | LSL_Leaf *parens = addParenthesis(left, expr, LSL_EXPRESSION, right); | ||
1024 | |||
1025 | if (parens) | ||
1026 | stat->parenthesis = parens->value.parenthesis; | ||
1027 | } | ||
1028 | |||
1029 | switch (stat->type) | ||
1030 | { | ||
1031 | case LSL_EXPRESSION : | ||
1032 | { | ||
1033 | break; | ||
1034 | } | ||
1035 | case LSL_FUNCTION : | ||
1036 | { | ||
1037 | break; | ||
1038 | } | ||
1039 | case LSL_DO : | ||
1040 | { | ||
1041 | break; | ||
1042 | } | ||
1043 | case LSL_FOR : | ||
1044 | { | ||
1045 | justOne = TRUE; | ||
1046 | break; | ||
1047 | } | ||
1048 | case LSL_IF : | ||
1049 | { | ||
1050 | justOne = TRUE; | ||
1051 | break; | ||
1052 | } | ||
1053 | case LSL_ELSE : | ||
1054 | { | ||
1055 | justOne = TRUE; | ||
1056 | break; | ||
1057 | } | ||
1058 | case LSL_ELSEIF : | ||
1059 | { | ||
1060 | justOne = TRUE; | ||
1061 | break; | ||
1062 | } | ||
1063 | case LSL_JUMP : | ||
1064 | { | ||
1065 | justOne = TRUE; | ||
1066 | break; | ||
1067 | } | ||
1068 | case LSL_LABEL : | ||
1069 | { | ||
1070 | justOne = TRUE; | ||
1071 | break; | ||
1072 | } | ||
1073 | case LSL_RETURN : | ||
1074 | { | ||
1075 | justOne = TRUE; | ||
1076 | break; | ||
1077 | } | ||
1078 | case LSL_STATE_CHANGE : | ||
1079 | { | ||
1080 | justOne = TRUE; | ||
1081 | break; | ||
1082 | } | ||
1083 | case LSL_STATEMENT : | ||
1084 | { | ||
1085 | break; | ||
1086 | } | ||
1087 | case LSL_WHILE : | ||
1088 | { | ||
1089 | stat->identifier.text = NULL; | ||
1090 | justOne = TRUE; | ||
1091 | break; | ||
1092 | } | ||
1093 | case LSL_IDENTIFIER : | ||
1094 | { | ||
1095 | break; | ||
1096 | } | ||
1097 | case LSL_VARIABLE : | ||
1098 | { | ||
1099 | if (identifier) | ||
1100 | { | ||
1101 | stat->identifier.text = identifier->value.identifierValue->name.text; | ||
1102 | identifier->value.identifierValue->definition = stat; | ||
1103 | stat->flags = identifier->value.identifierValue->flags; | ||
1104 | } | ||
1105 | break; | ||
1106 | } | ||
1107 | default : | ||
1108 | { | ||
1109 | compiler->script.bugCount++; | ||
1110 | PE("Should not be here %d.", stat->type); | ||
1111 | break; | ||
1112 | } | ||
1113 | } | ||
1114 | |||
1115 | #if LUASL_DIFF_CHECK | ||
1116 | if (justOne && (flow)) | ||
1117 | { | ||
1118 | stat->ignorable = calloc(2, sizeof(Eina_Strbuf *)); | ||
1119 | if (stat->ignorable) | ||
1120 | { | ||
1121 | stat->ignorable[1] = flow->ignorable; | ||
1122 | flow->ignorable = NULL; | ||
1123 | } | ||
1124 | } | ||
1125 | #endif | ||
1126 | |||
1127 | if (lval) | ||
1128 | { | ||
1129 | #if LUASL_DIFF_CHECK | ||
1130 | if (NULL == stat->ignorable) | ||
1131 | stat->ignorable = calloc(1, sizeof(Eina_Strbuf *)); | ||
1132 | if (stat->ignorable) | ||
1133 | { | ||
1134 | stat->ignorable[0] = lval->ignorable; | ||
1135 | lval->ignorable = NULL; | ||
1136 | } | ||
1137 | #endif | ||
1138 | lval->value.statementValue = stat; | ||
1139 | } | ||
1140 | |||
1141 | #if LUASL_DIFF_CHECK | ||
1142 | if (left) | ||
1143 | { | ||
1144 | if (NULL == stat->ignorable) | ||
1145 | stat->ignorable = calloc(3, sizeof(Eina_Strbuf *)); | ||
1146 | else | ||
1147 | stat->ignorable = realloc(stat->ignorable, 3 * sizeof(Eina_Strbuf *)); | ||
1148 | if (stat->ignorable) | ||
1149 | { | ||
1150 | stat->ignorable[2] = left->ignorable; | ||
1151 | left->ignorable = NULL; | ||
1152 | } | ||
1153 | } | ||
1154 | #endif | ||
1155 | } | ||
1156 | |||
1157 | return lval; | ||
1158 | } | ||
1159 | |||
1160 | LSL_Leaf *collectStatements(LuaSL_compiler *compiler, LSL_Leaf *list, LSL_Leaf *statement) | ||
1161 | { | ||
1162 | // Guess this is not needed after all, and seemed to cause the "states with only one function get dropped" bug. | ||
1163 | // boolean wasNull = FALSE; | ||
1164 | |||
1165 | if (NULL == list) | ||
1166 | { | ||
1167 | list = newLeaf(LSL_BLOCK_OPEN, NULL, NULL); | ||
1168 | // wasNull = TRUE; | ||
1169 | } | ||
1170 | |||
1171 | if (list) | ||
1172 | { | ||
1173 | if (statement) | ||
1174 | { | ||
1175 | // if (!wasNull) | ||
1176 | list->value.blockValue = compiler->currentBlock; // Maybe NULL. | ||
1177 | |||
1178 | if ((compiler->inState) && (LSL_FUNCTION == statement->value.statementValue->type)) | ||
1179 | { | ||
1180 | eina_clist_add_tail(&(compiler->state.block->statements), &(statement->value.statementValue->statement)); | ||
1181 | } | ||
1182 | else if (list->value.blockValue) | ||
1183 | { | ||
1184 | eina_clist_add_tail(&(list->value.blockValue->statements), &(statement->value.statementValue->statement)); | ||
1185 | } | ||
1186 | } | ||
1187 | } | ||
1188 | |||
1189 | return list; | ||
1190 | } | ||
1191 | |||
1192 | /* Typecasting | ||
1193 | |||
1194 | LSL is statically typed, so stored values are not converted, only the values used in expressions are. | ||
1195 | Lua is dynamically typed, so stored values are changed (sometimes I think). | ||
1196 | |||
1197 | LSL implicitly typecasts - There is a shitload of QUIRKs about this. Apparently some don't work anyway. | ||
1198 | integer -> float (Says in lslwiki that precision is never lost, which is bullshit, since they are both 32 bit. Would be true if the float is 64 bit. Lua suggest to use 64 bit floats to emulate 32 bit integers.) | ||
1199 | string -> key | ||
1200 | Some functions need help with this or the other way around. | ||
1201 | string -> vector (Maybe, should test that.) | ||
1202 | vector -> string (Maybe, should test that.) | ||
1203 | Also happens when getting stuff from lists. | ||
1204 | |||
1205 | Explicit type casting - | ||
1206 | string -> integer | ||
1207 | Leading spaces are ignored, as are any characters after the run of digits. | ||
1208 | All other strings convert to 0. | ||
1209 | Which means "" and " " convert to 0. | ||
1210 | Strings in hexadecimal format will work, same in Lua (though Lua can't handle "0x", but "0x0" is fine). | ||
1211 | keys <-> string | ||
1212 | No other typecasting can be done with keys. | ||
1213 | float -> string | ||
1214 | You get a bunch of trailing 0s. | ||
1215 | |||
1216 | QUIRK - I have seen cases where a double explicit typecast was needed in SL, but was considered to be invalid syntax in OS. | ||
1217 | |||
1218 | Any binary operation involving a float and an integer implicitly casts the integer to float. | ||
1219 | |||
1220 | A boolean operation deals with TRUE (1) and FALSE (0). Any non zero value is a TRUE (generally sigh). | ||
1221 | On the other hand, in Lua, only false and nil are false, everything else is true. 0 is true. sigh | ||
1222 | Bitwise operations only apply to integers. Right shifts are arithmetic, not logical. | ||
1223 | |||
1224 | integer = integer0 % integer1; // Apparently only applies to integers, but works fine on floats in OS. | ||
1225 | string = string0 + string1; // Concatenation. | ||
1226 | list = list0 + list1; // Concatenation. Also works if either is not a list, it's promoted to a list first. | ||
1227 | list = (list=[]) + list + ["new_item"]; // Voodoo needed for old LSL, works in Mono but not needed, does not work in OS. Works for strings to. | ||
1228 | bool = list == != int // Only compares the lengths, probably applies to the other conditionals to. | ||
1229 | vector = vector0 + vector1; // Add elements together. | ||
1230 | vector = vector0 - vector1; // Subtract elements of vector1 from elements of vector0. | ||
1231 | float = vector0 * vector1; // A dot product of the vectors. | ||
1232 | vector = vector0 % vector1; // A cross product of the vectors. | ||
1233 | vector = vector * float; // Scale the vector, works the other way around I think. Works for integer to, but it will end up being cast to float. | ||
1234 | vector = vector / float; // Scale the vector, works the other way around I think. Works for integer to, but it will end up being cast to float. | ||
1235 | vector = vector * rotation; // Rotate the vector by the rotation. Other way around wont compile. | ||
1236 | vector = vector / rotation; // Rotate the vector by the rotation, in the opposite direction. Other way around wont compile. | ||
1237 | rotation = llGetRot() * rotation; // Rotate an object around the global axis. | ||
1238 | rotation = rotation * llGetLocalRot(); // Rotate an object around the local axis. | ||
1239 | rotation = rotation0 * rotation1; // Add two rotations, so the result is as if you applied each rotation one after the other. | ||
1240 | // Division rotates in the opposite direction. | ||
1241 | rotation = rotation0 + rotation1; // Similar to vector, but it's a meaningless thing as far as rotations go. | ||
1242 | rotation = rotation0 - rotation1; // Similar to vector, but it's a meaningless thing as far as rotations go. | ||
1243 | |||
1244 | A boolean operator results in a boolean value. (any types) | ||
1245 | A comparison operator results in a boolean value. (any types) | ||
1246 | A bitwise operator results in an integer value. (intInt or int) | ||
1247 | A dot product operator results in a float value. (vector * vector) | ||
1248 | A vectorFloat results in a vector value. | ||
1249 | |||
1250 | */ | ||
1251 | |||
1252 | LSL_Leaf *addTypecast(LSL_Leaf *lval, LSL_Leaf *type, LSL_Leaf *rval, LSL_Leaf *expr) | ||
1253 | { | ||
1254 | addParenthesis(lval, expr, LSL_TYPECAST_OPEN, rval); | ||
1255 | if (lval) | ||
1256 | { | ||
1257 | if (type) | ||
1258 | { | ||
1259 | lval->basicType = type->basicType; | ||
1260 | if ((expr) && (OT_integer == type->basicType)) // TODO - Should be from string, but I guess I'm not propagating basic types up from function calls and parenthesis? | ||
1261 | lval->value.parenthesis->flags |= MF_TYPECAST; | ||
1262 | } | ||
1263 | // Actualy, at this point, type is no longer needed. | ||
1264 | lval->toKen = tokens[LSL_TYPECAST_OPEN - lowestToken]; | ||
1265 | } | ||
1266 | // if (rval) | ||
1267 | // rval->toKen = tokens[LSL_TYPECAST_CLOSE - lowestToken]; | ||
1268 | |||
1269 | return lval; | ||
1270 | } | ||
1271 | |||
1272 | LSL_Leaf *addVariable(LuaSL_compiler *compiler, LSL_Leaf *type, LSL_Leaf *identifier, LSL_Leaf *assignment, LSL_Leaf *expr) | ||
1273 | { | ||
1274 | LSL_Identifier *result = calloc(1, sizeof(LSL_Identifier)); | ||
1275 | |||
1276 | if ( (identifier) && (result)) | ||
1277 | { | ||
1278 | result->name.text = identifier->value.stringValue; | ||
1279 | #if LUASL_DIFF_CHECK | ||
1280 | result->name.ignorable = identifier->ignorable; | ||
1281 | identifier->ignorable = NULL; | ||
1282 | #endif | ||
1283 | result->value.toKen = tokens[LSL_UNKNOWN - lowestToken]; | ||
1284 | identifier->value.identifierValue = result; | ||
1285 | identifier->toKen = tokens[LSL_VARIABLE - lowestToken]; | ||
1286 | identifier->left = type; | ||
1287 | identifier->right = assignment; | ||
1288 | if (assignment) | ||
1289 | assignment->right = expr; | ||
1290 | else | ||
1291 | identifier->flags |= MF_NOASSIGN; | ||
1292 | if (type) | ||
1293 | { | ||
1294 | if (compiler->currentBlock) | ||
1295 | { | ||
1296 | identifier->flags |= MF_LOCAL; | ||
1297 | result->flags |= MF_LOCAL; | ||
1298 | type->flags |= MF_LOCAL; | ||
1299 | } | ||
1300 | identifier->basicType = type->basicType; | ||
1301 | result->value.basicType = type->basicType; | ||
1302 | result->value.toKen = type->toKen; // This is the LSL_TYPE_* toKen instead of the LSL_* toKen. Not sure if that's a problem. | ||
1303 | } | ||
1304 | if (compiler->currentBlock) | ||
1305 | eina_hash_add(compiler->currentBlock->variables, result->name.text, identifier); | ||
1306 | else | ||
1307 | eina_hash_add(compiler->script.variables, result->name.text, identifier); | ||
1308 | } | ||
1309 | |||
1310 | return identifier; | ||
1311 | } | ||
1312 | |||
1313 | LSL_Leaf *beginBlock(LuaSL_compiler *compiler, LSL_Leaf *block) | ||
1314 | { | ||
1315 | LSL_Block *blok = calloc(1, sizeof(LSL_Block)); | ||
1316 | |||
1317 | if (blok) | ||
1318 | { | ||
1319 | eina_clist_init(&(blok->statements)); | ||
1320 | blok->variables = eina_hash_stringshared_new(burnLeaf); | ||
1321 | block->value.blockValue = blok; | ||
1322 | if ((NULL == compiler->currentBlock) && (NULL == compiler->currentFunction)) | ||
1323 | { | ||
1324 | compiler->inState = TRUE; | ||
1325 | compiler->state.block=blok; | ||
1326 | compiler->state.handlers = eina_hash_stringshared_new(free); | ||
1327 | } | ||
1328 | blok->outerBlock = compiler->currentBlock; | ||
1329 | compiler->currentBlock = blok; | ||
1330 | blok->function = compiler->currentFunction; | ||
1331 | compiler->currentFunction = NULL; | ||
1332 | #if LUASL_DIFF_CHECK | ||
1333 | blok->openIgnorable = block->ignorable; | ||
1334 | block->ignorable = NULL; | ||
1335 | #endif | ||
1336 | } | ||
1337 | return block; | ||
1338 | } | ||
1339 | |||
1340 | static void secondPass(LuaSL_compiler *compiler, LSL_Leaf *leaf) | ||
1341 | { | ||
1342 | if (leaf) | ||
1343 | { | ||
1344 | secondPass(compiler, leaf->left); | ||
1345 | if (OT_undeclared == leaf->basicType) | ||
1346 | leaf = addOperation(compiler, leaf->left, leaf, leaf->right); | ||
1347 | secondPass(compiler, leaf->right); | ||
1348 | } | ||
1349 | } | ||
1350 | |||
1351 | static void outputLeaf(FILE *file, outputMode mode, LSL_Leaf *leaf) | ||
1352 | { | ||
1353 | if (leaf) | ||
1354 | { | ||
1355 | if ((OM_LUA == mode) &&(ST_BITWISE != leaf->toKen->subType)) | ||
1356 | outputLeaf(file, mode, leaf->left); | ||
1357 | #if LUASL_DIFF_CHECK | ||
1358 | if ((!(LSL_NOIGNORE & leaf->toKen->flags)) && (leaf->ignorable)) | ||
1359 | fwrite(eina_strbuf_string_get(leaf->ignorable), 1, eina_strbuf_length_get(leaf->ignorable), file); | ||
1360 | #endif | ||
1361 | if (leaf->toKen->output) | ||
1362 | leaf->toKen->output(file, mode, leaf); | ||
1363 | else | ||
1364 | { | ||
1365 | if (OM_LUA == mode) | ||
1366 | { | ||
1367 | if (MF_WRAPFUNC & leaf->flags) | ||
1368 | { | ||
1369 | // TODO - Leaving this here in case we trip over one. | ||
1370 | if ((leaf->left) && (leaf->right)) | ||
1371 | printf("%s %s %s\n", leaf->left->toKen->toKen, leaf->toKen->toKen, leaf->right->toKen->toKen); | ||
1372 | else if (leaf->left) | ||
1373 | printf("%s %s NORIGHT\n", leaf->left->toKen->toKen, leaf->toKen->toKen); | ||
1374 | else if (leaf->right) | ||
1375 | printf("NOLEFT %s %s\n", leaf->toKen->toKen, leaf->right->toKen->toKen); | ||
1376 | else | ||
1377 | printf("NOLEFT %s NORIGHT\n", leaf->toKen->toKen); | ||
1378 | } | ||
1379 | if ((LSL_ASSIGNMENT & leaf->toKen->flags) && (LSL_ASSIGNMENT_PLAIN != leaf->toKen->type)) | ||
1380 | { | ||
1381 | if (leaf->left->value.identifierValue->sub) | ||
1382 | fprintf(file, " --[[%s]] = %s.%s %.1s ", leaf->toKen->toKen, leaf->left->value.identifierValue->name.text, leaf->left->value.identifierValue->sub, leaf->toKen->toKen); | ||
1383 | else | ||
1384 | fprintf(file, " --[[%s]] = %s %.1s ", leaf->toKen->toKen, leaf->left->value.identifierValue->name.text, leaf->toKen->toKen); | ||
1385 | } | ||
1386 | else if (LSL_TYPE & leaf->toKen->flags) | ||
1387 | { | ||
1388 | if (MF_LOCAL & leaf->flags) | ||
1389 | fprintf(file, " local "); | ||
1390 | fprintf(file, " --[[%s]] ", leaf->toKen->toKen); | ||
1391 | } | ||
1392 | else if (LSL_BOOL_AND == leaf->toKen->type) | ||
1393 | fprintf(file, " and "); | ||
1394 | else if (LSL_BOOL_OR == leaf->toKen->type) | ||
1395 | fprintf(file, " or "); | ||
1396 | else if (LSL_BOOL_NOT == leaf->toKen->type) | ||
1397 | fprintf(file, " not "); | ||
1398 | else if (LSL_CONCATENATE == leaf->toKen->type) | ||
1399 | fprintf(file, " .. "); | ||
1400 | else if (LSL_NOT_EQUAL == leaf->toKen->type) | ||
1401 | fprintf(file, " ~= "); | ||
1402 | else | ||
1403 | fprintf(file, "%s", leaf->toKen->toKen); | ||
1404 | } | ||
1405 | else | ||
1406 | fprintf(file, "%s", leaf->toKen->toKen); | ||
1407 | } | ||
1408 | if ((OM_LUA == mode) &&(ST_BITWISE != leaf->toKen->subType)) | ||
1409 | outputLeaf(file, mode, leaf->right); | ||
1410 | } | ||
1411 | } | ||
1412 | |||
1413 | // Circular references, so declare this one first. | ||
1414 | static void outputRawStatement(FILE *file, outputMode mode, LSL_Statement *statement); | ||
1415 | |||
1416 | static void outputRawBlock(FILE *file, outputMode mode, LSL_Block *block, boolean doEnd) | ||
1417 | { | ||
1418 | if (block) | ||
1419 | { | ||
1420 | LSL_Statement *stat = NULL; | ||
1421 | |||
1422 | #if LUASL_DIFF_CHECK | ||
1423 | if (block->openIgnorable) | ||
1424 | fwrite(eina_strbuf_string_get(block->openIgnorable), 1, eina_strbuf_length_get(block->openIgnorable), file); | ||
1425 | if (OM_LSL == mode) | ||
1426 | fprintf(file, "{"); | ||
1427 | #else | ||
1428 | if (OM_LSL == mode) | ||
1429 | fprintf(file, "\n{\n"); | ||
1430 | else if (doEnd && (OM_LUA == mode)) | ||
1431 | fprintf(file, "\n"); | ||
1432 | #endif | ||
1433 | EINA_CLIST_FOR_EACH_ENTRY(stat, &(block->statements), LSL_Statement, statement) | ||
1434 | { | ||
1435 | outputRawStatement(file, mode, stat); | ||
1436 | } | ||
1437 | #if LUASL_DIFF_CHECK | ||
1438 | if (block->closeIgnorable) | ||
1439 | fwrite(eina_strbuf_string_get(block->closeIgnorable), 1, eina_strbuf_length_get(block->closeIgnorable), file); | ||
1440 | #endif | ||
1441 | if (OM_LSL == mode) | ||
1442 | fprintf(file, "}"); | ||
1443 | else if (doEnd && (OM_LUA == mode)) | ||
1444 | fprintf(file, "end "); | ||
1445 | } | ||
1446 | } | ||
1447 | |||
1448 | // TODO - should clean this up by refactoring the bits in the switch outside. | ||
1449 | static void outputRawParenthesisToken(FILE *file, outputMode mode, LSL_Parenthesis *parenthesis, const char *typeName) | ||
1450 | { | ||
1451 | if ((OM_LUA == mode) && (LSL_TYPECAST_OPEN == parenthesis->type)) | ||
1452 | { | ||
1453 | if (MF_TYPECAST & parenthesis->flags) | ||
1454 | fprintf(file, " _LSL.%sTypecast(", typeName); | ||
1455 | else | ||
1456 | fprintf(file, " --[[%s]] ", typeName); | ||
1457 | outputLeaf(file, mode, parenthesis->contents); | ||
1458 | if (MF_TYPECAST & parenthesis->flags) | ||
1459 | fprintf(file, ") "); | ||
1460 | return; | ||
1461 | } | ||
1462 | |||
1463 | if ((OM_LUA == mode) && (MF_WRAPFUNC & parenthesis->flags)) | ||
1464 | fprintf(file, " (function() "); | ||
1465 | else | ||
1466 | fprintf(file, "("); | ||
1467 | if (LSL_TYPECAST_OPEN == parenthesis->type) | ||
1468 | fprintf(file, "%s", typeName); // TODO - We are missing the type ignorable text here. | ||
1469 | else | ||
1470 | outputLeaf(file, mode, parenthesis->contents); | ||
1471 | if ((OM_LUA == mode) && (MF_WRAPFUNC & parenthesis->flags)) | ||
1472 | fprintf(file, "; return x; end)() "); | ||
1473 | else | ||
1474 | { | ||
1475 | #if LUASL_DIFF_CHECK | ||
1476 | fprintf(file, "%s)", eina_strbuf_string_get(parenthesis->rightIgnorable)); | ||
1477 | #else | ||
1478 | fprintf(file, ")"); | ||
1479 | #endif | ||
1480 | } | ||
1481 | |||
1482 | if (LSL_TYPECAST_OPEN == parenthesis->type) | ||
1483 | outputLeaf(file, mode, parenthesis->contents); | ||
1484 | } | ||
1485 | |||
1486 | static void outputText(FILE *file, LSL_Text *text, boolean ignore) | ||
1487 | { | ||
1488 | if (text->text) | ||
1489 | { | ||
1490 | #if LUASL_DIFF_CHECK | ||
1491 | if (ignore && (text->ignorable)) | ||
1492 | fwrite(eina_strbuf_string_get(text->ignorable), 1, eina_strbuf_length_get(text->ignorable), file); | ||
1493 | #endif | ||
1494 | fprintf(file, "%s", text->text); | ||
1495 | } | ||
1496 | } | ||
1497 | |||
1498 | static void outputRawStatement(FILE *file, outputMode mode, LSL_Statement *statement) | ||
1499 | { | ||
1500 | boolean isBlock = FALSE; | ||
1501 | |||
1502 | if (statement) | ||
1503 | { | ||
1504 | switch (statement->type) | ||
1505 | { | ||
1506 | case LSL_EXPRESSION : | ||
1507 | { | ||
1508 | break; | ||
1509 | } | ||
1510 | case LSL_FUNCTION : | ||
1511 | { | ||
1512 | isBlock = TRUE; | ||
1513 | break; | ||
1514 | } | ||
1515 | case LSL_DO : | ||
1516 | { | ||
1517 | fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); | ||
1518 | break; | ||
1519 | } | ||
1520 | case LSL_FOR : | ||
1521 | { | ||
1522 | #if LUASL_DIFF_CHECK | ||
1523 | if ((statement->ignorable) && (statement->ignorable[1])) | ||
1524 | fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); | ||
1525 | #endif | ||
1526 | if (OM_LSL == mode) | ||
1527 | { | ||
1528 | isBlock = TRUE; | ||
1529 | fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); | ||
1530 | } | ||
1531 | else if (OM_LUA == mode) | ||
1532 | { | ||
1533 | LSL_Leaf **exprs = (LSL_Leaf **) statement->expressions; | ||
1534 | |||
1535 | outputLeaf(file, mode, exprs[0]); | ||
1536 | fprintf(file, ";\nwhile ("); | ||
1537 | outputLeaf(file, mode, exprs[2]); | ||
1538 | #if LUASL_DIFF_CHECK | ||
1539 | fprintf(file, "%s)\n", eina_strbuf_string_get(statement->parenthesis->rightIgnorable)); | ||
1540 | #else | ||
1541 | fprintf(file, ") do\n"); | ||
1542 | #endif | ||
1543 | if (statement->block) | ||
1544 | outputRawBlock(file, mode, statement->block, FALSE); | ||
1545 | if (statement->single) | ||
1546 | outputRawStatement(file, mode, statement->single); | ||
1547 | fprintf(file, "\n"); | ||
1548 | outputLeaf(file, mode, exprs[4]); | ||
1549 | fprintf(file, ";\nend\n"); | ||
1550 | return; | ||
1551 | } | ||
1552 | break; | ||
1553 | } | ||
1554 | case LSL_IF : | ||
1555 | case LSL_ELSE : | ||
1556 | case LSL_ELSEIF : | ||
1557 | { | ||
1558 | isBlock = TRUE; | ||
1559 | #if LUASL_DIFF_CHECK | ||
1560 | if ((statement->ignorable) && (statement->ignorable[1])) | ||
1561 | fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); | ||
1562 | #endif | ||
1563 | fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); | ||
1564 | if (OM_LUA == mode) | ||
1565 | { | ||
1566 | fprintf(file, " "); | ||
1567 | if (LSL_ELSE != statement->type) | ||
1568 | { | ||
1569 | if (statement->parenthesis) | ||
1570 | outputRawParenthesisToken(file, mode, statement->parenthesis, ""); | ||
1571 | else | ||
1572 | outputLeaf(file, mode, statement->expressions); | ||
1573 | fprintf(file, " then\n"); | ||
1574 | } | ||
1575 | if (statement->block) | ||
1576 | outputRawBlock(file, mode, statement->block, FALSE); | ||
1577 | if (statement->single) | ||
1578 | outputRawStatement(file, mode, statement->single); | ||
1579 | if (statement->elseBlock) | ||
1580 | outputRawStatement(file, mode, statement->elseBlock); | ||
1581 | if (LSL_IF == statement->type) | ||
1582 | { | ||
1583 | #if 1 | ||
1584 | fprintf(file, " end\n"); | ||
1585 | #else | ||
1586 | fprintf(file, " end --[["); | ||
1587 | if (statement->parenthesis) | ||
1588 | outputRawParenthesisToken(file, mode, statement->parenthesis, ""); | ||
1589 | else | ||
1590 | outputLeaf(file, mode, statement->expressions); | ||
1591 | fprintf(file, "]]\n"); | ||
1592 | #endif | ||
1593 | } | ||
1594 | return; | ||
1595 | } | ||
1596 | break; | ||
1597 | } | ||
1598 | case LSL_JUMP : | ||
1599 | { | ||
1600 | #if LUASL_DIFF_CHECK | ||
1601 | if ((statement->ignorable) && (statement->ignorable[1])) | ||
1602 | fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); | ||
1603 | #endif | ||
1604 | fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); | ||
1605 | break; | ||
1606 | } | ||
1607 | case LSL_LABEL : | ||
1608 | { | ||
1609 | #if LUASL_DIFF_CHECK | ||
1610 | if ((statement->ignorable) && (statement->ignorable[1])) | ||
1611 | fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); | ||
1612 | #endif | ||
1613 | fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); | ||
1614 | break; | ||
1615 | } | ||
1616 | case LSL_RETURN : | ||
1617 | { | ||
1618 | #if LUASL_DIFF_CHECK | ||
1619 | if ((statement->ignorable) && (statement->ignorable[1])) | ||
1620 | fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); | ||
1621 | #endif | ||
1622 | fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); | ||
1623 | break; | ||
1624 | } | ||
1625 | case LSL_STATE_CHANGE : | ||
1626 | { | ||
1627 | #if LUASL_DIFF_CHECK | ||
1628 | if ((statement->ignorable) && (statement->ignorable[1])) | ||
1629 | fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); | ||
1630 | #endif | ||
1631 | if (OM_LSL == mode) | ||
1632 | { | ||
1633 | fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); | ||
1634 | if (statement->identifier.text) | ||
1635 | outputText(file, &(statement->identifier), TRUE); | ||
1636 | } | ||
1637 | else if (OM_LUA == mode) | ||
1638 | { | ||
1639 | fprintf(file, "return _LSL.stateChange(_"); | ||
1640 | if (statement->identifier.text) | ||
1641 | outputText(file, &(statement->identifier), TRUE); | ||
1642 | fprintf(file, "State)"); | ||
1643 | } | ||
1644 | break; | ||
1645 | } | ||
1646 | case LSL_STATEMENT : | ||
1647 | { | ||
1648 | break; | ||
1649 | } | ||
1650 | case LSL_WHILE : | ||
1651 | { | ||
1652 | isBlock = TRUE; | ||
1653 | #if LUASL_DIFF_CHECK | ||
1654 | if ((statement->ignorable) && (statement->ignorable[1])) | ||
1655 | fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); | ||
1656 | #else | ||
1657 | if (OM_LUA == mode) | ||
1658 | fprintf(file, "\n"); | ||
1659 | #endif | ||
1660 | fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); | ||
1661 | if (OM_LUA == mode) | ||
1662 | { | ||
1663 | if (statement->parenthesis) | ||
1664 | outputRawParenthesisToken(file, mode, statement->parenthesis, ""); | ||
1665 | fprintf(file, " do "); | ||
1666 | if (statement->block) | ||
1667 | outputRawBlock(file, mode, statement->block, TRUE); | ||
1668 | if (statement->single) | ||
1669 | outputRawStatement(file, mode, statement->single); | ||
1670 | fprintf(file, "\n"); | ||
1671 | return; | ||
1672 | } | ||
1673 | break; | ||
1674 | } | ||
1675 | case LSL_IDENTIFIER : | ||
1676 | { | ||
1677 | break; | ||
1678 | } | ||
1679 | case LSL_VARIABLE : | ||
1680 | { | ||
1681 | break; | ||
1682 | } | ||
1683 | default : | ||
1684 | { | ||
1685 | fprintf(file, "@@Should not be here %s.@@", tokens[statement->type - lowestToken]->toKen); | ||
1686 | break; | ||
1687 | } | ||
1688 | } | ||
1689 | |||
1690 | #if LUASL_DIFF_CHECK | ||
1691 | if ((statement->ignorable) && (statement->ignorable[2])) | ||
1692 | fwrite(eina_strbuf_string_get(statement->ignorable[2]), 1, eina_strbuf_length_get(statement->ignorable[2]), file); | ||
1693 | #else | ||
1694 | if (OM_LUA == mode) | ||
1695 | fprintf(file, " "); | ||
1696 | #endif | ||
1697 | if (LSL_FOR == statement->type) | ||
1698 | { | ||
1699 | LSL_Leaf **exprs = (LSL_Leaf **) statement->expressions; | ||
1700 | int i; | ||
1701 | |||
1702 | fprintf(file, "("); | ||
1703 | for (i = 0; i < 5; i++) | ||
1704 | { | ||
1705 | outputLeaf(file, mode, exprs[i]); | ||
1706 | if (i % 2) | ||
1707 | fprintf(file, ";"); | ||
1708 | } | ||
1709 | #if LUASL_DIFF_CHECK | ||
1710 | fprintf(file, "%s)", eina_strbuf_string_get(statement->parenthesis->rightIgnorable)); | ||
1711 | #else | ||
1712 | fprintf(file, ")"); | ||
1713 | #endif | ||
1714 | } | ||
1715 | else if (statement->parenthesis) | ||
1716 | outputRawParenthesisToken(file, mode, statement->parenthesis, ""); | ||
1717 | else | ||
1718 | outputLeaf(file, mode, statement->expressions); | ||
1719 | |||
1720 | if (statement->block) | ||
1721 | outputRawBlock(file, mode, statement->block, TRUE); | ||
1722 | if (statement->single) | ||
1723 | outputRawStatement(file, mode, statement->single); | ||
1724 | |||
1725 | #if LUASL_DIFF_CHECK | ||
1726 | if ((statement->ignorable) && (statement->ignorable[0])) | ||
1727 | fwrite(eina_strbuf_string_get(statement->ignorable[0]), 1, eina_strbuf_length_get(statement->ignorable[0]), file); | ||
1728 | #endif | ||
1729 | |||
1730 | if (!isBlock) | ||
1731 | { | ||
1732 | fprintf(file, ";"); | ||
1733 | if (!LUASL_DIFF_CHECK) | ||
1734 | fprintf(file, "\n"); | ||
1735 | } | ||
1736 | |||
1737 | if ((LSL_VARIABLE == statement->type) && (OM_LUA == mode) && (MF_LOCAL & statement->flags)) | ||
1738 | { | ||
1739 | const char *name = statement->identifier.text; | ||
1740 | |||
1741 | // if ((MF_PREDEC | MF_PREINC | MF_POSTDEC | MF_POSTINC) & statement->flags) | ||
1742 | // fprintf(file, "\n"); | ||
1743 | if (MF_PREDEC & statement->flags) fprintf(file, "local function _preDecrement_%s() %s = %s - 1; return %s; end\n", name, name, name, name); | ||
1744 | if (MF_PREINC & statement->flags) fprintf(file, "local function _preIncrement_%s() %s = %s + 1; return %s; end\n", name, name, name, name); | ||
1745 | if (MF_POSTDEC & statement->flags) fprintf(file, "local function _postDecrement_%s() local _temp = %s; %s = %s - 1; return _temp; end\n", name, name, name, name); | ||
1746 | if (MF_POSTINC & statement->flags) fprintf(file, "local function _postDecrement_%s() local _temp = %s; %s = %s + 1; return _temp; end\n", name, name, name, name); | ||
1747 | } | ||
1748 | |||
1749 | if (statement->elseBlock) | ||
1750 | outputRawStatement(file, mode, statement->elseBlock); | ||
1751 | } | ||
1752 | } | ||
1753 | |||
1754 | static void outputBitOp(FILE *file, outputMode mode, LSL_Leaf *leaf) | ||
1755 | { | ||
1756 | if (OM_LSL == mode) | ||
1757 | outputLeaf(file, mode, leaf); | ||
1758 | else if (OM_LUA == mode) | ||
1759 | { | ||
1760 | switch (leaf->toKen->type) | ||
1761 | { | ||
1762 | case LSL_BIT_AND : fprintf(file, " _bit.band("); break; | ||
1763 | case LSL_BIT_OR : fprintf(file, " _bit.bor("); break; | ||
1764 | case LSL_BIT_XOR : fprintf(file, " _bit.xor("); break; | ||
1765 | case LSL_BIT_NOT : fprintf(file, " _bit.bnot("); break; | ||
1766 | case LSL_LEFT_SHIFT : fprintf(file, " _bit.lshift("); break; | ||
1767 | case LSL_RIGHT_SHIFT : fprintf(file, " _bit.arshift("); break; | ||
1768 | default : break; | ||
1769 | } | ||
1770 | outputLeaf(file, mode, leaf->left); | ||
1771 | if (LSL_BIT_NOT != leaf->toKen->type) | ||
1772 | { | ||
1773 | fprintf(file, ", "); | ||
1774 | outputLeaf(file, mode, leaf->right); | ||
1775 | } | ||
1776 | fprintf(file, ") "); | ||
1777 | } | ||
1778 | } | ||
1779 | |||
1780 | static void outputBlockToken(FILE *file, outputMode mode, LSL_Leaf *content) | ||
1781 | { | ||
1782 | if (content) | ||
1783 | outputRawBlock(file, mode, content->value.blockValue, TRUE); | ||
1784 | } | ||
1785 | |||
1786 | static void outputCrementsToken(FILE *file, outputMode mode, LSL_Leaf *content) | ||
1787 | { | ||
1788 | if (content) | ||
1789 | { | ||
1790 | if (OM_LSL == mode) | ||
1791 | { | ||
1792 | switch (content->toKen->type) | ||
1793 | { | ||
1794 | case LSL_DECREMENT_PRE : | ||
1795 | case LSL_INCREMENT_PRE : | ||
1796 | { | ||
1797 | fprintf(file, "%s", content->toKen->toKen); | ||
1798 | #if LUASL_DIFF_CHECK | ||
1799 | if (content->value.identifierValue->ignorable) | ||
1800 | fwrite(eina_strbuf_string_get(content->value.identifierValue->ignorable), 1, eina_strbuf_length_get(content->value.identifierValue->ignorable), file); | ||
1801 | #endif | ||
1802 | outputText(file, &(content->value.identifierValue->name), FALSE); | ||
1803 | break; | ||
1804 | } | ||
1805 | case LSL_DECREMENT_POST : | ||
1806 | case LSL_INCREMENT_POST : | ||
1807 | { | ||
1808 | #if LUASL_DIFF_CHECK | ||
1809 | if (content->value.identifierValue->ignorable) | ||
1810 | fwrite(eina_strbuf_string_get(content->value.identifierValue->ignorable), 1, eina_strbuf_length_get(content->value.identifierValue->ignorable), file); | ||
1811 | #endif | ||
1812 | outputText(file, &(content->value.identifierValue->name), FALSE); | ||
1813 | fprintf(file, "%s", content->toKen->toKen); | ||
1814 | break; | ||
1815 | } | ||
1816 | default : | ||
1817 | break; | ||
1818 | } | ||
1819 | } | ||
1820 | else if (OM_LUA == mode) | ||
1821 | { | ||
1822 | if (MF_LOCAL & content->value.identifierValue->flags) | ||
1823 | fprintf(file, " _"); | ||
1824 | else | ||
1825 | fprintf(file, " _LSL."); | ||
1826 | switch (content->toKen->type) | ||
1827 | { | ||
1828 | case LSL_DECREMENT_PRE : fprintf(file, "preDecrement"); break; | ||
1829 | case LSL_INCREMENT_PRE : fprintf(file, "preIncrement"); break; | ||
1830 | case LSL_DECREMENT_POST : fprintf(file, "postDecrement"); break; | ||
1831 | case LSL_INCREMENT_POST : fprintf(file, "postIncrement"); break; | ||
1832 | default : | ||
1833 | break; | ||
1834 | } | ||
1835 | if (MF_LOCAL & content->value.identifierValue->flags) | ||
1836 | fprintf(file, "_"); | ||
1837 | else | ||
1838 | fprintf(file, "(\""); | ||
1839 | #if LUASL_DIFF_CHECK | ||
1840 | if (content->value.identifierValue->ignorable) | ||
1841 | fwrite(eina_strbuf_string_get(content->value.identifierValue->ignorable), 1, eina_strbuf_length_get(content->value.identifierValue->ignorable), file); | ||
1842 | #endif | ||
1843 | outputText(file, &(content->value.identifierValue->name), FALSE); | ||
1844 | if (MF_LOCAL & content->value.identifierValue->flags) | ||
1845 | fprintf(file, "()"); | ||
1846 | else | ||
1847 | fprintf(file, "\")"); | ||
1848 | } | ||
1849 | } | ||
1850 | } | ||
1851 | |||
1852 | static void outputFloatToken(FILE *file, outputMode mode, LSL_Leaf *content) | ||
1853 | { | ||
1854 | if (content) | ||
1855 | outputText(file, &(content->value.numbyValue->text), !(LSL_NOIGNORE & content->toKen->flags)); | ||
1856 | } | ||
1857 | |||
1858 | static void outputFunctionToken(FILE *file, outputMode mode, LSL_Leaf *content) | ||
1859 | { | ||
1860 | if (content) | ||
1861 | { | ||
1862 | LSL_Function *func = content->value.functionValue; | ||
1863 | LSL_Leaf *param = NULL; | ||
1864 | int first = TRUE; | ||
1865 | |||
1866 | if (OM_LSL == mode) | ||
1867 | { | ||
1868 | outputText(file, &(func->type), !(LSL_NOIGNORE & content->toKen->flags)); | ||
1869 | outputText(file, &(func->name), !(LSL_NOIGNORE & content->toKen->flags)); | ||
1870 | // TODO - should print comma and parenthesis ignorables. | ||
1871 | fprintf(file, "("); | ||
1872 | EINA_INARRAY_FOREACH((&(func->vars)), param) | ||
1873 | { | ||
1874 | if (!LUASL_DIFF_CHECK) | ||
1875 | { | ||
1876 | if (!first) | ||
1877 | fprintf(file, ", "); | ||
1878 | } | ||
1879 | outputLeaf(file, mode, param); | ||
1880 | first = FALSE; | ||
1881 | } | ||
1882 | fprintf(file, ")"); | ||
1883 | outputRawBlock(file, mode, func->block, TRUE); | ||
1884 | if (!LUASL_DIFF_CHECK) | ||
1885 | fprintf(file, "\n"); | ||
1886 | } | ||
1887 | else if (OM_LUA == mode) | ||
1888 | { | ||
1889 | if (func->state) | ||
1890 | fprintf(file, "\n\n_%sState.%s = function(", func->state, func->name.text); | ||
1891 | else | ||
1892 | { | ||
1893 | fprintf(file, "\n\nfunction "); | ||
1894 | if (func->type.text) | ||
1895 | fprintf(file, " --[[%s]] ", func->type.text); | ||
1896 | fprintf(file, "%s(", func->name.text); | ||
1897 | } | ||
1898 | EINA_INARRAY_FOREACH((&(func->vars)), param) | ||
1899 | { | ||
1900 | // Commenting out the types is done in outputLeaf() which outputs all the types. | ||
1901 | if (!LUASL_DIFF_CHECK) | ||
1902 | { | ||
1903 | if (!first) | ||
1904 | fprintf(file, ", "); | ||
1905 | } | ||
1906 | outputLeaf(file, mode, param); | ||
1907 | first = FALSE; | ||
1908 | } | ||
1909 | fprintf(file, ")"); | ||
1910 | outputRawBlock(file, mode, func->block, TRUE); | ||
1911 | } | ||
1912 | } | ||
1913 | } | ||
1914 | |||
1915 | static void outputFunctionCallToken(FILE *file, outputMode mode, LSL_Leaf *content) | ||
1916 | { | ||
1917 | if (content) | ||
1918 | { | ||
1919 | LSL_FunctionCall *call = content->value.functionCallValue; | ||
1920 | LSL_Leaf *param = NULL; | ||
1921 | boolean first = TRUE; | ||
1922 | |||
1923 | // TODO - should output it's own ignorable here. | ||
1924 | if ((OM_LUA == mode) && (MF_LSLCONST & call->function->flags)) | ||
1925 | fprintf(file, "_LSL."); | ||
1926 | outputText(file, &(call->function->name), FALSE); // Don't output the function definitions ignorable. | ||
1927 | fprintf(file, "("); | ||
1928 | EINA_INARRAY_FOREACH((&(call->params)), param) | ||
1929 | { | ||
1930 | if ((OM_LUA == mode) && (!first)) | ||
1931 | fprintf(file, ", "); | ||
1932 | outputLeaf(file, mode, param); | ||
1933 | first = FALSE; | ||
1934 | } | ||
1935 | fprintf(file, ")"); | ||
1936 | } | ||
1937 | } | ||
1938 | |||
1939 | static void outputIntegerToken(FILE *file, outputMode mode, LSL_Leaf *content) | ||
1940 | { | ||
1941 | if (content) | ||
1942 | outputText(file, &(content->value.numbyValue->text), !(LSL_NOIGNORE & content->toKen->flags)); | ||
1943 | } | ||
1944 | |||
1945 | static void outputIdentifierToken(FILE *file, outputMode mode, LSL_Leaf *content) | ||
1946 | { | ||
1947 | if (content) | ||
1948 | { | ||
1949 | if (LSL_IDENTIFIER == content->toKen->type) | ||
1950 | { | ||
1951 | if ((OM_LUA == mode) && (MF_LSLCONST & content->value.identifierValue->flags)) | ||
1952 | fprintf(file, "_LSL."); | ||
1953 | outputText(file, &(content->value.identifierValue->name), FALSE); | ||
1954 | if (content->value.identifierValue->sub) | ||
1955 | fprintf(file, ".%s", content->value.identifierValue->sub); | ||
1956 | } | ||
1957 | else | ||
1958 | if ((LSL_VARIABLE == content->toKen->type) && (MF_NOASSIGN & content->flags)) | ||
1959 | { | ||
1960 | outputText(file, &(content->value.identifierValue->name), !(LSL_NOIGNORE & content->toKen->flags)); | ||
1961 | if (OM_LUA == mode) | ||
1962 | { | ||
1963 | switch (content->basicType) | ||
1964 | { | ||
1965 | case OT_bool : fprintf(file, " = false"); break; | ||
1966 | case OT_integer : fprintf(file, " = 0"); break; | ||
1967 | case OT_float : fprintf(file, " = 0.0"); break; | ||
1968 | case OT_key : fprintf(file, " = _LSL.NULL_KEY"); break; | ||
1969 | case OT_list : fprintf(file, " = {}"); break; | ||
1970 | case OT_rotation : fprintf(file, " = _LSL.ZERO_ROTATION"); break; | ||
1971 | case OT_string : fprintf(file, " = \"\""); break; | ||
1972 | case OT_vector : fprintf(file, " = _LSL.ZERO_VECTOR"); break; | ||
1973 | default : fprintf(file, " = nil"); break; | ||
1974 | } | ||
1975 | } | ||
1976 | } | ||
1977 | else | ||
1978 | outputText(file, &(content->value.identifierValue->name), !(LSL_NOIGNORE & content->toKen->flags)); | ||
1979 | } | ||
1980 | } | ||
1981 | |||
1982 | static void outputListToken(FILE *file, outputMode mode, LSL_Leaf *content) | ||
1983 | { | ||
1984 | if (content) | ||
1985 | { | ||
1986 | LSL_Parenthesis *parens = content->value.parenthesis; | ||
1987 | |||
1988 | if (parens->contents) | ||
1989 | { | ||
1990 | LSL_FunctionCall *call = parens->contents->value.functionCallValue; | ||
1991 | LSL_Leaf *param = NULL; | ||
1992 | const char *ig = ""; | ||
1993 | |||
1994 | // TODO - should output it's own ignorable here. | ||
1995 | if (OM_LSL == mode) | ||
1996 | { | ||
1997 | switch (parens->type) | ||
1998 | { | ||
1999 | case LSL_LIST : fprintf(file, "["); break; | ||
2000 | case LSL_ROTATION : | ||
2001 | case LSL_VECTOR : fprintf(file, "<"); | ||
2002 | default : break; | ||
2003 | } | ||
2004 | } | ||
2005 | else if (OM_LUA == mode) | ||
2006 | { | ||
2007 | switch (parens->type) | ||
2008 | { | ||
2009 | case LSL_LIST : fprintf(file, "{"); break; | ||
2010 | case LSL_ROTATION : | ||
2011 | case LSL_VECTOR : fprintf(file, "{"); | ||
2012 | default : break; | ||
2013 | } | ||
2014 | } | ||
2015 | EINA_INARRAY_FOREACH((&(call->params)), param) | ||
2016 | { | ||
2017 | outputLeaf(file, mode, param); | ||
2018 | if (OM_LUA == mode) | ||
2019 | fprintf(file, ", "); | ||
2020 | } | ||
2021 | #if LUASL_DIFF_CHECK | ||
2022 | ig = eina_strbuf_string_get(parens->rightIgnorable); | ||
2023 | #endif | ||
2024 | if (OM_LSL == mode) | ||
2025 | { | ||
2026 | switch (parens->type) | ||
2027 | { | ||
2028 | case LSL_LIST : fprintf(file, "%s]", ig); break; | ||
2029 | case LSL_ROTATION : | ||
2030 | case LSL_VECTOR : fprintf(file, "%s>", ig); | ||
2031 | default : break; | ||
2032 | } | ||
2033 | } | ||
2034 | else if (OM_LUA == mode) | ||
2035 | { | ||
2036 | switch (parens->type) | ||
2037 | { | ||
2038 | case LSL_LIST : fprintf(file, "%s}", ig); break; | ||
2039 | case LSL_ROTATION : | ||
2040 | case LSL_VECTOR : fprintf(file, "%s}", ig); | ||
2041 | default : break; | ||
2042 | } | ||
2043 | } | ||
2044 | } | ||
2045 | } | ||
2046 | } | ||
2047 | |||
2048 | static void outputParameterListToken(FILE *file, outputMode mode, LSL_Leaf *content) | ||
2049 | { | ||
2050 | if (content) | ||
2051 | outputLeaf(file, mode, content->value.listValue); | ||
2052 | // TODO - Should go through the list, and output any crements functions we need at the top of the block. | ||
2053 | } | ||
2054 | |||
2055 | static void outputParenthesisToken(FILE *file, outputMode mode, LSL_Leaf *content) | ||
2056 | { | ||
2057 | if (content) | ||
2058 | outputRawParenthesisToken(file, mode, content->value.parenthesis, allowed[content->basicType].name); | ||
2059 | } | ||
2060 | |||
2061 | static void outputStateToken(FILE *file, outputMode mode, LSL_Leaf *content) | ||
2062 | { | ||
2063 | if (content) | ||
2064 | { | ||
2065 | LSL_State *state = content->value.stateValue; | ||
2066 | |||
2067 | if (state) | ||
2068 | { | ||
2069 | if (OM_LSL == mode) | ||
2070 | { | ||
2071 | outputText(file, &(state->state), !(LSL_NOIGNORE & content->toKen->flags)); | ||
2072 | outputText(file, &(state->name), !(LSL_NOIGNORE & content->toKen->flags)); | ||
2073 | outputRawBlock(file, mode, state->block, TRUE); | ||
2074 | } | ||
2075 | else if (OM_LUA == mode) | ||
2076 | { | ||
2077 | fprintf(file, "\n\n--[[state]] _"); | ||
2078 | outputText(file, &(state->name), !(LSL_NOIGNORE & content->toKen->flags)); | ||
2079 | fprintf(file, "State = {};"); | ||
2080 | outputRawBlock(file, mode, state->block, FALSE); | ||
2081 | } | ||
2082 | } | ||
2083 | } | ||
2084 | } | ||
2085 | |||
2086 | static void outputStatementToken(FILE *file, outputMode mode, LSL_Leaf *content) | ||
2087 | { | ||
2088 | if (content) | ||
2089 | outputRawStatement(file, mode, content->value.statementValue); | ||
2090 | } | ||
2091 | |||
2092 | static void outputStringToken(FILE *file, outputMode mode, LSL_Leaf *content) | ||
2093 | { | ||
2094 | if (content) | ||
2095 | fprintf(file, "%s", content->value.stringValue); // The quotes are part of the string value already. | ||
2096 | } | ||
2097 | |||
2098 | boolean compilerSetup(gameGlobals *ourGlobals) | ||
2099 | { | ||
2100 | int i; | ||
2101 | |||
2102 | // Figure out what numbers lemon gave to our tokens. | ||
2103 | for (i = 0; LSL_Tokens[i].toKen != NULL; i++) | ||
2104 | { | ||
2105 | if (lowestToken > LSL_Tokens[i].type) | ||
2106 | lowestToken = LSL_Tokens[i].type; | ||
2107 | } | ||
2108 | tokens = calloc(i + 1, sizeof(LSL_Token *)); | ||
2109 | if (tokens) | ||
2110 | { | ||
2111 | char buf[PATH_MAX]; | ||
2112 | |||
2113 | // Sort the token table. | ||
2114 | for (i = 0; LSL_Tokens[i].toKen != NULL; i++) | ||
2115 | { | ||
2116 | int j = LSL_Tokens[i].type - lowestToken; | ||
2117 | |||
2118 | tokens[j] = &(LSL_Tokens[i]); | ||
2119 | } | ||
2120 | |||
2121 | // Compile the constants. | ||
2122 | snprintf(buf, sizeof(buf), "lua -e 'require(\"LSL\").gimmeLSL()' > %s/libraries/constants.lsl", PACKAGE_DATA_DIR); | ||
2123 | system(buf); | ||
2124 | snprintf(buf, sizeof(buf), "%s/libraries/constants.lsl", PACKAGE_DATA_DIR); | ||
2125 | compileLSL(ourGlobals, NULL, "FAKE_SID", buf, TRUE); | ||
2126 | |||
2127 | return TRUE; | ||
2128 | } | ||
2129 | else | ||
2130 | PC("No memory for tokens!"); | ||
2131 | |||
2132 | return FALSE; | ||
2133 | } | ||
2134 | |||
2135 | static int luaWriter(lua_State *L, const void* p, size_t sz, void* ud) | ||
2136 | { | ||
2137 | FILE *out = ud; | ||
2138 | int result = 0; | ||
2139 | |||
2140 | if (sz != fwrite(p, 1, sz, out)) | ||
2141 | result = -1; | ||
2142 | return result; | ||
2143 | } | ||
2144 | |||
2145 | boolean compileLSL(gameGlobals *ourGlobals, Ecore_Con_Client *client, char *SID, char *script, boolean doConstants) | ||
2146 | { | ||
2147 | boolean result = FALSE; | ||
2148 | LuaSL_compiler compiler; | ||
2149 | void *pParser = ParseAlloc(malloc); | ||
2150 | int yv; | ||
2151 | |||
2152 | // Parse the LSL script, validating it and reporting errors. | ||
2153 | // Just pass all LSL constants and ll*() )function names through to Lua, assume they are globals there. | ||
2154 | |||
2155 | memset(&compiler, 0, sizeof(LuaSL_compiler)); | ||
2156 | compiler.game = ourGlobals; | ||
2157 | compiler.client = client; | ||
2158 | compiler.script.functions = eina_hash_stringshared_new(burnLeaf); | ||
2159 | compiler.script.states = eina_hash_stringshared_new(burnLeaf); | ||
2160 | compiler.script.variables = eina_hash_stringshared_new(burnLeaf); | ||
2161 | eina_clist_init(&(compiler.danglingCalls)); | ||
2162 | #if LUASL_DIFF_CHECK | ||
2163 | compiler.ignorable = eina_strbuf_new(); | ||
2164 | #endif | ||
2165 | |||
2166 | strncpy(compiler.SID, SID, 36); | ||
2167 | compiler.SID[36] = '\0'; | ||
2168 | strncpy(compiler.fileName, script, PATH_MAX - 1); | ||
2169 | compiler.fileName[PATH_MAX - 1] = '\0'; | ||
2170 | compiler.file = fopen(compiler.fileName, "r"); | ||
2171 | if (NULL == compiler.file) | ||
2172 | { | ||
2173 | PE("Error opening file %s.", compiler.fileName); | ||
2174 | return FALSE; | ||
2175 | } | ||
2176 | compiler.ast = NULL; | ||
2177 | compiler.lval = newLeaf(LSL_UNKNOWN, NULL, NULL); | ||
2178 | // Text editors usually start counting at 1, even programmers editors. mcedit is an exception, but you can deal with that yourself. | ||
2179 | compiler.column = 1; | ||
2180 | compiler.line = 1; | ||
2181 | |||
2182 | if (yylex_init_extra(&compiler, &(compiler.scanner))) | ||
2183 | return result; | ||
2184 | if (LUASL_DEBUG) | ||
2185 | { | ||
2186 | yyset_debug(1, compiler.scanner); | ||
2187 | ParseTrace(stdout, "LSL_lemon "); | ||
2188 | } | ||
2189 | yyset_in(compiler.file, compiler.scanner); | ||
2190 | // on EOF yylex will return 0 | ||
2191 | while((yv = yylex(compiler.lval, compiler.scanner)) != 0) | ||
2192 | { | ||
2193 | Parse(pParser, yv, compiler.lval, &compiler); | ||
2194 | if (LSL_SCRIPT == yv) | ||
2195 | break; | ||
2196 | compiler.lval = newLeaf(LSL_UNKNOWN, NULL, NULL); | ||
2197 | } | ||
2198 | |||
2199 | yylex_destroy(compiler.scanner); | ||
2200 | Parse (pParser, 0, compiler.lval, &compiler); | ||
2201 | ParseFree(pParser, free); | ||
2202 | |||
2203 | if (compiler.undeclared) | ||
2204 | { | ||
2205 | // PW("A second pass is needed to check if functions where used before they where declared. To avoid this second pass, don't do that."); | ||
2206 | if (eina_clist_count(&(compiler.danglingCalls))) | ||
2207 | { | ||
2208 | LSL_FunctionCall *call = NULL; | ||
2209 | |||
2210 | EINA_CLIST_FOR_EACH_ENTRY(call, &(compiler.danglingCalls), LSL_FunctionCall, dangler) | ||
2211 | { | ||
2212 | LSL_Leaf *func = findFunction(&(compiler), call->call->value.stringValue); | ||
2213 | |||
2214 | if (func) | ||
2215 | { | ||
2216 | call->function = func->value.functionValue; | ||
2217 | // Coz the leaf still had the stringValue from before. | ||
2218 | call->call->value.functionCallValue = call; | ||
2219 | call->call->toKen = tokens[LSL_FUNCTION_CALL - lowestToken]; | ||
2220 | call->call->basicType = func->basicType; | ||
2221 | } | ||
2222 | else | ||
2223 | sendBack(ourGlobals, compiler.client, compiler.SID, "compilerError(%d,%d,Undeclared function %s called)", call->call->line, call->call->column, call->call->value.stringValue); | ||
2224 | } | ||
2225 | } | ||
2226 | secondPass(&compiler, compiler.ast); | ||
2227 | // PD("Second pass completed."); | ||
2228 | } | ||
2229 | |||
2230 | if (doConstants) | ||
2231 | { | ||
2232 | memcpy(&constants, &(compiler.script), sizeof(LSL_Script)); | ||
2233 | result = TRUE; | ||
2234 | } | ||
2235 | else | ||
2236 | { | ||
2237 | result = FALSE; | ||
2238 | |||
2239 | if (compiler.ast) | ||
2240 | { | ||
2241 | FILE *out; | ||
2242 | char buffer[PATH_MAX]; | ||
2243 | char outName[PATH_MAX]; | ||
2244 | char luaName[PATH_MAX]; | ||
2245 | int count; | ||
2246 | |||
2247 | if (LUASL_DIFF_CHECK) | ||
2248 | { | ||
2249 | strcpy(outName, compiler.fileName); | ||
2250 | strcat(outName, "2"); | ||
2251 | out = fopen(outName, "w"); | ||
2252 | if (out) | ||
2253 | { | ||
2254 | char diffName[PATH_MAX]; | ||
2255 | |||
2256 | strcpy(diffName, compiler.fileName); | ||
2257 | strcat(diffName, ".diff"); | ||
2258 | outputLeaf(out, OM_LSL, compiler.ast); | ||
2259 | fclose(out); | ||
2260 | sprintf(buffer, "diff -u \"%s\" \"%s\" > \"%s\"", compiler.fileName, outName, diffName); | ||
2261 | count = system(buffer); | ||
2262 | if (0 != count) | ||
2263 | PE("LSL output file is different - %s!", outName); | ||
2264 | else | ||
2265 | result = TRUE; | ||
2266 | } | ||
2267 | else | ||
2268 | PC("Unable to open file %s for writing!", outName); | ||
2269 | } | ||
2270 | strcpy(luaName, compiler.fileName); | ||
2271 | strcat(luaName, ".lua"); | ||
2272 | out = fopen(luaName, "w"); | ||
2273 | // Generate the Lua source code. | ||
2274 | if (out) | ||
2275 | { | ||
2276 | lua_State *L; | ||
2277 | int err; | ||
2278 | char *ext; | ||
2279 | |||
2280 | fprintf(out, "--// Generated code goes here.\n\n"); | ||
2281 | fprintf(out, "local _bit = require(\"bit\")\n"); | ||
2282 | fprintf(out, "local _LSL = require(\"LSL\")\n\n"); | ||
2283 | fprintf(out, "local _SID = [=[%s]=]\n\n", compiler.SID); | ||
2284 | strcpy(buffer, basename(compiler.fileName)); | ||
2285 | if ((ext = rindex(buffer, '.'))) | ||
2286 | ext[0] = '\0'; | ||
2287 | fprintf(out, "local _scriptName = [=[%s]=]\n\n", buffer); | ||
2288 | outputLeaf(out, OM_LUA, compiler.ast); | ||
2289 | fprintf(out, "\n\n_LSL.mainLoop(_SID, _scriptName, _defaultState)\n"); // This actually starts the script running. | ||
2290 | fprintf(out, "\n--// End of generated code.\n\n"); | ||
2291 | fclose(out); | ||
2292 | |||
2293 | // Compile the Lua source code. | ||
2294 | L = luaL_newstate(); | ||
2295 | luaL_openlibs(L); | ||
2296 | // This ends up pushing a function onto the stack. The function is the compiled code. | ||
2297 | err = luaL_loadfile(L, luaName); | ||
2298 | if (err) | ||
2299 | { | ||
2300 | compiler.script.bugCount++; | ||
2301 | if (LUA_ERRSYNTAX == err) | ||
2302 | PE("Lua syntax error in %s: %s", luaName, lua_tostring(L, -1)); | ||
2303 | else if (LUA_ERRFILE == err) | ||
2304 | PE("Lua compile file error in %s: %s", luaName, lua_tostring(L, -1)); | ||
2305 | else if (LUA_ERRMEM == err) | ||
2306 | PE("Lua compile memory allocation error in %s: %s", luaName, lua_tostring(L, -1)); | ||
2307 | } | ||
2308 | else | ||
2309 | { | ||
2310 | // Write the compiled code to a file. | ||
2311 | strcat(luaName, ".out"); | ||
2312 | out = fopen(luaName, "w"); | ||
2313 | if (out) | ||
2314 | { | ||
2315 | err = lua_dump(L, luaWriter, out); | ||
2316 | if (err) | ||
2317 | { | ||
2318 | compiler.script.bugCount++; | ||
2319 | PE("Lua compile file error writing to %s", luaName); | ||
2320 | } | ||
2321 | fclose(out); | ||
2322 | } | ||
2323 | else | ||
2324 | PC("Unable to open file %s for writing!", luaName); | ||
2325 | } | ||
2326 | } | ||
2327 | else | ||
2328 | PC("Unable to open file %s for writing!", luaName); | ||
2329 | } | ||
2330 | |||
2331 | if (0 == compiler.script.bugCount) | ||
2332 | result = TRUE; | ||
2333 | } | ||
2334 | |||
2335 | if (NULL != compiler.file) | ||
2336 | { | ||
2337 | fclose(compiler.file); | ||
2338 | compiler.file = NULL; | ||
2339 | } | ||
2340 | |||
2341 | if (!doConstants) | ||
2342 | burnLeaf(compiler.ast); | ||
2343 | |||
2344 | return result; | ||
2345 | } | ||
diff --git a/src/LuaSL/LuaSL_lemon_yaccer.y b/src/LuaSL/LuaSL_lemon_yaccer.y new file mode 100644 index 0000000..182789f --- /dev/null +++ b/src/LuaSL/LuaSL_lemon_yaccer.y | |||
@@ -0,0 +1,275 @@ | |||
1 | %include | ||
2 | { | ||
3 | #include "LuaSL.h" | ||
4 | } | ||
5 | |||
6 | %extra_argument {LuaSL_compiler *compiler} | ||
7 | |||
8 | %stack_size 1024 | ||
9 | |||
10 | %token_type {LSL_Leaf *} | ||
11 | %default_type {LSL_Leaf *} | ||
12 | %token_destructor {burnLeaf($$);} | ||
13 | %default_destructor {burnLeaf($$);} | ||
14 | |||
15 | // The start symbol, just coz we need one. | ||
16 | |||
17 | // Lemon does not like the start symbol to be on the RHS, so give it a dummy start symbol. | ||
18 | program ::= script LSL_SCRIPT(A). { if (NULL != A) A->left = compiler->ast; compiler->ast = A; } | ||
19 | |||
20 | // Various forms of "space". The lexer takes care of them for us. | ||
21 | |||
22 | %nonassoc LSL_SPACE LSL_COMMENT LSL_COMMENT_LINE LSL_UNKNOWN. | ||
23 | |||
24 | // Basic script structure. | ||
25 | |||
26 | %nonassoc LSL_SCRIPT. | ||
27 | script ::= script state(A). { if (NULL != A) A->left = compiler->ast; compiler->ast = A; } | ||
28 | script ::= script functionBody(A). { if (NULL != A) A->left = compiler->ast; compiler->ast = A; } | ||
29 | script ::= script statement(A). { if (NULL != A) A->left = compiler->ast; compiler->ast = A; } | ||
30 | script ::= . | ||
31 | |||
32 | // State definitions. | ||
33 | |||
34 | %nonassoc LSL_BLOCK_OPEN LSL_BLOCK_CLOSE LSL_STATE. | ||
35 | stateBlock(A) ::= beginBlock(L) functionList(B) LSL_BLOCK_CLOSE(R). { A = addBlock(compiler, L, B, R); } | ||
36 | state(A) ::= LSL_DEFAULT(I) stateBlock(B). { A = addState(compiler, NULL, I, B); } | ||
37 | state(A) ::= LSL_STATE_CHANGE(S) LSL_IDENTIFIER(I) stateBlock(B). { A = addState(compiler, S, I, B); } | ||
38 | |||
39 | // Function definitions. | ||
40 | |||
41 | %nonassoc LSL_PARAMETER LSL_PARAMETER_LIST LSL_FUNCTION. | ||
42 | functionList(A) ::= functionList(B) functionBody(C). { A = collectStatements(compiler, B, C); } | ||
43 | functionList(A) ::= functionBody(C). { A = collectStatements(compiler, NULL, C); } | ||
44 | // No such thing as a function list with no functions. | ||
45 | //functionList(A) ::= . { A = collectStatements(compiler, NULL, NULL); } | ||
46 | |||
47 | functionBody(A) ::= function(F) block(B). { A = addFunctionBody(compiler, F, B); } // addFunctionBody returns an implied addStatement(compiler, NULL, F, NULL, F, NULL, NULL); | ||
48 | |||
49 | parameterList(A) ::= parameterList(B) LSL_COMMA(C) parameter(D). { A = collectParameters(compiler, B, C, D); } | ||
50 | parameterList(A) ::= parameter(D). { A = collectParameters(compiler, NULL, NULL, D); } | ||
51 | parameterList(A) ::= . { A = collectParameters(compiler, NULL, NULL, NULL); } | ||
52 | parameter(A) ::= type(B) LSL_IDENTIFIER(C). { A = addParameter(compiler, B, C); } | ||
53 | function(A) ::= LSL_IDENTIFIER(C) LSL_PARENTHESIS_OPEN(D) parameterList(E) LSL_PARENTHESIS_CLOSE(F). { A = addFunction(compiler, NULL, C, D, E, F); } | ||
54 | function(A) ::= type(B) LSL_IDENTIFIER(C) LSL_PARENTHESIS_OPEN(D) parameterList(E) LSL_PARENTHESIS_CLOSE(F). { A = addFunction(compiler, B, C, D, E, F); } | ||
55 | |||
56 | // Blocks. | ||
57 | |||
58 | block(A) ::= beginBlock(L) statementList(B) LSL_BLOCK_CLOSE(R). { A = addBlock(compiler, L, B, R); } | ||
59 | |||
60 | // Various forms of statement. | ||
61 | |||
62 | %nonassoc LSL_STATEMENT. | ||
63 | statementList(A) ::= statementList(B) statement(C). { A = collectStatements(compiler, B, C); } | ||
64 | // This causes infinite loops (and about 150 conflicts) with - if () single statement; | ||
65 | //statementList(A) ::= statement(C). { A = collectStatements(compiler, NULL, C); } | ||
66 | // Yes, you can have an empty block. | ||
67 | statementList(A) ::= . { A = collectStatements(compiler, NULL, NULL); } | ||
68 | |||
69 | %nonassoc LSL_DO LSL_FOR LSL_IF LSL_JUMP LSL_RETURN LSL_STATE_CHANGE LSL_WHILE. | ||
70 | %nonassoc LSL_ELSE LSL_ELSEIF. | ||
71 | statement(A) ::= LSL_DO(F) block(B) LSL_WHILE(W) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, L, E, R, B, W); } | ||
72 | statement(A) ::= LSL_FOR(F) LSL_PARENTHESIS_OPEN(L) expr(E0) LSL_STATEMENT(S0) expr(E1) LSL_STATEMENT(S1) expr(E2) LSL_PARENTHESIS_CLOSE(R) block(B). { A = addFor(compiler, NULL, F, L, E0, S0, E1, S1, E2, R, B); } | ||
73 | statement(A) ::= LSL_FOR(F) LSL_PARENTHESIS_OPEN(L) expr(E0) LSL_STATEMENT(S0) expr(E1) LSL_STATEMENT(S1) expr(E2) LSL_PARENTHESIS_CLOSE(R) statement(S). { A = addFor(compiler, NULL, F, L, E0, S0, E1, S1, E2, R, S); } | ||
74 | |||
75 | statement(A) ::= ifBlock(B). { A = B; } | ||
76 | ifBlock(A) ::= ifBlock(B) elseIfBlock(E). { A = addIfElse(compiler, B, E); } | ||
77 | ifBlock(A) ::= ifBlock(B) elseBlock(E). { A = addIfElse(compiler, B, E); } | ||
78 | ifBlock(A) ::= LSL_IF(F) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) block(B). [LSL_ELSE] { A = addStatement(compiler, NULL, F, L, E, R, B, NULL); } | ||
79 | ifBlock(A) ::= LSL_IF(F) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) statement(S). [LSL_ELSE] { A = addStatement(compiler, NULL, F, L, E, R, S, NULL); } | ||
80 | elseIfBlock(A) ::= LSL_ELSEIF(F) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) block(B). [LSL_ELSEIF] { A = addStatement(compiler, NULL, F, L, E, R, B, NULL); } | ||
81 | elseIfBlock(A) ::= LSL_ELSEIF(F) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) statement(S). [LSL_ELSEIF] { A = addStatement(compiler, NULL, F, L, E, R, S, NULL); } | ||
82 | elseBlock(A) ::= LSL_ELSE(F) block(B). { A = addStatement(compiler, NULL, F, NULL, NULL, NULL, B, NULL); } | ||
83 | elseBlock(A) ::= LSL_ELSE(F) statement(S). { A = addStatement(compiler, NULL, F, NULL, NULL, NULL, S, NULL); } | ||
84 | |||
85 | statement(A) ::= LSL_JUMP(F) LSL_IDENTIFIER(I) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, NULL, NULL, NULL, NULL, I); } | ||
86 | statement(A) ::= LSL_RETURN(F) expr(E) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, NULL, E, NULL, NULL, NULL); } | ||
87 | statement(A) ::= LSL_RETURN(F) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, NULL, NULL, NULL, NULL, NULL); } | ||
88 | statement(A) ::= LSL_STATE_CHANGE(F) LSL_DEFAULT(I) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, NULL, NULL, NULL, NULL, I); } | ||
89 | statement(A) ::= LSL_STATE_CHANGE(F) LSL_IDENTIFIER(I) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, NULL, NULL, NULL, NULL, I); } | ||
90 | statement(A) ::= LSL_WHILE(F) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) block(B). { A = addStatement(compiler, NULL, F, L, E, R, B, NULL); } | ||
91 | statement(A) ::= LSL_WHILE(F) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) statement(S). { A = addStatement(compiler, NULL, F, L, E, R, S, NULL); } | ||
92 | |||
93 | %nonassoc LSL_LABEL. | ||
94 | statement(A) ::= LSL_LABEL(F) LSL_IDENTIFIER(I) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, NULL, NULL, NULL, NULL, I); } | ||
95 | |||
96 | beginBlock(A) ::= LSL_BLOCK_OPEN(B). { A = beginBlock(compiler, B); } | ||
97 | |||
98 | // This might be bogus, or might be valid LSL, but it lets us test the expression parser by evaluating them. | ||
99 | statement(A) ::= expr(E) LSL_STATEMENT(S). { A = addStatement(compiler, S, S, NULL, E, NULL, NULL, NULL); } | ||
100 | |||
101 | // Various forms of expression. | ||
102 | |||
103 | // Used for function call params, and list contents. | ||
104 | exprList(A) ::= exprList(B) LSL_COMMA(C) expr(D). { A = collectArguments(compiler, B, C, D); } | ||
105 | exprList(A) ::= expr(D). { A = collectArguments(compiler, NULL, NULL, D); } | ||
106 | exprList(A) ::= . { A = collectArguments(compiler, NULL, NULL, NULL); } | ||
107 | |||
108 | // Assignments and variable declarations. | ||
109 | |||
110 | %right LSL_ASSIGNMENT_CONCATENATE LSL_ASSIGNMENT_ADD LSL_ASSIGNMENT_SUBTRACT LSL_ASSIGNMENT_MULTIPLY LSL_ASSIGNMENT_MODULO LSL_ASSIGNMENT_DIVIDE LSL_ASSIGNMENT_PLAIN. | ||
111 | // Variables and dealing with them. | ||
112 | |||
113 | expr(A) ::= identifier(B). { A = B; } | ||
114 | |||
115 | // Yes, these can be expressions, and can happen in if statements and such. In Lua they are NOT expressions. sigh | ||
116 | expr(A) ::= identifier(B) LSL_ASSIGNMENT_CONCATENATE(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
117 | expr(A) ::= identifier(B) LSL_ASSIGNMENT_ADD(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
118 | expr(A) ::= identifier(B) LSL_ASSIGNMENT_SUBTRACT(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
119 | expr(A) ::= identifier(B) LSL_ASSIGNMENT_MULTIPLY(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
120 | expr(A) ::= identifier(B) LSL_ASSIGNMENT_MODULO(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
121 | expr(A) ::= identifier(B) LSL_ASSIGNMENT_DIVIDE(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
122 | expr(A) ::= identifier(B) LSL_ASSIGNMENT_PLAIN(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
123 | |||
124 | // Hmm think this can have commas seperating the assignment parts, or is that only in C?. If so, best to separate them when converting to Lua, as it uses that syntax for something else. | ||
125 | // Well, not in OpenSim at least, nor in SL. So we are safe. B-) | ||
126 | // On the other hand, it might be legal to have comma separated bits in a for loop - for ((i = 1), (j=1); ... | ||
127 | statement(A) ::= type(T) LSL_IDENTIFIER(I) LSL_ASSIGNMENT_PLAIN(D) expr(E) LSL_STATEMENT(S). { A = addStatement(compiler, S, I, NULL, addVariable(compiler, T, I, D, E), NULL, NULL, I); } | ||
128 | statement(A) ::= type(T) LSL_IDENTIFIER(I) LSL_STATEMENT(S). { A = addStatement(compiler, S, I, NULL, addVariable(compiler, T, I, NULL, NULL), NULL, NULL, I); } | ||
129 | |||
130 | // Basic operators. | ||
131 | |||
132 | %right LSL_BOOL_AND. | ||
133 | expr(A) ::= expr(B) LSL_BOOL_AND(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
134 | %right LSL_BOOL_OR. | ||
135 | expr(A) ::= expr(B) LSL_BOOL_OR(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
136 | |||
137 | %left LSL_BIT_AND LSL_BIT_XOR LSL_BIT_OR. | ||
138 | expr(A) ::= expr(B) LSL_BIT_OR(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
139 | expr(A) ::= expr(B) LSL_BIT_XOR(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
140 | expr(A) ::= expr(B) LSL_BIT_AND(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
141 | |||
142 | %right LSL_EQUAL LSL_NOT_EQUAL. | ||
143 | expr(A) ::= expr(B) LSL_NOT_EQUAL(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
144 | expr(A) ::= expr(B) LSL_EQUAL(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
145 | %right LSL_LESS_THAN LSL_GREATER_THAN LSL_LESS_EQUAL LSL_GREATER_EQUAL. | ||
146 | expr(A) ::= expr(B) LSL_GREATER_EQUAL(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
147 | expr(A) ::= expr(B) LSL_LESS_EQUAL(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
148 | expr(A) ::= expr(B) LSL_GREATER_THAN(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
149 | expr(A) ::= expr(B) LSL_LESS_THAN(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
150 | |||
151 | %left LSL_LEFT_SHIFT LSL_RIGHT_SHIFT. | ||
152 | expr(A) ::= expr(B) LSL_RIGHT_SHIFT(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
153 | expr(A) ::= expr(B) LSL_LEFT_SHIFT(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
154 | |||
155 | %left LSL_SUBTRACT LSL_ADD LSL_CONCATENATE. | ||
156 | expr(A) ::= expr(B) LSL_ADD(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
157 | expr(A) ::= expr(B) LSL_SUBTRACT(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
158 | %left LSL_DIVIDE LSL_MODULO LSL_MULTIPLY LSL_DOT_PRODUCT LSL_CROSS_PRODUCT. | ||
159 | expr(A) ::= expr(B) LSL_MULTIPLY(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
160 | expr(A) ::= expr(B) LSL_MODULO(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
161 | expr(A) ::= expr(B) LSL_DIVIDE(C) expr(D). { A = addOperation(compiler, B, C, D); } | ||
162 | |||
163 | %right LSL_BIT_NOT LSL_BOOL_NOT LSL_NEGATION. | ||
164 | expr(A) ::= LSL_BIT_NOT(B) expr(C). { A = addOperation(compiler, NULL, B, C); } | ||
165 | expr(A) ::= LSL_BOOL_NOT(B) expr(C). { A = addOperation(compiler, NULL, B, C); } | ||
166 | expr(A) ::= LSL_SUBTRACT(B) expr(C). [LSL_NEGATION] { A = addOperation(compiler, NULL, B, C); } | ||
167 | |||
168 | // Types, typecasts, and expression reordering. | ||
169 | |||
170 | %right LSL_TYPECAST_OPEN LSL_TYPECAST_CLOSE. | ||
171 | %nonassoc LSL_TYPE_FLOAT LSL_TYPE_INTEGER LSL_TYPE_KEY LSL_TYPE_LIST LSL_TYPE_ROTATION LSL_TYPE_STRING LSL_TYPE_VECTOR. | ||
172 | type(A) ::= LSL_TYPE_FLOAT(B). { B->basicType = OT_float; A = B; } | ||
173 | type(A) ::= LSL_TYPE_INTEGER(B). { B->basicType = OT_integer; A = B; } | ||
174 | type(A) ::= LSL_TYPE_KEY(B). { B->basicType = OT_key; A = B; } | ||
175 | type(A) ::= LSL_TYPE_LIST(B). { B->basicType = OT_list; A = B; } | ||
176 | type(A) ::= LSL_TYPE_ROTATION(B). { B->basicType = OT_rotation; A = B; } | ||
177 | type(A) ::= LSL_TYPE_STRING(B). { B->basicType = OT_string; A = B; } | ||
178 | type(A) ::= LSL_TYPE_VECTOR(B). { B->basicType = OT_vector; A = B; } | ||
179 | |||
180 | %left LSL_ANGLE_OPEN LSL_ANGLE_CLOSE. | ||
181 | %nonassoc LSL_BRACKET_OPEN LSL_BRACKET_CLOSE. | ||
182 | %nonassoc LSL_PARENTHESIS_OPEN LSL_PARENTHESIS_CLOSE LSL_EXPRESSION. | ||
183 | |||
184 | expr(A) ::= LSL_PARENTHESIS_OPEN(B) expr(C) LSL_PARENTHESIS_CLOSE(D). { A = addParenthesis(B, C, LSL_EXPRESSION, D); } | ||
185 | expr(A) ::= LSL_PARENTHESIS_OPEN(B) type(C) LSL_PARENTHESIS_CLOSE(D) expr(E). { A = addTypecast(B, C, D, E); } | ||
186 | |||
187 | %right LSL_DOT LSL_IDENTIFIER LSL_FUNCTION_CALL LSL_VARIABLE. | ||
188 | identifier(A) ::= identifier(I) LSL_DOT(D) LSL_IDENTIFIER(B). { A = checkVariable(compiler, I, D, B); } | ||
189 | identifier(A) ::= LSL_IDENTIFIER(B). { A = checkVariable(compiler, B, NULL, NULL); } | ||
190 | |||
191 | %right LSL_DECREMENT_PRE LSL_INCREMENT_PRE LSL_DECREMENT_POST LSL_INCREMENT_POST. | ||
192 | expr(A) ::= identifier(B) LSL_DECREMENT_PRE(C). { A = addCrement(compiler, B, C, LSL_DECREMENT_POST); } | ||
193 | expr(A) ::= identifier(B) LSL_INCREMENT_PRE(C). { A = addCrement(compiler, B, C, LSL_INCREMENT_POST); } | ||
194 | expr(A) ::= LSL_DECREMENT_PRE(C) identifier(B). { A = addCrement(compiler, B, C, LSL_DECREMENT_PRE); } | ||
195 | expr(A) ::= LSL_INCREMENT_PRE(C) identifier(B). { A = addCrement(compiler, B, C, LSL_INCREMENT_PRE); } | ||
196 | |||
197 | // Function call. | ||
198 | |||
199 | // Causes a conflict when exprList is empty with a function definition with no type and no parameters. | ||
200 | expr(A) ::= LSL_IDENTIFIER(B) LSL_PARENTHESIS_OPEN(C) exprList(D) LSL_PARENTHESIS_CLOSE(E). { A = addFunctionCall(compiler, B, C, D, E); } | ||
201 | |||
202 | %nonassoc LSL_COMMA. | ||
203 | |||
204 | // Values. | ||
205 | |||
206 | %nonassoc LSL_FLOAT. | ||
207 | expr(A) ::= LSL_FLOAT(B). { A = addNumby(B); } | ||
208 | %nonassoc LSL_INTEGER. | ||
209 | expr(A) ::= LSL_INTEGER(B). { A = addNumby(B); } | ||
210 | %nonassoc LSL_KEY. | ||
211 | expr(A) ::= LSL_KEY(B). { B->basicType = OT_key; A = B; } | ||
212 | %nonassoc LSL_LIST. | ||
213 | expr(A) ::= LSL_BRACKET_OPEN(L) exprList(E) LSL_BRACKET_CLOSE(R). [LSL_BRACKET_OPEN] { A = addList(L, E, R); } | ||
214 | %nonassoc LSL_ROTATION LSL_VECTOR. | ||
215 | // Uses the same symbol for less than, greater than, and the rotation / vector delimiters. | ||
216 | expr(A) ::= LSL_LESS_THAN(L) exprList(E) LSL_GREATER_THAN(R). [LSL_ANGLE_OPEN] { A = addRotVec(L, E, R); } | ||
217 | %nonassoc LSL_STRING. | ||
218 | expr(A) ::= LSL_STRING(B). { B->basicType = OT_string; A = B; } | ||
219 | |||
220 | |||
221 | // Parser callbacks. | ||
222 | |||
223 | %parse_accept | ||
224 | { | ||
225 | // gameGlobals *ourGlobals = compiler->game; | ||
226 | |||
227 | // PI("Parsing complete."); | ||
228 | } | ||
229 | |||
230 | %parse_failure | ||
231 | { | ||
232 | gameGlobals *ourGlobals = compiler->game; | ||
233 | |||
234 | compiler->script.bugCount++; | ||
235 | PE("Giving up. Parser is hopelessly lost!"); | ||
236 | } | ||
237 | |||
238 | %stack_overflow | ||
239 | { | ||
240 | gameGlobals *ourGlobals = compiler->game; | ||
241 | |||
242 | compiler->script.bugCount++; | ||
243 | PE("Giving up. Parser stack overflow @ line %d, column %d!", yypMinor->yy0->line, yypMinor->yy0->column); // Gotta love consistancy, if it ever happens. | ||
244 | } | ||
245 | |||
246 | %syntax_error | ||
247 | { | ||
248 | gameGlobals *ourGlobals = compiler->game; | ||
249 | |||
250 | compiler->script.bugCount++; | ||
251 | PE("Syntax error @ line %d, column %d!", yyminor.yy0->line, yyminor.yy0->column); | ||
252 | } | ||
253 | |||
254 | |||
255 | /* Undocumented shit that might be useful later. Pffft | ||
256 | |||
257 | ** The next table maps tokens into fallback tokens. If a construct | ||
258 | ** like the following: | ||
259 | **. | ||
260 | ** %fallback ID X Y Z. | ||
261 | ** | ||
262 | ** appears in the grammar, then ID becomes a fallback token for X, Y, | ||
263 | ** and Z. Whenever one of the tokens X, Y, or Z is input to the parser | ||
264 | ** but it does not parse, the type of the token is changed to ID and | ||
265 | ** the parse is retried before an error is thrown. | ||
266 | |||
267 | %wildcard | ||
268 | %code | ||
269 | |||
270 | %ifdef | ||
271 | %endif | ||
272 | %ifndef | ||
273 | %endif | ||
274 | |||
275 | */ | ||
diff --git a/src/LuaSL/LuaSL_lexer.l b/src/LuaSL/LuaSL_lexer.l new file mode 100644 index 0000000..85b2821 --- /dev/null +++ b/src/LuaSL/LuaSL_lexer.l | |||
@@ -0,0 +1,164 @@ | |||
1 | %{ | ||
2 | |||
3 | #define excludeLexer | ||
4 | #include "LuaSL.h" | ||
5 | |||
6 | int common(YYSTYPE *lval, char *text, int len, LuaSL_compiler *compiler, boolean checkIgnorable, int type); | ||
7 | |||
8 | %} | ||
9 | |||
10 | %option reentrant never-interactive batch | ||
11 | %option bison-bridge 8bit | ||
12 | %option noreject noyymore | ||
13 | %option backup debug perf-report perf-report verbose warn | ||
14 | %option align full | ||
15 | %option extra-type="LuaSL_compiler *" | ||
16 | |||
17 | HEX [[:xdigit:]] | ||
18 | DECIMAL [[:digit:]] | ||
19 | /* LSL has no octal integer type. */ | ||
20 | INTEGER ({DECIMAL}+)|(0[xX]{HEX}+) | ||
21 | EXPONANT [eE][+-]?{DECIMAL}+ | ||
22 | /* Floats can be "0." or".0", but "." is not valid. At least in OpenSim. A single dot should be caught by the LSL_Dot rule first anyway.*/ | ||
23 | FLOAT {DECIMAL}*"."{DECIMAL}*{EXPONANT}?[fF]? | ||
24 | /* Variable identifiers can have these extra characters at the end or beginning, they will be ignored- #$`'\? */ | ||
25 | /* Function identifiers can start with $ */ | ||
26 | IDENTIFIER (_|[[:alpha:]])(_|[[:alpha:]]|[[:digit:]])* | ||
27 | CHAR '(\\.|[^\\'\n])+' | ||
28 | KEY \"{HEX}{8}-{HEX}{4}-{HEX}{4}-{HEX}{4}-{HEX}{12}\" | ||
29 | STRING \"(\\.|[^\\"\n])*\" | ||
30 | |||
31 | %% | ||
32 | |||
33 | /* The order here is important, in mysterious ways. The more specific the lower in case of ambiguities like "floats contain integers". I think, not tested that well yet. */ | ||
34 | |||
35 | /* Ignorables. */ | ||
36 | [[:space:]]+ %{ common(yylval, yytext, yyleng, yyextra, FALSE, LSL_SPACE); %} | ||
37 | /* Yes I know this will have problems with huge comments, just being simple to get it to work for now. */ | ||
38 | "/*"([^"*/"]*)"*/" %{ common(yylval, yytext, yyleng, yyextra, FALSE, LSL_COMMENT); %} | ||
39 | "//"[^\n]* %{ common(yylval, yytext, yyleng, yyextra, FALSE, LSL_COMMENT_LINE); %} | ||
40 | |||
41 | /* Operations. */ | ||
42 | "&&" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BOOL_AND); } | ||
43 | "||" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BOOL_OR); } | ||
44 | "|" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BIT_OR); } | ||
45 | "^" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BIT_XOR); } | ||
46 | "&" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BIT_AND); } | ||
47 | "!=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_NOT_EQUAL); } | ||
48 | "==" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_EQUAL); } | ||
49 | ">=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_GREATER_EQUAL); } | ||
50 | "<=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_LESS_EQUAL); } | ||
51 | ">" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_GREATER_THAN); } | ||
52 | "<" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_LESS_THAN); } | ||
53 | ">>" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_RIGHT_SHIFT); } | ||
54 | "<<" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_LEFT_SHIFT); } | ||
55 | "+" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ADD); } | ||
56 | "-" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_SUBTRACT); } | ||
57 | "*" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_MULTIPLY); } | ||
58 | "%" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_MODULO); } | ||
59 | "/" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_DIVIDE); } | ||
60 | "!" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BOOL_NOT); } | ||
61 | "~" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BIT_NOT); } | ||
62 | "[" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BRACKET_OPEN); } | ||
63 | "]" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BRACKET_CLOSE); } | ||
64 | "(" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_PARENTHESIS_OPEN); } | ||
65 | ")" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_PARENTHESIS_CLOSE); } | ||
66 | "+=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ASSIGNMENT_ADD); } | ||
67 | "-=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ASSIGNMENT_SUBTRACT); } | ||
68 | "*=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ASSIGNMENT_MULTIPLY); } | ||
69 | "%=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ASSIGNMENT_MODULO); } | ||
70 | "/=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ASSIGNMENT_DIVIDE); } | ||
71 | "=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ASSIGNMENT_PLAIN); } | ||
72 | "." { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_DOT); } | ||
73 | "--" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_DECREMENT_PRE); } | ||
74 | "++" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_INCREMENT_PRE); } | ||
75 | "," { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_COMMA); } | ||
76 | |||
77 | /* Other symbols. */ | ||
78 | "@" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_LABEL); %} | ||
79 | "{" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BLOCK_OPEN); %} | ||
80 | "}" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BLOCK_CLOSE); %} | ||
81 | ";" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_STATEMENT); %} | ||
82 | |||
83 | /* Type keywords. */ | ||
84 | "float" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_FLOAT); %} | ||
85 | "integer" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_INTEGER); %} | ||
86 | "key" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_KEY); %} | ||
87 | "list" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_LIST); %} | ||
88 | "quaternion" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_ROTATION); %} | ||
89 | "rotation" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_ROTATION); %} | ||
90 | "string" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_STRING); %} | ||
91 | "vector" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_VECTOR); %} | ||
92 | |||
93 | /* Statement keywords. */ | ||
94 | "default" %{ yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_DEFAULT); %} | ||
95 | "do" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_DO); %} | ||
96 | "for" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_FOR); %} | ||
97 | "else" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ELSE); %} | ||
98 | "if" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_IF); %} | ||
99 | "else"[[:space:]]+"if" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ELSEIF); %} /* TODO - deal with people that slap a comment in between them. */ | ||
100 | "jump" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_JUMP); %} | ||
101 | "return" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_RETURN); %} | ||
102 | "state" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_STATE_CHANGE); %} | ||
103 | "while" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_WHILE); %} | ||
104 | |||
105 | {IDENTIFIER} %{ yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_IDENTIFIER); %} | ||
106 | |||
107 | /* Types. */ | ||
108 | {INTEGER} %{ yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_INTEGER); %} | ||
109 | {FLOAT} %{ yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_FLOAT); %} | ||
110 | {KEY} %{ yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_KEY); %} | ||
111 | {STRING} %{ yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_STRING); %} | ||
112 | |||
113 | <<EOF>> { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_SCRIPT); } | ||
114 | |||
115 | /* Everything else */ | ||
116 | . %{ printf(" unexpected character.\n"); yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); common(yylval, yytext, yyleng, yyextra, TRUE, LSL_UNKNOWN); %} | ||
117 | |||
118 | %% | ||
119 | |||
120 | int common(YYSTYPE *lval, char *text, int len, LuaSL_compiler *compiler, boolean checkIgnorable, int type) | ||
121 | { | ||
122 | char *p; | ||
123 | |||
124 | lval->toKen = tokens[type - lowestToken]; | ||
125 | lval->line = compiler->line; | ||
126 | lval->column = compiler->column; | ||
127 | lval->len = len; | ||
128 | |||
129 | for (p = text; *p; p++) | ||
130 | { | ||
131 | if ('\n' == *p) | ||
132 | { | ||
133 | compiler->line++; | ||
134 | compiler->column = 1; | ||
135 | } | ||
136 | else | ||
137 | compiler->column++; | ||
138 | } | ||
139 | |||
140 | #if LUASL_DIFF_CHECK | ||
141 | if (checkIgnorable) | ||
142 | { | ||
143 | lval->ignorable = compiler->ignorable; | ||
144 | compiler->ignorable = eina_strbuf_new(); | ||
145 | } | ||
146 | else | ||
147 | eina_strbuf_append_length(compiler->ignorable, text, len); | ||
148 | #endif | ||
149 | |||
150 | return type; | ||
151 | } | ||
152 | |||
153 | int yywrap(yyscan_t yyscanner) // This as actually useless for our needs, as it is called BEFORE the last token is dealt with. | ||
154 | { | ||
155 | #ifdef FLEX_SCANNER | ||
156 | #ifndef LL_WINDOWS | ||
157 | // Get gcc to stop complaining about lack of use of yyunput and input. | ||
158 | (void) yyunput; | ||
159 | (void) input; | ||
160 | #endif | ||
161 | #endif | ||
162 | |||
163 | return 1; | ||
164 | } | ||
diff --git a/src/LuaSL/LuaSL_main.c b/src/LuaSL/LuaSL_main.c new file mode 100644 index 0000000..0a40712 --- /dev/null +++ b/src/LuaSL/LuaSL_main.c | |||
@@ -0,0 +1,708 @@ | |||
1 | |||
2 | #include "LuaSL.h" | ||
3 | |||
4 | |||
5 | static int CPUs = 4; | ||
6 | static Eina_Strbuf *clientStream; | ||
7 | |||
8 | |||
9 | static Eina_Bool _sleep_timer_cb(void *data) | ||
10 | { | ||
11 | script *script = data; | ||
12 | gameGlobals *ourGlobals = script->game; | ||
13 | |||
14 | PD("Waking up %s", script->SID); | ||
15 | sendToChannel(ourGlobals, script->SID, "return 0.0"); | ||
16 | return ECORE_CALLBACK_CANCEL; | ||
17 | } | ||
18 | |||
19 | static Eina_Bool _timer_timer_cb(void *data) | ||
20 | { | ||
21 | script *script = data; | ||
22 | gameGlobals *ourGlobals = script->game; | ||
23 | |||
24 | PD("Timer for %s", script->SID); | ||
25 | sendToChannel(ourGlobals, script->SID, "events.timer()"); | ||
26 | return ECORE_CALLBACK_RENEW; | ||
27 | } | ||
28 | |||
29 | static script *findThem(gameGlobals *ourGlobals, const char *base, const char *text) | ||
30 | { | ||
31 | char name[PATH_MAX]; | ||
32 | char *temp; | ||
33 | |||
34 | strncpy(name, base, PATH_MAX); | ||
35 | if ((temp = rindex(name, '/'))) | ||
36 | temp[1] = '\0'; | ||
37 | strcat(name, text); | ||
38 | if ((temp = rindex(name, '"'))) | ||
39 | temp[0] = '\0'; | ||
40 | strcat(name, ".lsl"); | ||
41 | return eina_hash_find(ourGlobals->names, name); | ||
42 | } | ||
43 | |||
44 | static void resetScript(script *victim) | ||
45 | { | ||
46 | gameGlobals *ourGlobals = victim->game; | ||
47 | |||
48 | PD("Resetting %s", victim->fileName); | ||
49 | // TODO - now what? | ||
50 | } | ||
51 | |||
52 | void scriptSendBack(void * data) | ||
53 | { | ||
54 | scriptMessage *message = data; | ||
55 | gameGlobals *ourGlobals = message->script->game; | ||
56 | |||
57 | if (0 == strncmp(message->message, "llSleep(", 8)) | ||
58 | ecore_timer_add(atof(&(message->message)[8]), _sleep_timer_cb, message->script); | ||
59 | else if (0 == strncmp(message->message, "llSetTimerEvent(", 16)) | ||
60 | { | ||
61 | message->script->timerTime = atof(&(message->message)[16]); | ||
62 | if (0.0 == message->script->timerTime) | ||
63 | { | ||
64 | if (message->script->timer) | ||
65 | ecore_timer_del(message->script->timer); | ||
66 | message->script->timer = NULL; | ||
67 | } | ||
68 | else | ||
69 | message->script->timer = ecore_timer_add(message->script->timerTime, _timer_timer_cb, message->script); | ||
70 | } | ||
71 | else if (0 == strncmp(message->message, "llSetScriptState(", 17)) | ||
72 | { | ||
73 | script *them; | ||
74 | |||
75 | if ((them = findThem(ourGlobals, message->script->fileName, &(message->message[18])))) | ||
76 | { | ||
77 | char *temp = rindex(&(message->message[18]), ','); | ||
78 | |||
79 | if (temp) | ||
80 | { | ||
81 | temp++; | ||
82 | while (isspace(*temp)) | ||
83 | temp++; | ||
84 | if ('1' == *temp) | ||
85 | sendToChannel(ourGlobals, them->SID, "start()"); | ||
86 | else | ||
87 | sendToChannel(ourGlobals, them->SID, "stop()"); | ||
88 | PD("Stopped %s", them->fileName); | ||
89 | } | ||
90 | else | ||
91 | PE("Missing script state in llSetScriptState(%s, )", them->fileName); | ||
92 | } | ||
93 | else | ||
94 | { | ||
95 | char *temp = rindex(&(message->message[18]), '"'); | ||
96 | |||
97 | if (temp) | ||
98 | *temp = '\0'; | ||
99 | PE("Can't stop script, can't find %s", &(message->message[18])); | ||
100 | } | ||
101 | } | ||
102 | else if (0 == strncmp(message->message, "llResetOtherScript(", 19)) | ||
103 | { | ||
104 | script *them; | ||
105 | |||
106 | if ((them = findThem(ourGlobals, message->script->fileName, &(message->message[20])))) | ||
107 | resetScript(them); | ||
108 | } | ||
109 | else if (0 == strncmp(message->message, "llResetScript(", 14)) | ||
110 | resetScript(message->script); | ||
111 | else | ||
112 | sendBack(ourGlobals, message->script->client, message->script->SID, message->message); | ||
113 | free(message); | ||
114 | } | ||
115 | |||
116 | static Eina_Bool _add(void *data, int type __UNUSED__, Ecore_Con_Event_Client_Add *ev) | ||
117 | { | ||
118 | ecore_con_client_timeout_set(ev->client, 0); | ||
119 | return ECORE_CALLBACK_RENEW; | ||
120 | } | ||
121 | |||
122 | static Eina_Bool _data(void *data, int type __UNUSED__, Ecore_Con_Event_Client_Data *ev) | ||
123 | { | ||
124 | gameGlobals *ourGlobals = data; | ||
125 | char buf[PATH_MAX]; | ||
126 | char SID[PATH_MAX]; | ||
127 | const char *command; | ||
128 | char *ext; | ||
129 | |||
130 | eina_strbuf_append_length(clientStream, ev->data, ev->size); | ||
131 | command = eina_strbuf_string_get(clientStream); | ||
132 | while ((ext = index(command, '\n'))) | ||
133 | { | ||
134 | int length = ext - command; | ||
135 | |||
136 | strncpy(SID, command, length + 1); | ||
137 | SID[length] = '\0'; | ||
138 | eina_strbuf_remove(clientStream, 0, length + 1); | ||
139 | ext = index(SID, '.'); | ||
140 | if (ext) | ||
141 | { | ||
142 | ext[0] = '\0'; | ||
143 | command = ext + 1; | ||
144 | if (0 == strncmp(command, "compile(", 8)) | ||
145 | { | ||
146 | char *temp; | ||
147 | char *file; | ||
148 | |||
149 | strcpy(buf, &command[8]); | ||
150 | temp = buf; | ||
151 | file = temp; | ||
152 | while (')' != temp[0]) | ||
153 | temp++; | ||
154 | temp[0] = '\0'; | ||
155 | |||
156 | PD("Compiling %s, %s.", SID, file); | ||
157 | if (compileLSL(ourGlobals, ev->client, SID, file, FALSE)) | ||
158 | { | ||
159 | script *me = calloc(1, sizeof(script)); | ||
160 | |||
161 | gettimeofday(&me->startTime, NULL); | ||
162 | strncpy(me->SID, SID, sizeof(me->SID)); | ||
163 | strncpy(me->fileName, file, sizeof(me->fileName)); | ||
164 | me->game = ourGlobals; | ||
165 | me->client = ev->client; | ||
166 | eina_hash_add(ourGlobals->scripts, me->SID, me); | ||
167 | eina_hash_add(ourGlobals->names, me->fileName, me); | ||
168 | sendBack(ourGlobals, ev->client, SID, "compiled(true)"); | ||
169 | } | ||
170 | else | ||
171 | sendBack(ourGlobals, ev->client, SID, "compiled(false)"); | ||
172 | } | ||
173 | else if (0 == strcmp(command, "run()")) | ||
174 | { | ||
175 | script *me; | ||
176 | char buf[PATH_MAX]; | ||
177 | |||
178 | me = eina_hash_find(ourGlobals->scripts, SID); | ||
179 | if (me) | ||
180 | { | ||
181 | sprintf(buf, "%s.lua.out", me->fileName); | ||
182 | newProc(buf, TRUE, me); | ||
183 | } | ||
184 | } | ||
185 | else if (0 == strcmp(command, "exit()")) | ||
186 | { | ||
187 | PD("Told to exit."); | ||
188 | ecore_main_loop_quit(); | ||
189 | } | ||
190 | else | ||
191 | { | ||
192 | const char *status = NULL; | ||
193 | |||
194 | status = sendToChannel(ourGlobals, SID, command); | ||
195 | if (status) | ||
196 | PE("Error sending command %s to script %s : %s", command, SID, status); | ||
197 | } | ||
198 | } | ||
199 | |||
200 | // Get the next blob to check it. | ||
201 | command = eina_strbuf_string_get(clientStream); | ||
202 | } | ||
203 | |||
204 | return ECORE_CALLBACK_RENEW; | ||
205 | } | ||
206 | |||
207 | static Eina_Bool _del(void *data, int type __UNUSED__, Ecore_Con_Event_Client_Del *ev) | ||
208 | { | ||
209 | gameGlobals *ourGlobals = data; | ||
210 | |||
211 | if (ev->client) | ||
212 | { | ||
213 | PD("No more clients, exiting."); | ||
214 | ecore_con_client_del(ev->client); | ||
215 | ecore_main_loop_quit(); | ||
216 | } | ||
217 | return ECORE_CALLBACK_RENEW; | ||
218 | } | ||
219 | |||
220 | int main(int argc, char **argv) | ||
221 | { | ||
222 | gameGlobals ourGlobals; | ||
223 | int result = EXIT_FAILURE; | ||
224 | |||
225 | memset(&ourGlobals, 0, sizeof(gameGlobals)); | ||
226 | ourGlobals.address = "127.0.0.1"; | ||
227 | ourGlobals.port = 8211; | ||
228 | |||
229 | if (eina_init()) | ||
230 | { | ||
231 | ourGlobals.logDom = loggingStartup("LuaSL", ourGlobals.logDom); | ||
232 | ourGlobals.scripts = eina_hash_string_superfast_new(NULL); | ||
233 | ourGlobals.names = eina_hash_string_superfast_new(NULL); | ||
234 | if (ecore_con_init()) | ||
235 | { | ||
236 | if (ecore_con_init()) | ||
237 | { | ||
238 | if ((ourGlobals.server = ecore_con_server_add(ECORE_CON_REMOTE_TCP, ourGlobals.address, ourGlobals.port, &ourGlobals))) | ||
239 | { | ||
240 | ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_ADD, (Ecore_Event_Handler_Cb) _add, &ourGlobals); | ||
241 | ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_DATA, (Ecore_Event_Handler_Cb) _data, &ourGlobals); | ||
242 | ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_DEL, (Ecore_Event_Handler_Cb) _del, &ourGlobals); | ||
243 | ecore_con_server_timeout_set(ourGlobals.server, 0); | ||
244 | ecore_con_server_client_limit_set(ourGlobals.server, -1, 0); | ||
245 | ecore_con_server_timeout_set(ourGlobals.server, 10); | ||
246 | ecore_con_server_client_limit_set(ourGlobals.server, 3, 0); | ||
247 | clientStream = eina_strbuf_new(); | ||
248 | |||
249 | if (edje_init()) | ||
250 | { | ||
251 | int i; | ||
252 | |||
253 | result = 0; | ||
254 | compilerSetup(&ourGlobals); | ||
255 | luaprocInit(); | ||
256 | for (i = 0; i < CPUs; i++) | ||
257 | { | ||
258 | if ( sched_create_worker( ) != LUAPROC_SCHED_OK ) | ||
259 | PEm("Error creating luaproc worker thread."); | ||
260 | } | ||
261 | ecore_main_loop_begin(); | ||
262 | |||
263 | // TODO - this is what hangs the system, should change from raw pthreads to ecore threads. | ||
264 | sched_join_workerthreads(); | ||
265 | edje_shutdown(); | ||
266 | } | ||
267 | else | ||
268 | PCm("Failed to init edje!"); | ||
269 | } | ||
270 | else | ||
271 | PCm("Failed to add server!"); | ||
272 | ecore_con_shutdown(); | ||
273 | } | ||
274 | else | ||
275 | PCm("Failed to init ecore_con!"); | ||
276 | ecore_shutdown(); | ||
277 | } | ||
278 | else | ||
279 | PCm("Failed to init ecore!"); | ||
280 | } | ||
281 | else | ||
282 | fprintf(stderr, "Failed to init eina!"); | ||
283 | |||
284 | return result; | ||
285 | } | ||
286 | |||
287 | |||
288 | /* Stuff to be merged in later. | ||
289 | |||
290 | #ifdef _WIN32 | ||
291 | # define FMT_SIZE_T "%Iu" | ||
292 | #else | ||
293 | # define FMT_SIZE_T "%zu" | ||
294 | #endif | ||
295 | |||
296 | #define MAX_LUA_MEM (64 * 1024)) // LSL Mono is 64KB, edje Lua is 4MB. (4 * (1024 * 1024)) | ||
297 | |||
298 | #define _edje_lua2_error(L, err_code) _edje_lua2_error_full(__FILE__, __FUNCTION__, __LINE__, L, err_code) | ||
299 | |||
300 | |||
301 | typedef struct _Edje_Lua_Alloc Edje_Lua_Alloc; | ||
302 | |||
303 | struct _Edje_Lua_Alloc | ||
304 | { | ||
305 | size_t max, cur; | ||
306 | }; | ||
307 | |||
308 | static void * | ||
309 | _elua_alloc(void *ud, void *ptr, size_t osize, size_t nsize) | ||
310 | { | ||
311 | Edje_Lua_Alloc *ela = ud; | ||
312 | void *ptr2 = NULL; | ||
313 | |||
314 | if (ela) | ||
315 | { | ||
316 | ela->cur += nsize - osize; | ||
317 | if (ela->cur > ela->max) | ||
318 | { | ||
319 | printf("Lua memory limit of " FMT_SIZE_T " bytes reached (" FMT_SIZE_T " allocated)", ela->max, ela->cur); | ||
320 | } | ||
321 | else if (nsize == 0) | ||
322 | { | ||
323 | free(ptr); | ||
324 | } | ||
325 | else | ||
326 | { | ||
327 | ptr2 = realloc(ptr, nsize); | ||
328 | if (NULL == ptr2) | ||
329 | printf("Lua cannot re-allocate " FMT_SIZE_T " bytes", nsize); | ||
330 | } | ||
331 | } | ||
332 | else | ||
333 | printf("Lua cannoct allocate memory, no Edje_Lua_Alloc"); | ||
334 | |||
335 | return ptr2; | ||
336 | } | ||
337 | |||
338 | static int panics = 0; | ||
339 | static int | ||
340 | _elua_custom_panic(lua_State *L) // Stack usage [-0, +0, m] | ||
341 | { | ||
342 | // If we somehow manage to have multiple panics, it's likely due to being out | ||
343 | // of memory in the following lua_tostring() call. | ||
344 | panics++; | ||
345 | if (panics) | ||
346 | { | ||
347 | printf("Lua PANICS!!!!!"); | ||
348 | } | ||
349 | else | ||
350 | { | ||
351 | printf("Lua PANIC!!!!!: %s", lua_tostring(L, -1)); // Stack usage [-0, +0, m] | ||
352 | } | ||
353 | // The docs say that this will cause an exit(EXIT_FAILURE) if we return, | ||
354 | // and that we we should long jump some where to avoid that. This is only | ||
355 | // called for things not called from a protected environment. We always | ||
356 | // use pcalls though, except for the library load calls. If we can't load | ||
357 | // the standard libraries, then perhaps a crash is the right thing. | ||
358 | return 0; | ||
359 | } | ||
360 | |||
361 | static void | ||
362 | _edje_lua2_error_full(const char *file, const char *fnc, int line, | ||
363 | lua_State *L, int err_code) // Stack usage [-0, +0, m] | ||
364 | { | ||
365 | const char *err_type; | ||
366 | |||
367 | switch (err_code) | ||
368 | { | ||
369 | case LUA_ERRRUN: | ||
370 | err_type = "runtime"; | ||
371 | break; | ||
372 | case LUA_ERRSYNTAX: | ||
373 | err_type = "syntax"; | ||
374 | break; | ||
375 | case LUA_ERRMEM: | ||
376 | err_type = "memory allocation"; | ||
377 | break; | ||
378 | case LUA_ERRERR: | ||
379 | err_type = "error handler"; | ||
380 | break; | ||
381 | default: | ||
382 | err_type = "unknown"; | ||
383 | break; | ||
384 | } | ||
385 | printf("Lua %s error: %s\n", err_type, lua_tostring(L, -1)); // Stack usage [-0, +0, m] | ||
386 | } | ||
387 | |||
388 | static int errFunc(lua_State *L) | ||
389 | { | ||
390 | int i = 0; | ||
391 | lua_Debug ar; | ||
392 | |||
393 | while (lua_getstack(L, i++, &ar)) | ||
394 | { | ||
395 | if (lua_getinfo(L, "nSlu", &ar)) | ||
396 | { | ||
397 | if (NULL == ar.name) | ||
398 | ar.name = "DUNNO"; | ||
399 | printf("Lua error in the %s %s %s @ line %d in %s\n%s!", ar.what, ar.namewhat, ar.name, ar.currentline, ar.short_src, ar.source); | ||
400 | } | ||
401 | } | ||
402 | return 0; | ||
403 | } | ||
404 | |||
405 | void runLuaFile(gameGlobals *ourGlobals, const char *filename) | ||
406 | { | ||
407 | PD("Starting %s", filename); | ||
408 | newProc(filename, TRUE); | ||
409 | |||
410 | // TODO, should set up our panic and errfunc as below. Plus the other TODO stuff. | ||
411 | |||
412 | |||
413 | // TODO - hack up LuaJIT so that we can limit memory per state. | ||
414 | // lua_setallocf(L, _elua_alloc, &ela); // LuaJIT uses a heavily hacked up dlmalloc. Seems that standard realloc is not so thread safe? | ||
415 | lua_atpanic(L, _elua_custom_panic); | ||
416 | // TODO - Sandbox out what this opens. See lib_init.c from LuaJIT. | ||
417 | // Just noticed this in the LuaJIT docs - "To change or extend the list of standard libraries to load, copy src/lib_init.c to your project and modify it accordingly. Make sure the jit library is loaded or the JIT compiler will not be activated." | ||
418 | luaL_openlibs(L); | ||
419 | |||
420 | lua_pushcfunction(L, errFunc); | ||
421 | ... | ||
422 | if ((err = lua_pcall(L, 0, 0, -2))) | ||
423 | _edje_lua2_error(L, err); | ||
424 | } | ||
425 | */ | ||
426 | |||
427 | |||
428 | |||
429 | |||
430 | /* What we need to do, and how we might do it. | ||
431 | * | ||
432 | * Get configuration info. | ||
433 | * | ||
434 | * Some of the configuration info we need is tucked away in OpenSim .ini files. So we have to read that. | ||
435 | * Eet is probably not so useful here, as we would have to write a UI tool, and there's no UI otherwise. | ||
436 | * | ||
437 | * Multi task! | ||
438 | * | ||
439 | * Luaproc starts up X worker threads. Each one takes the next ready Lua state and runs it until it blocks waiting for a channel message, or yields. | ||
440 | * The current mainloop waits for events and commands from the message channel, executes them, then goes back to waiting. | ||
441 | * So that is fine in general, so long as the LSL event handlers actually return in a reasonable time. | ||
442 | * We need to be able to yield at suitable places, or be able to force a yield. Both without slowing things down too much. | ||
443 | * | ||
444 | * Watchdog thread. | ||
445 | * | ||
446 | * It's easy enough to have a watchdog thread wake up every now and then to check if any given Lua state has been hogging it's CPU for too long. | ||
447 | * The hard part is forcing Lua states to yield cleanly, without slowing performance too much. | ||
448 | * | ||
449 | * Identifying scripts. - OpenSim/Region/ScriptEngine/Interfaces/IScriptInstance.cs | ||
450 | * | ||
451 | * We need to be able to identify scripts so that messages can get to the right ones. Also the objects they are in. | ||
452 | * And do it all in a way that OpenSim is happy with. | ||
453 | * Copies of the same script in the same prim would have the same asset UUID, but different name, though they would run independently. | ||
454 | * Copies of the same script in different prims could have the same asset UUID AND the same name. | ||
455 | * OpenSim seems to be using a UUID to identify single scripts, and a uint to identify single prims, when sending events for either case. | ||
456 | * The UUID is from the script item in the prims inventory. | ||
457 | * There is also an asset UUID (the one printed out on the console at script startup time) that points to the source code in the prim. | ||
458 | * Which will be identical to the asset UUID for the multiple copies of the same script. | ||
459 | * | ||
460 | * script assetID | ||
461 | * UUID of the script source in the grids asset database, also the script source in the prim. | ||
462 | * | ||
463 | * script itemID | ||
464 | * UUID of this instance of the running script. | ||
465 | * UUID of the scripts binary in the prims inventory. | ||
466 | * This is the one used to identify the running script. | ||
467 | * | ||
468 | * prim uint localID | ||
469 | * Some sort of number representing the prim the script is running in. | ||
470 | * Events are sometimes sent to this. | ||
471 | * | ||
472 | * path/filename | ||
473 | * An invention of LuaSL, coz we store stuff as files. | ||
474 | * | ||
475 | * OpenSim says "compile this assetID for this itemID, in this prim uint" | ||
476 | * Current infrastructure does not allow easy sending of the script source, but we don't have ROBUST code to get it either. | ||
477 | * ROBUST is the way to go though, coz we can sneakily start to suck other stuff, like prim contents across when needed. | ||
478 | * Though that sort of thing needs access to the local sim databases to lookup the prim and it's other contents. sigh | ||
479 | * I think that new script and notecard contents get new assetIDs anyway, so keeping an eye on assets.create_time or asset_access_time wont help much. | ||
480 | * | ||
481 | * OpenSim says "start / stop this itemID" | ||
482 | * Already catered for. | ||
483 | * | ||
484 | * What does OpenSim REALLY do? | ||
485 | * | ||
486 | * Region/Framework/Scenes/Scene.Inventory.cs - CapsUpdateTaskInventoryScriptAsset(IClientAPI remoteClient, UUID itemId, UUID primId, bool isScriptRunning, byte[] data) | ||
487 | * remoteClient | ||
488 | * itemID - UUID of the script source. | ||
489 | * primID - UUID of the prim it is in. | ||
490 | * isScriptRunning | ||
491 | * data - the script source code. | ||
492 | * Called when a user saves the script. itemID stays the same, but we get a new assetID, for the new source code asset. | ||
493 | * Looks up the item in the prim. | ||
494 | * AssetBase asset = CreateAsset(item.Name, item.Description, (sbyte)AssetType.LSLText, data, remoteClient.AgentId); | ||
495 | * AssetService.Store(asset); | ||
496 | * stashes the new assetID in the item | ||
497 | * updates the item in the prim | ||
498 | * if (isScriptRunning) | ||
499 | * part.Inventory.RemoveScriptInstance(item.ItemID, false); | ||
500 | * part.Inventory.CreateScriptInstance(item.ItemID, 0, false, DefaultScriptEngine, 0); | ||
501 | * errors = part.Inventory.GetScriptErrors(item.ItemID); | ||
502 | * | ||
503 | * CreateScriptInstance() is generally called to start scripts, part.ParentGroup.ResumeScripts(); is usually called after CreateScriptInstance() | ||
504 | * | ||
505 | * Region/Framework/Scenes/SceneObjectPartInventory.cs - CreateScriptInstance(UUID itemId, int startParam, bool postOnRez, string engine, int stateSource) | ||
506 | * looks up the itemID, then calls the real one - | ||
507 | * Region/Framework/Scenes/SceneObjectPartInventory.cs - CreateScriptInstance(TaskInventoryItem item, int startParam, bool postOnRez, string engine, int stateSource) | ||
508 | * get the asset from the asset database using the items assetID | ||
509 | * restores script state if needed | ||
510 | * converts asset.data to a string called script | ||
511 | * m_part.ParentGroup.Scene.EventManager.TriggerRezScript(m_part.LocalId, item.ItemID, script, startParam, postOnRez, engine, stateSource); | ||
512 | * QUIRK - if it's a sim border crossing, TriggerRezScript() gets called with empty script source. | ||
513 | * | ||
514 | * Region/ScriptEngine/XEngine/XEngine.cs - AddRegion(Scene scene) | ||
515 | * m_log.InfoFormat("[XEngine] Initializing scripts in region {0}", scene.RegionInfo.RegionName); | ||
516 | * gets the script config info, which is the same damn stuff for each sim. Pffft | ||
517 | * Think it relies on the scenes event manager to call OnRezScript() - | ||
518 | * m_Scene.EventManager.OnRezScript += OnRezScript; | ||
519 | * | ||
520 | * Region/Framework/Scenes/EventManager.cs - TriggerRezScript(uint localID, UUID itemID, string script, int startParam, bool postOnRez, string engine, int stateSource) | ||
521 | * Loops through Scene.EventManager.OnRezScript calling them. | ||
522 | * | ||
523 | * Region/ScriptEngine/XEngine/XEngine.cs - OnRezScript(uint localID, UUID itemID, string script, int startParam, bool postOnRez, string engine, int stateSource) | ||
524 | * Looks at the script source to figure out if it's an XEngine script. | ||
525 | * Either queues the script for later, or does it direct. | ||
526 | * Region/ScriptEngine/XEngine/XEngine.cs - DoOnRezScript() is passed an array holding - | ||
527 | * localID is a uint that represents the containing prim in the current scene | ||
528 | * itemID is the UUID of the script in the prims contents | ||
529 | * script is the script source code. | ||
530 | * startParam is the scripts startParam | ||
531 | * postOnRez | ||
532 | * stateSource is an integer saying how we where started, used to trigger the appropriate startup events. | ||
533 | * uses localID to look up the prim in the scene, then looks inside that for the itemID to find the assetID. | ||
534 | * m_Compiler.PerformScriptCompile(script, assetID.ToString(), item.OwnerID, out assembly, out linemap); | ||
535 | * Which is in Region/ScriptEngine/Shared/CodeTools/Compiler.cs | ||
536 | * instance = new ScriptInstance(this, part, itemID, assetID, assembly, m_AppDomains[appDomain], part.ParentGroup.RootPart.Name, item.Name, startParam, postOnRez, stateSource, m_MaxScriptQueue); | ||
537 | * Region/ScriptEngine/Shared/Instance/ScriptInstance.cs - ScriptInstance(IScriptEngine engine, SceneObjectPart part, UUID itemID, UUID assetID, string assembly, AppDomain dom, string primName, string scriptName, int startParam, bool postOnRez, StateSource stateSource, int maxScriptQueue) | ||
538 | * inits all the APIs | ||
539 | * loads in any saved state if it can find one | ||
540 | * m_log.DebugFormat("[XEngine] Loaded script {0}.{1}, script UUID {2}, prim UUID {3} @ {4}.{5}", part.ParentGroup.RootPart.Name, item.Name, assetID, part.UUID, part.ParentGroup.RootPart.AbsolutePosition, part.ParentGroup.Scene.RegionInfo.RegionName); | ||
541 | * | ||
542 | * Soooo, when a script is saved - | ||
543 | * the new source is saved in the asset database | ||
544 | * The script item in the prim gets the new assetID | ||
545 | * if the script is running - | ||
546 | * remove the old script instance (item.ItemID) | ||
547 | * create a new one (item.ItemID) | ||
548 | * get the source code from the asset database (item.assetID) | ||
549 | * restore script state | ||
550 | * TriggerRezOnScript() | ||
551 | * Loop through all those that are interested, incuding XEngine.onRezScript() | ||
552 | *** check the first line to see if it's an XEngine script | ||
553 | * sooner or later passes it to XEngine.DoOnRezScript() | ||
554 | * looks up localID to get the prim | ||
555 | * looks inside prim to get the script from itemID | ||
556 | * gets the assetID from the script item | ||
557 | * compiles the script | ||
558 | * creates the script instance | ||
559 | * loads up the APIs | ||
560 | * restores any script state | ||
561 | * calls instance.Init() which is Region/ScriptEngine/Shared/Instance/ScriptInstance.cs - Init() | ||
562 | * passes the usual startup events to the script. | ||
563 | * part.ParentGroup.ResumeScripts() | ||
564 | * | ||
565 | * At the *** marked point, LuaSL.onRezScript should - | ||
566 | * check the first line to see if it's an LuaSL script | ||
567 | * looks up localID to get the prim | ||
568 | * looks inside prim to get the script from itemID | ||
569 | * gets the assetID from the script item | ||
570 | * filename encode the sim name, object name, and script name | ||
571 | * replace anything less than 0x21, DEL " * / : < > ? \ | + [ ] - , . ( ) $ % # @ from - http://en.wikipedia.org/wiki/Filename plus a few more | ||
572 | * THEN reduce to 254 characters | ||
573 | * NOTE the object names might be identical, disambiguate them. | ||
574 | * write the script to a file - /script/engine/path/sim_name/objects/object_name/script_name | ||
575 | * send the itemID.compile(/script/engine/path/sim_name/objects/object_name/script_name) message to the script engine's socket | ||
576 | * | ||
577 | * | ||
578 | * Object inventory "cache". | ||
579 | * | ||
580 | * This code currently pretends that there is a local file based sim object store available. | ||
581 | * I think it would be a good idea to abuse the OpenSim cache system to produce that file based object store. | ||
582 | * It will help with the "damn OpenSim's asset database has to be a bottomless pit" monster design flaw. | ||
583 | * Prim contents must all be unique names anyway, and there are SOME constraints on contents names, so probably don't have to do much to convert an item name to a legal file name. | ||
584 | * Oops, names can have directory slashes in them. lol | ||
585 | * On the other hand, sim objects CAN have the same name. | ||
586 | * | ||
587 | * So we got sim directories, with an objects directory inside it, with object directories inside that. The object directories have object files in them. This is all like the test setup that is here. | ||
588 | * We need metadata. Sim metadata, object metadata, and object contents metadata. That can be done with a "foo.omg" file at each level. | ||
589 | * sim/index.omg - the list of object name.UUIDs, their X,Y,Z location, size, and rotation. | ||
590 | * sim/objects/objectName_UUID/index.omg - the list of contents names, item UUIDs, asset UUIDs, and types. | ||
591 | * sim/objects/objectName/subObjectName - the list of ITS contents names, item UUIDs, asset UUIDs, and types. | ||
592 | * | ||
593 | * Script start, stop, reset. - OpenSim/Region/ScriptEngine/Interfaces/IScriptEngine.cs | ||
594 | * | ||
595 | * Scripts and users have to be able to start, stop, and reset scripts. | ||
596 | * Users have to be able to see the start / stopped status of scripts from the viewer. | ||
597 | * Which means if the script does it, we have to tell OpenSim. | ||
598 | * Naturally, if OpenSim does it, it has to tell us. | ||
599 | * Should be able to do both by passing textual Lua function calls between OpenSim and LuaSL. | ||
600 | * | ||
601 | * Event handling, llDetect*() functions. | ||
602 | * | ||
603 | * OpenSim will generate events at random times and send us relevant information for llDetect*() functions and the handler calls. | ||
604 | * These should come through the scripts main loop eventually. | ||
605 | * | ||
606 | * Send messages from / to OpenSim and ROBUST. | ||
607 | * | ||
608 | * ROBUST uses HTTP for the communications, and some sort of XML, probably XML-RPC. | ||
609 | * OpenSim has some sort of generic mechanism for talking to script engines in C#. I want to turn that into a socket based, pass textual Lua function calls, type system. | ||
610 | * That assumes C# has some sort of semi decent introspection or reflection system. | ||
611 | * After a minimum of research, I have come to the conclusion that C# has suitable introspection, and will go ahead with my plans, leaving that problem to the C# coders. | ||
612 | * Looks like OpenSim is already using a bit of C# introspection for ll*() functions anyway. | ||
613 | * The scripts main loop can already deal with incoming commands as a string with a Lua function call in it. | ||
614 | * | ||
615 | * Send messages from / to Lua or C, and wait or not wait. | ||
616 | * | ||
617 | * Luaproc channels are distinct from Lua states, but some Lua state has to create them before they can be used. | ||
618 | * On the other hand, looks like broadcasting messages is not really catered for, it's first come first served. | ||
619 | * luaproc.send() can send multiple messages to a single channel. It blocks if no one is listening. | ||
620 | * This was done to simplify things for the luaproc programmers, who suggest creating more Lua states to deal with asynchronous message sending. | ||
621 | * luaprog.receive() gets a channel message. It can block waiting, or not block. | ||
622 | * I already hacked up C code to send and not block. I might have broken the luaproc.send() ability to send multiple messages. | ||
623 | * Before I hacked it up, actual message sending was done by copying the contents of the sending Lua states stack to the receiver states stack. | ||
624 | * This is the simple method for the luaproc programmers, as both states are in the context of a luaproc call, so both stacks are available. | ||
625 | * My hacked up version either takes one message from the sender, or is passed one from C. The C call just returns if there is no one waiting on that channel. | ||
626 | * luaproc.send() calls that C function after taking a single message from the stack, and block waits as usual if the C call cannot deliver. | ||
627 | * Don't think there is C to receive messages, luaproc seems to be lacking entirely in C side API. | ||
628 | * NOTE - Sending from C means that the message goes nowhere if no one is waiting for it. | ||
629 | * SOOOO, we may need to queue messages to. | ||
630 | * Just chuck them in a FIFO per channel, and destroy the FIFO when the channel get's destroyed. | ||
631 | * See if I can create the SID channel in C before I start the Lua state running. | ||
632 | * Edje messages might have to be used instead, or some hybrid. | ||
633 | * | ||
634 | * Main loop is waiting on messages, and that's the main driver. Luaproc is fine with that. Good for events. | ||
635 | * End of event handler - | ||
636 | * just wait for the next event. | ||
637 | * Stop a script from LSL - | ||
638 | * gotta find it's SID from it's name, and the prim UUID | ||
639 | * send the message | ||
640 | * wait for it to get the message - BUT we don't really want to wait. | ||
641 | * Stop a script from OpenSim - | ||
642 | * we should have it's SID from OpenSim, just send the message from C, no need to wait. | ||
643 | * Start a script - | ||
644 | * if it's stopped, it's already waiting for the message. | ||
645 | * if it's not stopped, then we don't care. BUT then we might be waiting for it to get the message if starting it from LSL. | ||
646 | * Reset a script - | ||
647 | * probably should be done from C anyway, and can reuse the libraries like luaproc likes to do. | ||
648 | * ask C to reset it. | ||
649 | * LSL calls a function we have to hand to OpenSim - | ||
650 | * send the message to C, wait. | ||
651 | * C eventually sends a message back. | ||
652 | * Sleep - | ||
653 | * tell C it's waiting for the wake up message. | ||
654 | * wait for the wake up message. | ||
655 | * | ||
656 | * C needs - | ||
657 | * Lua call for stop script. | ||
658 | * get the SID from the name, and the prim UUID. | ||
659 | * send the stop message to the SID. | ||
660 | * send something to OpenSim so it knows. | ||
661 | * return to Lua. | ||
662 | * Lua call for start script. | ||
663 | * get the SID from the name, and the prim UUID. | ||
664 | * send the start message to the SID. | ||
665 | * send something to OpenSim so it knows. | ||
666 | * return to Lua. | ||
667 | * Lua call for reset other script. | ||
668 | * get the SID from the name, and the prim UUID. | ||
669 | * figure out which Lua state it is. | ||
670 | * fall through to "reset this script", only with the script set to the found one. | ||
671 | * Lua call for reset this script. | ||
672 | * get luaproc to close this Lua state | ||
673 | * reload the script file | ||
674 | * start it again, reusing the previous Lua state, or which ever one luaproc wants to use. | ||
675 | * Lua call for sending a function to OpenSim. | ||
676 | * Lua first strings up the function call and args, with SID. | ||
677 | * C packs it off to opensim. | ||
678 | * C puts Lua state on the "waiting for message" queue if a return value is needed. | ||
679 | * OpenSim sends back the return value, business as usual. | ||
680 | * Lua call for sleep. | ||
681 | * setup an ecore timer callback | ||
682 | * put the Lua state into "waiting for message" queue. | ||
683 | * ecore timer callback sends the wake up message. | ||
684 | * | ||
685 | * Time and timers, plus deal with time dilation. | ||
686 | * | ||
687 | * Various LSL functions deal with time, that's no problem. | ||
688 | * Some might deal with time dilation, which means dealing with that info through OpenSim. | ||
689 | * There is a timer event, which should be done through ecore timers and whatever message system we end up with. | ||
690 | * Finally, a sleep call, which can be done with ecore timer and messages to. | ||
691 | * | ||
692 | * Deal directly with MySQL and SQLite databases. | ||
693 | * | ||
694 | * The script engine can run on the same computer as the sim server, that's how OpenSim does stuff. So we can directly access the database the sim server has, which gives us access to sim object metadata. | ||
695 | * Changing that metadata might require us to inform OpenSim about the changes. It's entirely possible that different changes do or do not need that. | ||
696 | * Esskyuehl may be suitable, though it's still in the prototype stage. | ||
697 | * | ||
698 | * Serialise the script state, send it somewhere. | ||
699 | * | ||
700 | * Lua can generally serialise itself as as a string of code to be executed at the destination. There might be some C side state that needs to be take care of as well. We shall see. | ||
701 | * | ||
702 | * Email, HTTP, XML-RPC? | ||
703 | * | ||
704 | * LSL has functions for using these as communications methods. We should implement them ourselves eventually, but use the existing OpenSim methods for now. | ||
705 | * Note that doing it ourselves may cause issues with OpenSim doing it for some other script engine. | ||
706 | * Azy might be suitable, but it's also in prototype. | ||
707 | * | ||
708 | */ | ||
diff --git a/src/LuaSL/LuaSL_test.c b/src/LuaSL/LuaSL_test.c new file mode 100644 index 0000000..58d6225 --- /dev/null +++ b/src/LuaSL/LuaSL_test.c | |||
@@ -0,0 +1,467 @@ | |||
1 | |||
2 | #include "LuaSL.h" | ||
3 | |||
4 | |||
5 | static Eina_Strbuf *clientStream; | ||
6 | static int scriptCount = 0; | ||
7 | static int compiledCount = 0; | ||
8 | static float compileTime = 0.0; | ||
9 | static struct timeval startTime; | ||
10 | static int timedEvent = 0; | ||
11 | static char *ownerKey = "12345678-1234-4321-abcd-0123456789ab"; | ||
12 | static char *ownerName = "onefang rejected"; | ||
13 | |||
14 | static const char *names[] = | ||
15 | { | ||
16 | "bub1", "sh1", | ||
17 | "bub2", "sh2", | ||
18 | "bub3", "sh3", | ||
19 | }; | ||
20 | |||
21 | |||
22 | static void | ||
23 | _edje_signal_cb(void *data, Evas_Object *obj __UNUSED__, const char *emission, const char *source) | ||
24 | { | ||
25 | // gameGlobals *ourGlobals = data; | ||
26 | } | ||
27 | |||
28 | static | ||
29 | Eina_Bool anim(void *data) | ||
30 | { | ||
31 | gameGlobals *ourGlobals = data; | ||
32 | Evas_Object *bub, *sh; | ||
33 | Evas_Coord x, y, w, h, vw, vh; | ||
34 | double t, xx, yy, zz, r, fac; | ||
35 | double lx, ly; | ||
36 | unsigned int i; | ||
37 | |||
38 | evas_output_viewport_get(ourGlobals->canvas, 0, 0, &vw, &vh); | ||
39 | r = 48; | ||
40 | t = ecore_loop_time_get(); | ||
41 | fac = 2.0 / (double)((sizeof(names) / sizeof(char *) / 2)); | ||
42 | evas_pointer_canvas_xy_get(ourGlobals->canvas, &x, &y); | ||
43 | lx = x; | ||
44 | ly = y; | ||
45 | |||
46 | for (i = 0; i < (sizeof(names) / sizeof(char *) / 2); i++) | ||
47 | { | ||
48 | bub = evas_object_data_get(ourGlobals->bg, names[i * 2]); | ||
49 | sh = evas_object_data_get(ourGlobals->bg, names[(i * 2) + 1]); | ||
50 | zz = (((2 + sin(t * 6 + (M_PI * (i * fac)))) / 3) * 64) * 2; | ||
51 | xx = (cos(t * 4 + (M_PI * (i * fac))) * r) * 2; | ||
52 | yy = (sin(t * 6 + (M_PI * (i * fac))) * r) * 2; | ||
53 | |||
54 | w = zz; | ||
55 | h = zz; | ||
56 | x = (vw / 2) + xx - (w / 2); | ||
57 | y = (vh / 2) + yy - (h / 2); | ||
58 | |||
59 | evas_object_move(bub, x, y); | ||
60 | evas_object_resize(bub, w, h); | ||
61 | |||
62 | x = x - ((lx - (x + (w / 2))) / 4); | ||
63 | y = y - ((ly - (y + (h / 2))) / 4); | ||
64 | |||
65 | evas_object_move(sh, x, y); | ||
66 | evas_object_resize(sh, w, h); | ||
67 | evas_object_raise(sh); | ||
68 | evas_object_raise(bub); | ||
69 | } | ||
70 | return ECORE_CALLBACK_RENEW; | ||
71 | } | ||
72 | |||
73 | static void | ||
74 | _on_delete(Ecore_Evas *ee __UNUSED__) | ||
75 | { | ||
76 | ecore_main_loop_quit(); | ||
77 | } | ||
78 | |||
79 | static void dirList_compile(const char *name, const char *path, void *data) | ||
80 | { | ||
81 | gameGlobals *ourGlobals = data; | ||
82 | |||
83 | char *ext = rindex(name, '.'); | ||
84 | |||
85 | if (ext) | ||
86 | { | ||
87 | if (0 == strcmp(ext, ".lsl")) | ||
88 | { | ||
89 | script *me = calloc(1, sizeof(script)); | ||
90 | |||
91 | scriptCount++; | ||
92 | gettimeofday(&me->startTime, NULL); | ||
93 | snprintf(me->SID, sizeof(me->SID), "%08lx-%04lx-%04lx-%04lx-%012lx", random(), random() % 0xFFFF, random() % 0xFFFF, random() % 0xFFFF, random()); | ||
94 | snprintf(me->fileName, sizeof(me->fileName), "%s/%s", path, name); | ||
95 | eina_hash_add(ourGlobals->scripts, me->SID, me); | ||
96 | sendForth(ourGlobals, me->SID, "compile(%s)", me->fileName); | ||
97 | } | ||
98 | } | ||
99 | } | ||
100 | |||
101 | static Eina_Bool _timer_cb(void *data) | ||
102 | { | ||
103 | gameGlobals *ourGlobals = data; | ||
104 | Eina_Iterator *scripts; | ||
105 | script *me; | ||
106 | boolean exit = FALSE; | ||
107 | |||
108 | scripts = eina_hash_iterator_data_new(ourGlobals->scripts); | ||
109 | while(eina_iterator_next(scripts, (void **) &me)) | ||
110 | { | ||
111 | switch (timedEvent) | ||
112 | { | ||
113 | case 5 : | ||
114 | { | ||
115 | sendForth(ourGlobals, me->SID, "events.detectedKeys({\"%s\"})", ownerKey); | ||
116 | sendForth(ourGlobals, me->SID, "events.detectedNames({\"%s\"})", ownerName); | ||
117 | sendForth(ourGlobals, me->SID, "events.touch_start(1)"); | ||
118 | break; | ||
119 | } | ||
120 | case 9 : | ||
121 | { | ||
122 | sendForth(ourGlobals, me->SID, "quit()"); | ||
123 | break; | ||
124 | } | ||
125 | case 11 : | ||
126 | { | ||
127 | exit = TRUE; | ||
128 | break; | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | timedEvent++; | ||
133 | |||
134 | if (exit) | ||
135 | { | ||
136 | sendForth(ourGlobals, ownerKey, "exit()"); | ||
137 | ecore_main_loop_quit(); | ||
138 | return ECORE_CALLBACK_CANCEL; | ||
139 | } | ||
140 | return ECORE_CALLBACK_RENEW; | ||
141 | } | ||
142 | |||
143 | static Eina_Bool _add(void *data, int type __UNUSED__, Ecore_Con_Event_Server_Add *ev) | ||
144 | { | ||
145 | gameGlobals *ourGlobals = data; | ||
146 | char buf[PATH_MAX]; | ||
147 | |||
148 | ourGlobals->server = ev->server; | ||
149 | gettimeofday(&startTime, NULL); | ||
150 | snprintf(buf, sizeof(buf), "%s/media/Test sim/objects", PACKAGE_DATA_DIR); | ||
151 | eina_file_dir_list(buf, EINA_TRUE, dirList_compile, ourGlobals); | ||
152 | // Wait awhile, then start sending events for testing. | ||
153 | ecore_timer_add(0.5, _timer_cb, ourGlobals); | ||
154 | return ECORE_CALLBACK_RENEW; | ||
155 | } | ||
156 | |||
157 | static Eina_Bool _data(void *data, int type __UNUSED__, Ecore_Con_Event_Server_Data *ev) | ||
158 | { | ||
159 | gameGlobals *ourGlobals = data; | ||
160 | |||
161 | char buf[PATH_MAX]; | ||
162 | char SID[PATH_MAX]; | ||
163 | const char *command; | ||
164 | char *ext; | ||
165 | |||
166 | eina_strbuf_append_length(clientStream, ev->data, ev->size); | ||
167 | command = eina_strbuf_string_get(clientStream); | ||
168 | while ((ext = index(command, '\n'))) | ||
169 | { | ||
170 | int length = ext - command; | ||
171 | |||
172 | strncpy(SID, command, length + 1); | ||
173 | SID[length] = '\0'; | ||
174 | eina_strbuf_remove(clientStream, 0, length + 1); | ||
175 | ext = index(SID, '.'); | ||
176 | if (ext) | ||
177 | { | ||
178 | script *me; | ||
179 | |||
180 | ext[0] = '\0'; | ||
181 | command = ext + 1; | ||
182 | me = eina_hash_find(ourGlobals->scripts, SID); | ||
183 | if (0 == strncmp(command, "compilerWarning(", 16)) | ||
184 | { | ||
185 | char *temp; | ||
186 | char *line; | ||
187 | char *column; | ||
188 | char *text; | ||
189 | |||
190 | strcpy(buf, &command[16]); | ||
191 | temp = buf; | ||
192 | line = temp; | ||
193 | while (',' != temp[0]) | ||
194 | temp++; | ||
195 | temp[0] = '\0'; | ||
196 | column = ++temp; | ||
197 | while (',' != temp[0]) | ||
198 | temp++; | ||
199 | temp[0] = '\0'; | ||
200 | text = ++temp; | ||
201 | while (')' != temp[0]) | ||
202 | temp++; | ||
203 | temp[0] = '\0'; | ||
204 | PW("%s @ line %s, column %s.", text, line, column); | ||
205 | if (me) | ||
206 | me->warnings++; | ||
207 | } | ||
208 | else if (0 == strncmp(command, "compilerError(", 14)) | ||
209 | { | ||
210 | char *temp; | ||
211 | char *line; | ||
212 | char *column; | ||
213 | char *text; | ||
214 | |||
215 | strcpy(buf, &command[14]); | ||
216 | temp = buf; | ||
217 | line = temp; | ||
218 | while (',' != temp[0]) | ||
219 | temp++; | ||
220 | temp[0] = '\0'; | ||
221 | column = ++temp; | ||
222 | while (',' != temp[0]) | ||
223 | temp++; | ||
224 | temp[0] = '\0'; | ||
225 | text = ++temp; | ||
226 | while (')' != temp[0]) | ||
227 | temp++; | ||
228 | temp[0] = '\0'; | ||
229 | PE("%s @ line %s, column %s.", text, line, column); | ||
230 | if (me) | ||
231 | me->bugs++; | ||
232 | } | ||
233 | else if (0 == strcmp(command, "compiled(false)")) | ||
234 | PE("The compile of %s failed!", SID); | ||
235 | else if (0 == strcmp(command, "compiled(true)")) | ||
236 | { | ||
237 | if (me) | ||
238 | { | ||
239 | struct timeval now; | ||
240 | |||
241 | me->compileTime = timeDiff(&now, &me->startTime); | ||
242 | me->running = TRUE; | ||
243 | compiledCount++; | ||
244 | compileTime += me->compileTime; | ||
245 | // PD("Average compile speed is %f scripts per second", compiledCount / compileTime); | ||
246 | if (compiledCount == scriptCount) | ||
247 | PD("TOTAL compile speed is %f scripts per second", compiledCount / timeDiff(&now, &startTime)); | ||
248 | } | ||
249 | // PD("The compile of %s worked, running it now.", SID); | ||
250 | sendForth(ourGlobals, SID, "run()"); | ||
251 | } | ||
252 | else | ||
253 | { | ||
254 | // Send back some random or fixed values for testing. | ||
255 | if (0 == strcmp(command, "llGetKey()")) | ||
256 | sendForth(ourGlobals, SID, "return \"%08lx-%04lx-%04lx-%04lx-%012lx\"", random(), random() % 0xFFFF, random() % 0xFFFF, random() % 0xFFFF, random()); | ||
257 | else if (0 == strcmp(command, "llGetOwner()")) | ||
258 | sendForth(ourGlobals, SID, "return \"%s\"", ownerKey); | ||
259 | else if (0 == strcmp(command, "llGetPos()")) | ||
260 | sendForth(ourGlobals, SID, "return {x=128.0, y=128.0, z=128.0}"); | ||
261 | else if (0 == strcmp(command, "llGetRot()")) | ||
262 | sendForth(ourGlobals, SID, "return {x=0.0, y=0.0, z=0.0, s=1.0}"); | ||
263 | else if (0 == strcmp(command, "llGetObjectDesc()")) | ||
264 | sendForth(ourGlobals, SID, "return \"\""); | ||
265 | else if (0 == strncmp(command, "llGetAlpha(", 11)) | ||
266 | sendForth(ourGlobals, SID, "return 1.0"); | ||
267 | else if (0 == strcmp(command, "llGetInventoryNumber(7)")) | ||
268 | sendForth(ourGlobals, SID, "return 3"); | ||
269 | else if (0 == strcmp(command, "llGetInventoryName(7, 2)")) | ||
270 | sendForth(ourGlobals, SID, "return \".readme\""); | ||
271 | else if (0 == strcmp(command, "llGetInventoryName(7, 1)")) | ||
272 | sendForth(ourGlobals, SID, "return \".POSITIONS\""); | ||
273 | else if (0 == strcmp(command, "llGetInventoryName(7, 0)")) | ||
274 | sendForth(ourGlobals, SID, "return \".MENUITEMS\""); | ||
275 | else | ||
276 | PI("Script %s sent command %s", SID, command); | ||
277 | } | ||
278 | } | ||
279 | |||
280 | // Get the next blob to check it. | ||
281 | command = eina_strbuf_string_get(clientStream); | ||
282 | } | ||
283 | |||
284 | return ECORE_CALLBACK_RENEW; | ||
285 | } | ||
286 | |||
287 | static Eina_Bool _del(void *data, int type __UNUSED__, Ecore_Con_Event_Server_Del *ev) | ||
288 | { | ||
289 | gameGlobals *ourGlobals = data; | ||
290 | |||
291 | if (ev->server) | ||
292 | { | ||
293 | ourGlobals->server = NULL; | ||
294 | ecore_con_server_del(ev->server); | ||
295 | if (!ourGlobals->ui) | ||
296 | ecore_main_loop_quit(); | ||
297 | } | ||
298 | |||
299 | return ECORE_CALLBACK_RENEW; | ||
300 | } | ||
301 | |||
302 | int main(int argc, char **argv) | ||
303 | { | ||
304 | /* put here any init specific to this app like parsing args etc. */ | ||
305 | gameGlobals ourGlobals; | ||
306 | char *programName = argv[0]; | ||
307 | boolean badArgs = FALSE; | ||
308 | int result = EXIT_FAILURE; | ||
309 | |||
310 | memset(&ourGlobals, 0, sizeof(gameGlobals)); | ||
311 | ourGlobals.address = "127.0.0.1"; | ||
312 | ourGlobals.port = 8211; | ||
313 | |||
314 | if (eina_init()) | ||
315 | { | ||
316 | ourGlobals.logDom = loggingStartup("LuaSL_test", ourGlobals.logDom); | ||
317 | ourGlobals.scripts = eina_hash_string_superfast_new(NULL); | ||
318 | |||
319 | if (ecore_con_init()) | ||
320 | { | ||
321 | if ((ourGlobals.server = ecore_con_server_connect(ECORE_CON_REMOTE_TCP, ourGlobals.address, ourGlobals.port, &ourGlobals))) | ||
322 | { | ||
323 | ecore_event_handler_add(ECORE_CON_EVENT_SERVER_ADD, (Ecore_Event_Handler_Cb) _add, &ourGlobals); | ||
324 | ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DATA, (Ecore_Event_Handler_Cb) _data, &ourGlobals); | ||
325 | ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DEL, (Ecore_Event_Handler_Cb) _del, &ourGlobals); | ||
326 | clientStream = eina_strbuf_new(); | ||
327 | |||
328 | if (ecore_evas_init()) | ||
329 | { | ||
330 | if (edje_init()) | ||
331 | { | ||
332 | // get the arguments passed in | ||
333 | while (--argc > 0 && *++argv != '\0') | ||
334 | { | ||
335 | if (*argv[0] == '-') | ||
336 | { | ||
337 | // point to the characters after the '-' sign | ||
338 | char *s = argv[0] + 1; | ||
339 | |||
340 | switch (*s) | ||
341 | { | ||
342 | case 'u': | ||
343 | { | ||
344 | ourGlobals.ui = TRUE; | ||
345 | break; | ||
346 | } | ||
347 | default: | ||
348 | badArgs = TRUE; | ||
349 | } | ||
350 | } | ||
351 | else | ||
352 | badArgs = TRUE; | ||
353 | } | ||
354 | |||
355 | if (badArgs) | ||
356 | { | ||
357 | // display the program usage to the user as they have it wrong | ||
358 | printf("Usage: %s [-u]\n", programName); | ||
359 | printf(" -u: Show the test UI.\n"); | ||
360 | } | ||
361 | else | ||
362 | // else if ((ourGlobals.config) && (ourGlobals.data)) | ||
363 | { | ||
364 | unsigned int i; | ||
365 | Evas_Object *bub, *sh; | ||
366 | Ecore_Animator *ani; | ||
367 | char *group = "main"; | ||
368 | char buf[PATH_MAX]; | ||
369 | |||
370 | if (ourGlobals.ui) | ||
371 | { | ||
372 | /* this will give you a window with an Evas canvas under the first engine available */ | ||
373 | ourGlobals.ee = ecore_evas_new(NULL, 0, 0, WIDTH, HEIGHT, NULL); | ||
374 | if (!ourGlobals.ee) | ||
375 | { | ||
376 | PEm("You got to have at least one evas engine built and linked up to ecore-evas for this example to run properly."); | ||
377 | edje_shutdown(); | ||
378 | ecore_evas_shutdown(); | ||
379 | return -1; | ||
380 | } | ||
381 | ourGlobals.canvas = ecore_evas_get(ourGlobals.ee); | ||
382 | ecore_evas_title_set(ourGlobals.ee, "LuaSL test harness"); | ||
383 | ecore_evas_show(ourGlobals.ee); | ||
384 | |||
385 | ourGlobals.bg = evas_object_rectangle_add(ourGlobals.canvas); | ||
386 | evas_object_color_set(ourGlobals.bg, 255, 255, 255, 255); /* white bg */ | ||
387 | evas_object_move(ourGlobals.bg, 0, 0); /* at canvas' origin */ | ||
388 | evas_object_size_hint_weight_set(ourGlobals.bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); | ||
389 | evas_object_resize(ourGlobals.bg, WIDTH, HEIGHT); /* covers full canvas */ | ||
390 | evas_object_show(ourGlobals.bg); | ||
391 | ecore_evas_object_associate(ourGlobals.ee, ourGlobals.bg, ECORE_EVAS_OBJECT_ASSOCIATE_BASE); | ||
392 | evas_object_focus_set(ourGlobals.bg, EINA_TRUE); | ||
393 | |||
394 | ourGlobals.edje = edje_object_add(ourGlobals.canvas); | ||
395 | snprintf(buf, sizeof(buf), "%s/media/%s.edj", PACKAGE_DATA_DIR, "LuaSL"); | ||
396 | if (!edje_object_file_set(ourGlobals.edje, buf, group)) | ||
397 | { | ||
398 | int err = edje_object_load_error_get(ourGlobals.edje); | ||
399 | const char *errmsg = edje_load_error_str(err); | ||
400 | PEm("Could not load '%s' from %s: %s\n", group, buf, errmsg); | ||
401 | |||
402 | evas_object_del(ourGlobals.edje); | ||
403 | ecore_evas_free(ourGlobals.ee); | ||
404 | edje_shutdown(); | ||
405 | ecore_evas_shutdown(); | ||
406 | return -2; | ||
407 | } | ||
408 | evas_object_move(ourGlobals.edje, 0, 0); | ||
409 | evas_object_resize(ourGlobals.edje, WIDTH, HEIGHT); | ||
410 | evas_object_show(ourGlobals.edje); | ||
411 | |||
412 | snprintf(buf, sizeof(buf), "%s/media/bubble_sh.png", PACKAGE_DATA_DIR); | ||
413 | for (i = 0; i < (sizeof(names) / sizeof(char *) / 2); i++) | ||
414 | { | ||
415 | sh = evas_object_image_filled_add(ourGlobals.canvas); | ||
416 | evas_object_image_file_set(sh, buf, NULL); | ||
417 | evas_object_resize(sh, 64, 64); | ||
418 | evas_object_show(sh); | ||
419 | evas_object_data_set(ourGlobals.bg, names[(i * 2) + 1], sh); | ||
420 | } | ||
421 | |||
422 | snprintf(buf, sizeof(buf), "%s/media/bubble.png", PACKAGE_DATA_DIR); | ||
423 | for (i = 0; i < (sizeof(names) / sizeof(char *) / 2); i++) | ||
424 | { | ||
425 | bub = evas_object_image_filled_add(ourGlobals.canvas); | ||
426 | evas_object_image_file_set(bub, buf, NULL); | ||
427 | evas_object_resize(bub, 64, 64); | ||
428 | evas_object_show(bub); | ||
429 | evas_object_data_set(ourGlobals.bg, names[(i * 2)], bub); | ||
430 | } | ||
431 | ani = ecore_animator_add(anim, &ourGlobals); | ||
432 | evas_object_data_set(ourGlobals.bg, "animator", ani); | ||
433 | |||
434 | // Setup our callbacks. | ||
435 | ecore_evas_callback_delete_request_set(ourGlobals.ee, _on_delete); | ||
436 | edje_object_signal_callback_add(ourGlobals.edje, "*", "game_*", _edje_signal_cb, &ourGlobals); | ||
437 | } | ||
438 | |||
439 | ecore_main_loop_begin(); | ||
440 | if (ourGlobals.ui) | ||
441 | { | ||
442 | ecore_animator_del(ani); | ||
443 | ecore_evas_free(ourGlobals.ee); | ||
444 | } | ||
445 | } | ||
446 | |||
447 | edje_shutdown(); | ||
448 | } | ||
449 | else | ||
450 | PCm("Failed to init edje!"); | ||
451 | ecore_evas_shutdown(); | ||
452 | } | ||
453 | else | ||
454 | PCm("Failed to init ecore_evas!"); | ||
455 | } | ||
456 | else | ||
457 | PCm("Failed to connect to server!"); | ||
458 | ecore_con_shutdown(); | ||
459 | } | ||
460 | else | ||
461 | PCm("Failed to init ecore_con!"); | ||
462 | } | ||
463 | else | ||
464 | fprintf(stderr, "Failed to init eina!"); | ||
465 | |||
466 | return result; | ||
467 | } | ||
diff --git a/src/LuaSL/LuaSL_threads.c b/src/LuaSL/LuaSL_threads.c new file mode 100644 index 0000000..234a7c5 --- /dev/null +++ b/src/LuaSL/LuaSL_threads.c | |||
@@ -0,0 +1,496 @@ | |||
1 | /* This code is heavily based on luaproc. | ||
2 | * | ||
3 | * The luaproc copyright notice and license is - | ||
4 | |||
5 | *************************************************** | ||
6 | |||
7 | Copyright 2008 Alexandre Skyrme, Noemi Rodriguez, Roberto Ierusalimschy | ||
8 | |||
9 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
10 | of this software and associated documentation files (the "Software"), to deal | ||
11 | in the Software without restriction, including without limitation the rights | ||
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
13 | copies of the Software, and to permit persons to whom the Software is | ||
14 | furnished to do so, subject to the following conditions: | ||
15 | |||
16 | The above copyright notice and this permission notice shall be included in | ||
17 | all copies or substantial portions of the Software. | ||
18 | |||
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
25 | THE SOFTWARE. | ||
26 | |||
27 | **************************************************** | ||
28 | * | ||
29 | * Additions and changes Copyright 2012 by David Seikel, using the above license. | ||
30 | */ | ||
31 | |||
32 | |||
33 | /* This is a redesign of luaproc. The design goals and notes - | ||
34 | * | ||
35 | * In general use EFL where it is useful. | ||
36 | * Probably one fixed unique message channel per object, which each script in the object shares. | ||
37 | * But might be better to handle that C side anyway. | ||
38 | * Better integration with LuaSL. | ||
39 | * Use ecore threads instead of raw pthreads. | ||
40 | * Ecore threads pretty much wraps pthreads on posix, but has Windows support to. | ||
41 | * Merge in the edje Lua code, and keep an eye on that, coz we might want to actually add this to edje Lua in the future. | ||
42 | * Use my coding standards, or EFL ones. Pffft. | ||
43 | * | ||
44 | */ | ||
45 | |||
46 | #include "LuaSL.h" | ||
47 | |||
48 | |||
49 | /* ready process queue insertion status */ | ||
50 | #define LUAPROC_SCHED_QUEUE_PROC_OK 0 | ||
51 | //#define LUAPROC_SCHED_QUEUE_PROC_ERR -1 | ||
52 | |||
53 | /* process is idle */ | ||
54 | #define LUAPROC_STAT_IDLE 0 | ||
55 | /* process is ready to run */ | ||
56 | #define LUAPROC_STAT_READY 1 | ||
57 | /* process is blocked on send */ | ||
58 | #define LUAPROC_STAT_BLOCKED_SEND 2 | ||
59 | /* process is blocked on receive */ | ||
60 | #define LUAPROC_STAT_BLOCKED_RECV 3 | ||
61 | |||
62 | |||
63 | typedef struct | ||
64 | { | ||
65 | Eina_Clist node; | ||
66 | lua_State *L; | ||
67 | } recycled; | ||
68 | |||
69 | /********* | ||
70 | * globals | ||
71 | *********/ | ||
72 | |||
73 | /* ready process list */ | ||
74 | Eina_Clist lpready; | ||
75 | |||
76 | /* ready process queue access mutex */ | ||
77 | pthread_mutex_t mutex_queue_access = PTHREAD_MUTEX_INITIALIZER; | ||
78 | |||
79 | /* wake worker up conditional variable */ | ||
80 | pthread_cond_t cond_wakeup_worker = PTHREAD_COND_INITIALIZER; | ||
81 | |||
82 | /* active luaproc count access mutex */ | ||
83 | pthread_mutex_t mutex_lp_count = PTHREAD_MUTEX_INITIALIZER; | ||
84 | |||
85 | /* no active luaproc conditional variable */ | ||
86 | pthread_cond_t cond_no_active_lp = PTHREAD_COND_INITIALIZER; | ||
87 | |||
88 | /* number of active luaprocs */ | ||
89 | int lpcount = 0; | ||
90 | |||
91 | /* no more lua processes flag */ | ||
92 | int no_more_processes = FALSE; | ||
93 | |||
94 | /* channel operations mutex */ | ||
95 | pthread_mutex_t mutex_channel = PTHREAD_MUTEX_INITIALIZER; | ||
96 | |||
97 | /* recycle list mutex */ | ||
98 | pthread_mutex_t mutex_recycle_list = PTHREAD_MUTEX_INITIALIZER; | ||
99 | |||
100 | /* recycled lua process list */ | ||
101 | Eina_Clist recyclelp; | ||
102 | |||
103 | |||
104 | /****************************** | ||
105 | * library functions prototypes | ||
106 | ******************************/ | ||
107 | /* send a message to a lua process */ | ||
108 | static int luaproc_send( lua_State *L ); | ||
109 | /* receive a message from a lua process */ | ||
110 | static int luaproc_receive( lua_State *L ); | ||
111 | /* send a message back to the main loop */ | ||
112 | static int luaproc_send_back( lua_State *L ); | ||
113 | |||
114 | /* luaproc function registration array - main (parent) functions */ | ||
115 | static const struct luaL_reg luaproc_funcs_parent[] = { | ||
116 | { "sendback", luaproc_send_back }, | ||
117 | { NULL, NULL } | ||
118 | }; | ||
119 | |||
120 | /* luaproc function registration array - newproc (child) functions */ | ||
121 | static const struct luaL_reg luaproc_funcs_child[] = { | ||
122 | { "send", luaproc_send }, | ||
123 | { "receive", luaproc_receive }, | ||
124 | { "sendback", luaproc_send_back }, | ||
125 | { NULL, NULL } | ||
126 | }; | ||
127 | |||
128 | |||
129 | /* increase active lua process count */ | ||
130 | static void sched_lpcount_inc(void) | ||
131 | { | ||
132 | pthread_mutex_lock(&mutex_lp_count); | ||
133 | lpcount++; | ||
134 | pthread_mutex_unlock(&mutex_lp_count); | ||
135 | } | ||
136 | |||
137 | /* decrease active lua process count */ | ||
138 | static void sched_lpcount_dec(void) | ||
139 | { | ||
140 | pthread_mutex_lock(&mutex_lp_count); | ||
141 | lpcount--; | ||
142 | /* if count reaches zero, signal there are no more active processes */ | ||
143 | if (lpcount == 0) | ||
144 | pthread_cond_signal(&cond_no_active_lp); | ||
145 | pthread_mutex_unlock(&mutex_lp_count); | ||
146 | } | ||
147 | |||
148 | /* worker thread main function */ | ||
149 | static void *workermain( void *args ) { | ||
150 | |||
151 | script *lp; | ||
152 | int procstat; | ||
153 | |||
154 | /* detach thread so resources are freed as soon as thread exits (no further joining) */ | ||
155 | pthread_detach( pthread_self( )); | ||
156 | |||
157 | /* main worker loop */ | ||
158 | while ( 1 ) { | ||
159 | |||
160 | /* get exclusive access to the ready process queue */ | ||
161 | pthread_mutex_lock( &mutex_queue_access ); | ||
162 | |||
163 | /* wait until instructed to wake up (because there's work to do or because its time to finish) */ | ||
164 | while (( eina_clist_count( &lpready ) == 0 ) && ( no_more_processes == FALSE )) { | ||
165 | pthread_cond_wait( &cond_wakeup_worker, &mutex_queue_access ); | ||
166 | } | ||
167 | |||
168 | /* pop the first node from the ready process queue */ | ||
169 | if ((lp = (script *) eina_clist_head(&lpready))) | ||
170 | eina_clist_remove(&(lp->node)); | ||
171 | else { | ||
172 | /* free access to the process ready queue */ | ||
173 | pthread_mutex_unlock( &mutex_queue_access ); | ||
174 | /* finished thread */ | ||
175 | pthread_exit( NULL ); | ||
176 | } | ||
177 | |||
178 | /* free access to the process ready queue */ | ||
179 | pthread_mutex_unlock( &mutex_queue_access ); | ||
180 | |||
181 | /* execute the lua code specified in the lua process struct */ | ||
182 | procstat = lua_resume(lp->L, lp->args); | ||
183 | /* reset the process argument count */ | ||
184 | lp->args = 0; | ||
185 | |||
186 | /* check if process finished its whole execution, then recycle it */ | ||
187 | if (procstat == 0) | ||
188 | { | ||
189 | recycled *trash = malloc(sizeof(recycled)); | ||
190 | |||
191 | if (trash) | ||
192 | { | ||
193 | trash->L = lp->L; | ||
194 | pthread_mutex_lock(&mutex_recycle_list); | ||
195 | eina_clist_add_tail(&recyclelp, &(trash->node)); | ||
196 | pthread_mutex_unlock(&mutex_recycle_list); | ||
197 | sched_lpcount_dec(); | ||
198 | } | ||
199 | lua_close(lp->L); | ||
200 | if (lp->timer) | ||
201 | ecore_timer_del(lp->timer); | ||
202 | free(lp); | ||
203 | } | ||
204 | |||
205 | /* check if process yielded */ | ||
206 | else if ( procstat == LUA_YIELD ) { | ||
207 | |||
208 | /* if so, further check if yield originated from an unmatched send/recv operation */ | ||
209 | if (lp->status == LUAPROC_STAT_BLOCKED_SEND) | ||
210 | { | ||
211 | } | ||
212 | else if (lp->status == LUAPROC_STAT_BLOCKED_RECV) | ||
213 | { | ||
214 | } | ||
215 | /* or if yield resulted from an explicit call to coroutine.yield in the lua code being executed */ | ||
216 | else | ||
217 | { | ||
218 | /* get exclusive access to the ready process queue */ | ||
219 | pthread_mutex_lock( &mutex_queue_access ); | ||
220 | /* re-insert the job at the end of the ready process queue */ | ||
221 | eina_clist_add_tail(&lpready, &(lp->node)); | ||
222 | /* free access to the process ready queue */ | ||
223 | pthread_mutex_unlock( &mutex_queue_access ); | ||
224 | } | ||
225 | } | ||
226 | /* check if there was any execution error (LUA_ERRRUN, LUA_ERRSYNTAX, LUA_ERRMEM or LUA_ERRERR) */ | ||
227 | else | ||
228 | { | ||
229 | /* print error message */ | ||
230 | fprintf( stderr, "close lua_State (error: %s)\n", luaL_checkstring(lp->L, -1 )); | ||
231 | /* close lua state */ | ||
232 | lua_close(lp->L); | ||
233 | /* decrease active lua process count */ | ||
234 | sched_lpcount_dec(); | ||
235 | } | ||
236 | } | ||
237 | } | ||
238 | |||
239 | /* move process to ready queue (ie, schedule process) */ | ||
240 | static int sched_queue_proc( script *lp ) { | ||
241 | |||
242 | /* get exclusive access to the ready process queue */ | ||
243 | pthread_mutex_lock( &mutex_queue_access ); | ||
244 | |||
245 | /* add process to ready queue */ | ||
246 | eina_clist_add_tail(&lpready, &(lp->node)); | ||
247 | |||
248 | lp->status = LUAPROC_STAT_READY; | ||
249 | |||
250 | /* wake worker up */ | ||
251 | pthread_cond_signal( &cond_wakeup_worker ); | ||
252 | /* free access to the process ready queue */ | ||
253 | pthread_mutex_unlock( &mutex_queue_access ); | ||
254 | |||
255 | return LUAPROC_SCHED_QUEUE_PROC_OK; | ||
256 | } | ||
257 | |||
258 | /* synchronize worker threads */ | ||
259 | void sched_join_workerthreads( void ) { | ||
260 | |||
261 | pthread_mutex_lock( &mutex_lp_count ); | ||
262 | |||
263 | /* wait until there is no more active lua processes */ | ||
264 | while( lpcount != 0 ) { | ||
265 | pthread_cond_wait( &cond_no_active_lp, &mutex_lp_count ); | ||
266 | } | ||
267 | /* get exclusive access to the ready process queue */ | ||
268 | pthread_mutex_lock( &mutex_queue_access ); | ||
269 | /* set the no more active lua processes flag to true */ | ||
270 | no_more_processes = TRUE; | ||
271 | /* wake ALL workers up */ | ||
272 | pthread_cond_broadcast( &cond_wakeup_worker ); | ||
273 | /* free access to the process ready queue */ | ||
274 | pthread_mutex_unlock( &mutex_queue_access ); | ||
275 | |||
276 | // We don't need this, as we only get here during shutdown. Linking this to EFL results in a hang otherwise anyway. | ||
277 | /* wait for (join) worker threads */ | ||
278 | // pthread_exit( NULL ); | ||
279 | |||
280 | pthread_mutex_unlock( &mutex_lp_count ); | ||
281 | |||
282 | } | ||
283 | |||
284 | /* create a new worker pthread */ | ||
285 | int sched_create_worker(void) | ||
286 | { | ||
287 | pthread_t worker; | ||
288 | |||
289 | /* create a new pthread */ | ||
290 | if (pthread_create( &worker, NULL, workermain, NULL ) != 0) | ||
291 | return LUAPROC_SCHED_PTHREAD_ERROR; | ||
292 | return LUAPROC_SCHED_OK; | ||
293 | } | ||
294 | |||
295 | void newProc(const char *code, int file, script *lp) | ||
296 | { | ||
297 | int ret; | ||
298 | recycled *trash; | ||
299 | |||
300 | // Try to recycle a Lua state, otherwise create one from scratch. | ||
301 | pthread_mutex_lock(&mutex_recycle_list); | ||
302 | /* pop list head */ | ||
303 | if ((trash = (recycled *) eina_clist_head(&recyclelp))) | ||
304 | { | ||
305 | eina_clist_remove(&(trash->node)); | ||
306 | lp->L = trash->L; | ||
307 | free(trash); | ||
308 | } | ||
309 | pthread_mutex_unlock(&mutex_recycle_list); | ||
310 | |||
311 | if (NULL == lp->L) | ||
312 | { | ||
313 | lp->L = luaL_newstate(); | ||
314 | |||
315 | /* store the script struct in its own Lua state */ | ||
316 | lua_pushlightuserdata(lp->L, lp); | ||
317 | lua_setfield(lp->L, LUA_REGISTRYINDEX, "_SELF"); | ||
318 | luaL_openlibs(lp->L); | ||
319 | luaL_register(lp->L, "luaproc", luaproc_funcs_child); | ||
320 | } | ||
321 | |||
322 | lp->status = LUAPROC_STAT_IDLE; | ||
323 | lp->args = 0; | ||
324 | eina_clist_element_init(&(lp->node)); | ||
325 | eina_clist_init(&(lp->messages)); | ||
326 | |||
327 | /* load process' code */ | ||
328 | if (file) | ||
329 | ret = luaL_loadfile(lp->L, code); | ||
330 | else | ||
331 | ret = luaL_loadstring(lp->L, code); | ||
332 | |||
333 | /* in case of errors, destroy Lua process */ | ||
334 | if (ret != 0) | ||
335 | { | ||
336 | lua_close(lp->L); | ||
337 | lp->L = NULL; | ||
338 | } | ||
339 | |||
340 | if (lp->L) | ||
341 | { | ||
342 | sched_lpcount_inc(); | ||
343 | |||
344 | /* schedule luaproc */ | ||
345 | if (sched_queue_proc(lp) != LUAPROC_SCHED_QUEUE_PROC_OK) | ||
346 | { | ||
347 | printf( "[luaproc] error queueing Lua process\n" ); | ||
348 | sched_lpcount_dec(); | ||
349 | lua_close(lp->L); | ||
350 | } | ||
351 | } | ||
352 | } | ||
353 | |||
354 | /* return the lua process associated with a given lua state */ | ||
355 | static script *luaproc_getself(lua_State *L) | ||
356 | { | ||
357 | script *lp; | ||
358 | |||
359 | lua_getfield(L, LUA_REGISTRYINDEX, "_SELF"); | ||
360 | lp = (script *) lua_touserdata(L, -1); | ||
361 | lua_pop(L, 1); | ||
362 | return lp; | ||
363 | } | ||
364 | |||
365 | /* send a message to the client process */ | ||
366 | static int luaproc_send_back(lua_State *L) | ||
367 | { | ||
368 | script *self = luaproc_getself(L); | ||
369 | const char *message = luaL_checkstring(L, 1); | ||
370 | |||
371 | if (self) | ||
372 | { | ||
373 | scriptMessage *sm = calloc(1, sizeof(scriptMessage)); | ||
374 | |||
375 | if (sm) | ||
376 | { | ||
377 | eina_clist_element_init(&(sm->node)); | ||
378 | sm->script = self; | ||
379 | strcpy((char *) sm->message, message); | ||
380 | ecore_main_loop_thread_safe_call_async(scriptSendBack, sm); | ||
381 | } | ||
382 | } | ||
383 | |||
384 | return 0; | ||
385 | } | ||
386 | |||
387 | /* error messages for the sendToChannel function */ | ||
388 | const char *sendToChannelErrors[] = | ||
389 | { | ||
390 | "non-existent channel", | ||
391 | "error scheduling process" | ||
392 | }; | ||
393 | |||
394 | /* send a message to a lua process */ | ||
395 | const char *sendToChannel(gameGlobals *ourGlobals, const char *SID, const char *message) | ||
396 | { | ||
397 | const char *result = NULL; | ||
398 | script *dstlp; | ||
399 | |||
400 | /* get exclusive access to operate on channels */ | ||
401 | pthread_mutex_lock(&mutex_channel); | ||
402 | |||
403 | // Add the message to the queue. | ||
404 | if ((dstlp = eina_hash_find(ourGlobals->scripts, SID))) | ||
405 | { | ||
406 | scriptMessage *sm = NULL; | ||
407 | |||
408 | if ((sm = malloc(sizeof(scriptMessage)))) | ||
409 | { | ||
410 | sm->script = dstlp; | ||
411 | strcpy((char *) sm->message, message); | ||
412 | eina_clist_add_tail(&(dstlp->messages), &(sm->node)); | ||
413 | } | ||
414 | |||
415 | /* if it's already waiting, send the next message to it and (queue) wake it */ | ||
416 | if (dstlp->status == LUAPROC_STAT_BLOCKED_RECV) | ||
417 | { | ||
418 | scriptMessage *msg = (scriptMessage *) eina_clist_head(&(dstlp->messages)); | ||
419 | |||
420 | // See if there's a message on the queue. Note, this may not be the same as the incoming message, if there was already a queue. | ||
421 | if (msg) | ||
422 | { | ||
423 | eina_clist_remove(&(msg->node)); | ||
424 | message = msg->message; | ||
425 | } | ||
426 | /* push the message onto the receivers stack */ | ||
427 | lua_pushstring(dstlp->L, message); | ||
428 | dstlp->args = lua_gettop(dstlp->L) - 1; | ||
429 | if (msg) | ||
430 | free(msg); | ||
431 | |||
432 | if (sched_queue_proc(dstlp) != LUAPROC_SCHED_QUEUE_PROC_OK) | ||
433 | { | ||
434 | sched_lpcount_dec(); | ||
435 | lua_close(dstlp->L); | ||
436 | result = sendToChannelErrors[1]; | ||
437 | } | ||
438 | } | ||
439 | } | ||
440 | |||
441 | pthread_mutex_unlock(&mutex_channel); | ||
442 | |||
443 | return result; | ||
444 | } | ||
445 | |||
446 | /* send a message to a lua process */ | ||
447 | static int luaproc_send(lua_State *L) | ||
448 | { | ||
449 | script *self = luaproc_getself(L); | ||
450 | const char *result = sendToChannel(self->game, luaL_checkstring(L, 1), luaL_checkstring(L, 2)); | ||
451 | |||
452 | if (result) | ||
453 | { | ||
454 | lua_pushnil(L); | ||
455 | lua_pushstring(L, result); | ||
456 | return 2; | ||
457 | } | ||
458 | |||
459 | lua_pushboolean(L, TRUE); | ||
460 | return 1; | ||
461 | } | ||
462 | |||
463 | /* receive a message from a lua process */ | ||
464 | static int luaproc_receive(lua_State *L) | ||
465 | { | ||
466 | script *self; | ||
467 | const char *chname = luaL_checkstring(L, 1); | ||
468 | scriptMessage *msg; | ||
469 | |||
470 | // First check if there are queued messages, and grab one. | ||
471 | self = luaproc_getself(L); | ||
472 | if ((msg = (scriptMessage *) eina_clist_head(&(self->messages)))) | ||
473 | { | ||
474 | eina_clist_remove(&(msg->node)); | ||
475 | lua_pushstring(L, msg->message); | ||
476 | free(msg); | ||
477 | return lua_gettop(L) - 1; | ||
478 | } | ||
479 | |||
480 | /* if trying an asynchronous receive, return an error */ | ||
481 | if ( lua_toboolean( L, 2 )) | ||
482 | { | ||
483 | lua_pushnil(L); | ||
484 | lua_pushfstring(L, "no senders waiting on channel %s", chname); | ||
485 | return 2; | ||
486 | } | ||
487 | /* otherwise (synchronous receive) simply block process */ | ||
488 | self->status = LUAPROC_STAT_BLOCKED_RECV; | ||
489 | return lua_yield(L, lua_gettop(L)); | ||
490 | } | ||
491 | |||
492 | void luaprocInit(void) | ||
493 | { | ||
494 | eina_clist_init(&recyclelp); | ||
495 | eina_clist_init(&lpready); | ||
496 | } | ||
diff --git a/src/LuaSL/LuaSL_threads.h b/src/LuaSL/LuaSL_threads.h new file mode 100644 index 0000000..9a11b5c --- /dev/null +++ b/src/LuaSL/LuaSL_threads.h | |||
@@ -0,0 +1,54 @@ | |||
1 | /* This code is heavily based on luaproc. | ||
2 | * | ||
3 | * The luaproc copyright notice and license is - | ||
4 | |||
5 | *************************************************** | ||
6 | |||
7 | Copyright 2008 Alexandre Skyrme, Noemi Rodriguez, Roberto Ierusalimschy | ||
8 | |||
9 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
10 | of this software and associated documentation files (the "Software"), to deal | ||
11 | in the Software without restriction, including without limitation the rights | ||
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
13 | copies of the Software, and to permit persons to whom the Software is | ||
14 | furnished to do so, subject to the following conditions: | ||
15 | |||
16 | The above copyright notice and this permission notice shall be included in | ||
17 | all copies or substantial portions of the Software. | ||
18 | |||
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
25 | THE SOFTWARE. | ||
26 | |||
27 | **************************************************** | ||
28 | * | ||
29 | * Additions and changes Copyright 2012 by David Seikel, using the above license. | ||
30 | */ | ||
31 | |||
32 | #ifndef __LUASL_THREADS_H__ | ||
33 | #define __LUASL_THREADS_H__ | ||
34 | |||
35 | /* scheduler function return constants */ | ||
36 | #define LUAPROC_SCHED_OK 0 | ||
37 | #define LUAPROC_SCHED_SOCKET_ERROR -1 | ||
38 | #define LUAPROC_SCHED_SETSOCKOPT_ERROR -2 | ||
39 | #define LUAPROC_SCHED_BIND_ERROR -3 | ||
40 | #define LUAPROC_SCHED_LISTEN_ERROR -4 | ||
41 | #define LUAPROC_SCHED_FORK_ERROR -5 | ||
42 | #define LUAPROC_SCHED_PTHREAD_ERROR -6 | ||
43 | #define LUAPROC_SCHED_INIT_ERROR -7 | ||
44 | |||
45 | |||
46 | void luaprocInit(void); | ||
47 | int sched_create_worker(void); | ||
48 | void newProc(const char *code, int file, script *lp); | ||
49 | const char *sendToChannel(gameGlobals *ourGlobals, const char *SID, const char *message); | ||
50 | |||
51 | /* join all worker threads and exit */ | ||
52 | void sched_join_workerthreads(void); | ||
53 | |||
54 | #endif | ||
diff --git a/src/LuaSL/LuaSL_utilities.c b/src/LuaSL/LuaSL_utilities.c new file mode 100644 index 0000000..40263df --- /dev/null +++ b/src/LuaSL/LuaSL_utilities.c | |||
@@ -0,0 +1,60 @@ | |||
1 | #include "LuaSL.h" | ||
2 | |||
3 | |||
4 | void sendBack(gameGlobals *ourGlobals, Ecore_Con_Client *client, const char *SID, const char *message, ...) | ||
5 | { | ||
6 | va_list args; | ||
7 | char buf[PATH_MAX]; | ||
8 | int length = strlen(SID); | ||
9 | |||
10 | strncpy(buf, SID, length); | ||
11 | buf[length++] = '.'; | ||
12 | va_start(args, message); | ||
13 | length += vsprintf(&buf[length], message, args); | ||
14 | va_end(args); | ||
15 | buf[length++] = '\n'; | ||
16 | buf[length++] = '\0'; | ||
17 | ecore_con_client_send(client, buf, strlen(buf)); | ||
18 | ecore_con_client_flush(client); | ||
19 | } | ||
20 | |||
21 | void sendForth(gameGlobals *ourGlobals, const char *SID, const char *message, ...) | ||
22 | { | ||
23 | va_list args; | ||
24 | char buf[PATH_MAX]; | ||
25 | int length = strlen(SID); | ||
26 | |||
27 | strncpy(buf, SID, length); | ||
28 | buf[length++] = '.'; | ||
29 | va_start(args, message); | ||
30 | length += vsprintf(&buf[length], message, args); | ||
31 | va_end(args); | ||
32 | buf[length++] = '\n'; | ||
33 | buf[length++] = '\0'; | ||
34 | ecore_con_server_send(ourGlobals->server, buf, strlen(buf)); | ||
35 | ecore_con_server_flush(ourGlobals->server); | ||
36 | } | ||
37 | |||
38 | float timeDiff(struct timeval *now, struct timeval *then) | ||
39 | { | ||
40 | if (0 == gettimeofday(now, 0)) | ||
41 | { | ||
42 | struct timeval thisTime = { 0, 0 }; | ||
43 | double result = 0.0; | ||
44 | |||
45 | thisTime.tv_sec = now->tv_sec; | ||
46 | thisTime.tv_usec = now->tv_usec; | ||
47 | if (thisTime.tv_usec < then->tv_usec) | ||
48 | { | ||
49 | thisTime.tv_sec--; | ||
50 | thisTime.tv_usec += 1000000; | ||
51 | } | ||
52 | thisTime.tv_usec -= then->tv_usec; | ||
53 | thisTime.tv_sec -= then->tv_sec; | ||
54 | result = ((double) thisTime.tv_usec) / ((double) 1000000.0); | ||
55 | result += thisTime.tv_sec; | ||
56 | return result; | ||
57 | } | ||
58 | else | ||
59 | return 0.0; | ||
60 | } | ||
diff --git a/src/LuaSL/build.lua b/src/LuaSL/build.lua new file mode 100755 index 0000000..f86715c --- /dev/null +++ b/src/LuaSL/build.lua | |||
@@ -0,0 +1,23 @@ | |||
1 | #!/usr/bin/env lua | ||
2 | |||
3 | local dir = ... | ||
4 | |||
5 | if 'nil' == type(dir) then | ||
6 | local build, err = loadfile('../../build.lua') | ||
7 | if build then | ||
8 | setfenv(build, getfenv(2)) | ||
9 | build(2) | ||
10 | else | ||
11 | print("ERROR - " .. err) | ||
12 | end | ||
13 | dir = workingDir | ||
14 | end | ||
15 | |||
16 | removeFiles(dir, {'../../LuaSL', '*.o', '*.output', '*.backup', '../../media/LuaSL.edj', 'LuaSL_lexer.h', 'LuaSL_lexer.c', 'LuaSL_lemon_yaccer.h', 'LuaSL_lemon_yaccer.c', 'LuaSL_lemon_yaccer.out'}) | ||
17 | |||
18 | -- Run lemon first, flex depends on it to define the symbol values. | ||
19 | runCommand('lemon', dir, '../../libraries/lemon/lemon -s -T../../libraries/lemon/lempar.c LuaSL_lemon_yaccer.y') | ||
20 | runCommand('flex', dir, 'flex -C --outfile=LuaSL_lexer.c --header-file=LuaSL_lexer.h LuaSL_lexer.l') | ||
21 | runCommand('edje_cc', dir, 'edje_cc ' .. EDJE_FLAGS .. ' LuaSL.edc ../../media/LuaSL.edj') | ||
22 | compileFiles('../../LuaSL', dir, {'LuaSL_main', 'LuaSL_compile', 'LuaSL_threads', 'LuaSL_utilities', 'LuaSL_lexer', 'LuaSL_lemon_yaccer'}) | ||
23 | compileFiles('LuaSL_test', dir, {'LuaSL_test', 'LuaSL_utilities'}) | ||
diff --git a/src/LuaSL/test.sh b/src/LuaSL/test.sh new file mode 100755 index 0000000..1c26ade --- /dev/null +++ b/src/LuaSL/test.sh | |||
@@ -0,0 +1,27 @@ | |||
1 | #! /bin/bash | ||
2 | |||
3 | wd=$(pwd) | ||
4 | |||
5 | # Kill any left overs. | ||
6 | killall -KILL LuaSL | ||
7 | export LUA_PATH="$wd/../../libraries/?.lua" | ||
8 | |||
9 | case $@ in | ||
10 | |||
11 | ddd) | ||
12 | ddd ../../LuaSL | ||
13 | ;; | ||
14 | |||
15 | gdb) | ||
16 | gdb ../../LuaSL | ||
17 | ;; | ||
18 | |||
19 | *) | ||
20 | echo "_______________ STARTING LuaSL _______________" | ||
21 | ../../LuaSL & | ||
22 | sleep 1 | ||
23 | echo "_______________ STARTING LuaSL_test _______________" | ||
24 | ./LuaSL_test | ||
25 | ;; | ||
26 | |||
27 | esac | ||
diff --git a/src/extantz/CDemo.cpp b/src/extantz/CDemo.cpp new file mode 100644 index 0000000..0ca40f1 --- /dev/null +++ b/src/extantz/CDemo.cpp | |||
@@ -0,0 +1,510 @@ | |||
1 | // This is a Demo of the Irrlicht Engine (c) 2005-2009 by N.Gebhardt. | ||
2 | // This file is not documented. | ||
3 | |||
4 | #include <irrlicht.h> | ||
5 | #include "extantz.h" | ||
6 | #include "extantzCamera.h" | ||
7 | #include "CDemo.h" | ||
8 | |||
9 | |||
10 | CDemo::CDemo(GLData *gld, bool a) | ||
11 | : additive(a), | ||
12 | device(gld->device), | ||
13 | currentScene(0), | ||
14 | quakeLevelMesh(0), quakeLevelNode(0), skyboxNode(0), model1(0), model2(0), | ||
15 | campFire(0), metaSelector(0), mapSelector(0), sceneStartTime(0), | ||
16 | timeForThisScene(0) | ||
17 | { | ||
18 | } | ||
19 | |||
20 | |||
21 | CDemo::~CDemo() | ||
22 | { | ||
23 | if (mapSelector) | ||
24 | mapSelector->drop(); | ||
25 | |||
26 | if (metaSelector) | ||
27 | metaSelector->drop(); | ||
28 | } | ||
29 | |||
30 | |||
31 | void CDemo::setup(GLData *gld) | ||
32 | { | ||
33 | device = gld->device; | ||
34 | IrrlichtDevice *device = gld->device; | ||
35 | // IVideoDriver *driver = gld->driver; | ||
36 | // ISceneManager *smgr = gld->smgr; | ||
37 | |||
38 | if (device->getFileSystem()->existFile("irrlicht.dat")) | ||
39 | device->getFileSystem()->addFileArchive("irrlicht.dat"); | ||
40 | else | ||
41 | device->getFileSystem()->addFileArchive("media/Irrlicht/irrlicht.dat"); | ||
42 | if (device->getFileSystem()->existFile("map-20kdm2.pk3")) | ||
43 | device->getFileSystem()->addFileArchive("map-20kdm2.pk3"); | ||
44 | else | ||
45 | device->getFileSystem()->addFileArchive("media/Irrlicht/map-20kdm2.pk3"); | ||
46 | |||
47 | sceneStartTime = device->getTimer()->getTime(); | ||
48 | timeForThisScene = 0; | ||
49 | loadSceneData(); | ||
50 | } | ||
51 | |||
52 | |||
53 | void CDemo::preDraw(GLData *gld, u32 now) | ||
54 | { | ||
55 | if (((now - sceneStartTime) > timeForThisScene) && (timeForThisScene != -1)) | ||
56 | switchToNextScene(gld); | ||
57 | |||
58 | createParticleImpacts(); | ||
59 | } | ||
60 | |||
61 | |||
62 | bool CDemo::OnEvent(const SEvent& event) | ||
63 | { | ||
64 | if (!device) | ||
65 | return false; | ||
66 | |||
67 | if (( ((event.EventType == EET_KEY_INPUT_EVENT) && (event.KeyInput.Key == KEY_SPACE) && (event.KeyInput.PressedDown == false)) || | ||
68 | ((event.EventType == EET_MOUSE_INPUT_EVENT) && (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)) ) && (currentScene == 3)) | ||
69 | { | ||
70 | shoot(); | ||
71 | } | ||
72 | else | ||
73 | if (device->getSceneManager()->getActiveCamera()) | ||
74 | { | ||
75 | device->getSceneManager()->getActiveCamera()->OnEvent(event); | ||
76 | return true; | ||
77 | } | ||
78 | |||
79 | return false; | ||
80 | } | ||
81 | |||
82 | |||
83 | void CDemo::switchToNextScene(GLData *gld) | ||
84 | { | ||
85 | currentScene++; | ||
86 | if (currentScene > 3) | ||
87 | currentScene = 1; | ||
88 | |||
89 | scene::ISceneManager* sm = device->getSceneManager(); | ||
90 | scene::ISceneNodeAnimator* sa = 0; | ||
91 | scene::ICameraSceneNode* camera = 0; | ||
92 | |||
93 | camera = sm->getActiveCamera(); | ||
94 | |||
95 | switch(currentScene) | ||
96 | { | ||
97 | case 1: // panorama camera | ||
98 | { | ||
99 | core::array<core::vector3df> points, points2; | ||
100 | |||
101 | points.push_back(core::vector3df(-931.473755f, 900.0f, 2000.0f)); // -49873 | ||
102 | points.push_back(core::vector3df(-931.473755f, 900.0f, 2000.0f)); // -49873 | ||
103 | points.push_back(core::vector3df(-931.473755f, 700.0f, 1750.0f)); // -49873 | ||
104 | points.push_back(core::vector3df(-931.473755f, 500.0f, 1500.0f)); // -49873 | ||
105 | points.push_back(core::vector3df(-931.473755f, 300.0f, 1250.0f)); // -49873 | ||
106 | points.push_back(core::vector3df(-931.473755f, 200.0f, 1000.0f)); // -49873 | ||
107 | points.push_back(core::vector3df(-931.473755f, 138.300003f, 987.279114f)); // -49873 | ||
108 | points.push_back(core::vector3df(-847.902222f, 136.757553f, 915.792725f)); // -50559 | ||
109 | points.push_back(core::vector3df(-748.680420f, 152.254501f, 826.418945f)); // -51964 | ||
110 | points.push_back(core::vector3df(-708.428406f, 213.569580f, 784.466675f)); // -53251 | ||
111 | points.push_back(core::vector3df(-686.217651f, 288.141174f, 762.965576f)); // -54015 | ||
112 | points.push_back(core::vector3df(-679.685059f, 365.095612f, 756.551453f)); // -54733 | ||
113 | points.push_back(core::vector3df(-671.317871f, 447.360107f, 749.394592f)); // -55588 | ||
114 | points.push_back(core::vector3df(-669.468445f, 583.335632f, 747.711853f)); // -56178 | ||
115 | points.push_back(core::vector3df(-667.611267f, 727.313232f, 746.018250f)); // -56757 | ||
116 | points.push_back(core::vector3df(-665.853210f, 862.791931f, 744.436096f)); // -57859 | ||
117 | points.push_back(core::vector3df(-642.649597f, 1026.047607f, 724.259827f)); // -59705 | ||
118 | points.push_back(core::vector3df(-517.793884f, 838.396790f, 490.326050f)); // -60983 | ||
119 | points.push_back(core::vector3df(-474.387299f, 715.691467f, 344.639984f)); // -61629 | ||
120 | points.push_back(core::vector3df(-444.600250f, 601.155701f, 180.938095f)); // -62319 | ||
121 | points.push_back(core::vector3df(-414.808899f, 479.691406f, 4.866660f)); // -63048 | ||
122 | points.push_back(core::vector3df(-410.418945f, 429.642242f, -134.332687f)); // -63757 | ||
123 | points.push_back(core::vector3df(-399.837585f, 411.498383f, -349.350983f)); // -64418 | ||
124 | points.push_back(core::vector3df(-390.756653f, 403.970093f, -524.454407f)); // -65005 | ||
125 | points.push_back(core::vector3df(-334.864227f, 350.065491f, -732.397400f)); // -65701 | ||
126 | points.push_back(core::vector3df(-195.253387f, 349.577209f, -812.475891f)); // -66335 | ||
127 | points.push_back(core::vector3df(16.255573f, 363.743134f, -833.800415f)); // -67170 | ||
128 | points.push_back(core::vector3df(234.940964f, 352.957825f, -820.150696f)); // -67939 | ||
129 | points.push_back(core::vector3df(436.797668f, 349.236450f, -816.914185f)); // -68596 | ||
130 | points.push_back(core::vector3df(575.236206f, 356.244812f, -719.788513f)); // -69166 | ||
131 | points.push_back(core::vector3df(594.131042f, 387.173828f, -609.675598f)); // -69744 | ||
132 | points.push_back(core::vector3df(617.615234f, 412.002899f, -326.174072f)); // -70640 | ||
133 | points.push_back(core::vector3df(606.456848f, 403.221954f, -104.179291f)); // -71390 | ||
134 | points.push_back(core::vector3df(610.958252f, 407.037750f, 117.209778f)); // -72085 | ||
135 | points.push_back(core::vector3df(597.956909f, 395.167877f, 345.942200f)); // -72817 | ||
136 | points.push_back(core::vector3df(587.383118f, 391.444519f, 566.098633f)); // -73477 | ||
137 | points.push_back(core::vector3df(559.572449f, 371.991333f, 777.689453f)); // -74124 | ||
138 | points.push_back(core::vector3df(423.753204f, 329.990051f, 925.859741f)); // -74941 | ||
139 | points.push_back(core::vector3df(247.520050f, 252.818954f, 935.311829f)); // -75651 | ||
140 | points.push_back(core::vector3df(114.756012f, 199.799759f, 805.014160f)); | ||
141 | points.push_back(core::vector3df(96.783348f, 181.639481f, 648.188110f)); | ||
142 | points.push_back(core::vector3df(97.865623f, 138.905975f, 484.812561f)); | ||
143 | points.push_back(core::vector3df(99.612457f, 102.463669f, 347.603210f)); | ||
144 | points.push_back(core::vector3df(99.0f, 95.0f, 347.0f)); | ||
145 | points.push_back(core::vector3df(99.0f, 90.0f, 347.0f)); | ||
146 | points.push_back(core::vector3df(99.0f, 85.0f, 347.0f)); | ||
147 | points.push_back(core::vector3df(99.0f, 80.0f, 347.0f)); | ||
148 | points.push_back(core::vector3df(99.0f, 75.0f, 347.0f)); | ||
149 | points.push_back(core::vector3df(99.0f, 75.0f, 347.0f)); | ||
150 | points.push_back(core::vector3df(99.0f, 75.0f, 347.0f)); | ||
151 | timeForThisScene = (points.size() - 2) * 1000; | ||
152 | camera = sm->addCameraSceneNode(0, points[0], core::vector3df(0, 400, 0)); | ||
153 | sa = sm->createFollowSplineAnimator(device->getTimer()->getTime(), points, 1.0f, 0.6f, false, false); | ||
154 | camera->addAnimator(sa); | ||
155 | sa->drop(); | ||
156 | } | ||
157 | break; | ||
158 | |||
159 | case 2: // panorama camera | ||
160 | { | ||
161 | core::array<core::vector3df> points; | ||
162 | |||
163 | camera->setTarget(core::vector3df(100, 145, -80)); | ||
164 | |||
165 | points.push_back(core::vector3df(99.0f, 75.0f, 347.0f)); | ||
166 | points.push_back(core::vector3df(100.0f, 75.0f, 347.0f)); | ||
167 | points.push_back(core::vector3df(105.0f, 75.0f, 347.0f)); | ||
168 | points.push_back(core::vector3df(110.0f, 70.0f, 347.0f)); | ||
169 | points.push_back(core::vector3df(115.0f, 70.0f, -160.0f)); | ||
170 | points.push_back(core::vector3df(120.0f, 70.0f, -160.0f)); | ||
171 | points.push_back(core::vector3df(125.0f, 65.0f, -160.0f)); | ||
172 | points.push_back(core::vector3df(130.0f, 65.0f, -160.0f)); | ||
173 | points.push_back(core::vector3df(135.0f, 65.0f, -160.0f)); | ||
174 | points.push_back(core::vector3df(150.0f, 170.0f, -160.0f)); | ||
175 | points.push_back(core::vector3df(150.0f, 170.0f, -160.0f)); | ||
176 | points.push_back(core::vector3df(150.0f, 170.0f, -160.0f)); | ||
177 | timeForThisScene = (points.size() - 2) * 1000; | ||
178 | sa = sm->createFollowSplineAnimator(device->getTimer()->getTime(), points, 1.0f, 0.6f, false, false); | ||
179 | camera->addAnimator(sa); | ||
180 | sa->drop(); | ||
181 | } | ||
182 | break; | ||
183 | |||
184 | case 3: // interactive, go around | ||
185 | { | ||
186 | if (camera) | ||
187 | { | ||
188 | sm->setActiveCamera(0); | ||
189 | camera->remove(); | ||
190 | camera = 0; | ||
191 | } | ||
192 | timeForThisScene = -1; | ||
193 | |||
194 | gld->camera = addExtantzCamera(sm, NULL, -1); | ||
195 | camera = gld->camera; | ||
196 | camera->setPosition(core::vector3df(108, 140, -140)); | ||
197 | camera->setFarValue(5000.0f); | ||
198 | gld->move = getCameraMove(gld->camera); | ||
199 | |||
200 | scene::ISceneNodeAnimatorCollisionResponse* collider = | ||
201 | sm->createCollisionResponseAnimator(metaSelector, camera, core::vector3df(25, 50, 25), core::vector3df(0, quakeLevelMesh ? -10.f : 0.0f, 0), core::vector3df(0, 45, 0), 0.005f); | ||
202 | camera->addAnimator(collider); | ||
203 | collider->drop(); | ||
204 | } | ||
205 | break; | ||
206 | } | ||
207 | |||
208 | sceneStartTime = device->getTimer()->getTime(); | ||
209 | } | ||
210 | |||
211 | |||
212 | void CDemo::loadSceneData() | ||
213 | { | ||
214 | // load quake level | ||
215 | |||
216 | video::IVideoDriver* driver = device->getVideoDriver(); | ||
217 | scene::ISceneManager* sm = device->getSceneManager(); | ||
218 | |||
219 | // Quake3 Shader controls Z-Writing | ||
220 | sm->getParameters()->setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true); | ||
221 | |||
222 | quakeLevelMesh = (scene::IQ3LevelMesh*) sm->getMesh("maps/20kdm2.bsp"); | ||
223 | |||
224 | if (quakeLevelMesh) | ||
225 | { | ||
226 | u32 i; | ||
227 | |||
228 | //move all quake level meshes (non-realtime) | ||
229 | core::matrix4 m; | ||
230 | m.setTranslation(core::vector3df(-1300,-70,-1249)); | ||
231 | |||
232 | for (i = 0; i != scene::quake3::E_Q3_MESH_SIZE; ++i) | ||
233 | sm->getMeshManipulator()->transform(quakeLevelMesh->getMesh(i), m); | ||
234 | |||
235 | quakeLevelNode = sm->addOctreeSceneNode(quakeLevelMesh->getMesh( scene::quake3::E_Q3_MESH_GEOMETRY)); | ||
236 | if (quakeLevelNode) | ||
237 | { | ||
238 | //quakeLevelNode->setPosition(core::vector3df(-1300, -70, -1249)); | ||
239 | quakeLevelNode->setVisible(true); | ||
240 | |||
241 | // create map triangle selector | ||
242 | mapSelector = sm->createOctreeTriangleSelector(quakeLevelMesh->getMesh(0), quakeLevelNode, 128); | ||
243 | |||
244 | // if not using shader and no gamma it's better to use more lighting, because | ||
245 | // quake3 level are usually dark | ||
246 | quakeLevelNode->setMaterialType(video::EMT_LIGHTMAP_M4); | ||
247 | |||
248 | // set additive blending if wanted | ||
249 | if (additive) | ||
250 | quakeLevelNode->setMaterialType(video::EMT_LIGHTMAP_ADD); | ||
251 | } | ||
252 | |||
253 | // the additional mesh can be quite huge and is unoptimized | ||
254 | scene::IMesh *additional_mesh = quakeLevelMesh->getMesh(scene::quake3::E_Q3_MESH_ITEMS); | ||
255 | |||
256 | for (i = 0; i != additional_mesh->getMeshBufferCount(); ++i) | ||
257 | { | ||
258 | scene::IMeshBuffer *meshBuffer = additional_mesh->getMeshBuffer(i); | ||
259 | const video::SMaterial &material = meshBuffer->getMaterial(); | ||
260 | |||
261 | //! The ShaderIndex is stored in the material parameter | ||
262 | s32 shaderIndex = (s32) material.MaterialTypeParam2; | ||
263 | |||
264 | // the meshbuffer can be rendered without additional support, or it has no shader | ||
265 | const scene::quake3::IShader *shader = quakeLevelMesh->getShader(shaderIndex); | ||
266 | if (0 == shader) | ||
267 | { | ||
268 | continue; | ||
269 | } | ||
270 | // Now add the MeshBuffer(s) with the current Shader to the Manager | ||
271 | sm->addQuake3SceneNode(meshBuffer, shader); | ||
272 | } | ||
273 | } | ||
274 | |||
275 | // load sydney model and create 2 instances | ||
276 | |||
277 | scene::IAnimatedMesh *mesh = 0; | ||
278 | mesh = sm->getMesh("media/Irrlicht/sydney.md2"); | ||
279 | if (mesh) | ||
280 | { | ||
281 | model1 = sm->addAnimatedMeshSceneNode(mesh); | ||
282 | if (model1) | ||
283 | { | ||
284 | model1->setMaterialTexture(0, driver->getTexture("media/Irrlicht/sydney.bmp")); | ||
285 | model1->setPosition(core::vector3df(100, 40, -80)); | ||
286 | model1->setScale(core::vector3df(2, 2, 2)); | ||
287 | model1->setMD2Animation(scene::EMAT_STAND); | ||
288 | model1->setMaterialFlag(video::EMF_LIGHTING, true); | ||
289 | model1->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true); | ||
290 | model1->addShadowVolumeSceneNode(); | ||
291 | } | ||
292 | |||
293 | model2 = sm->addAnimatedMeshSceneNode(mesh); | ||
294 | if (model2) | ||
295 | { | ||
296 | model2->setMaterialTexture(0, driver->getTexture("media/Irrlicht/spheremap.jpg")); | ||
297 | model2->setPosition(core::vector3df(180, 15, -60)); | ||
298 | model2->setScale(core::vector3df(2, 2, 2)); | ||
299 | model2->setMD2Animation(scene::EMAT_RUN); | ||
300 | model2->setMaterialFlag(video::EMF_LIGHTING, false); | ||
301 | model2->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true); | ||
302 | model2->setMaterialType(video::EMT_SPHERE_MAP); | ||
303 | model2->addShadowVolumeSceneNode(); | ||
304 | } | ||
305 | } | ||
306 | |||
307 | scene::ISceneNodeAnimator *anim = 0; | ||
308 | |||
309 | // create sky box | ||
310 | driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false); | ||
311 | skyboxNode = sm->addSkyBoxSceneNode( | ||
312 | driver->getTexture("media/Irrlicht/irrlicht2_up.jpg"), | ||
313 | driver->getTexture("media/Irrlicht/irrlicht2_dn.jpg"), | ||
314 | driver->getTexture("media/Irrlicht/irrlicht2_lf.jpg"), | ||
315 | driver->getTexture("media/Irrlicht/irrlicht2_rt.jpg"), | ||
316 | driver->getTexture("media/Irrlicht/irrlicht2_ft.jpg"), | ||
317 | driver->getTexture("media/Irrlicht/irrlicht2_bk.jpg")); | ||
318 | driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true); | ||
319 | |||
320 | // create walk-between-portals animation | ||
321 | core::vector3df waypoint[2]; | ||
322 | waypoint[0].set(-150, 40, 100); | ||
323 | waypoint[1].set(350, 40, 100); | ||
324 | |||
325 | if (model2) | ||
326 | { | ||
327 | anim = device->getSceneManager()->createFlyStraightAnimator(waypoint[0], waypoint[1], 2000, true); | ||
328 | model2->addAnimator(anim); | ||
329 | anim->drop(); | ||
330 | } | ||
331 | |||
332 | // create animation for portals; | ||
333 | core::array<video::ITexture*> textures; | ||
334 | for (s32 g=1; g<8; ++g) | ||
335 | { | ||
336 | core::stringc tmp("media/Irrlicht/portal"); | ||
337 | tmp += g; | ||
338 | tmp += ".bmp"; | ||
339 | video::ITexture* t = driver->getTexture(tmp); | ||
340 | textures.push_back(t); | ||
341 | } | ||
342 | |||
343 | anim = sm->createTextureAnimator(textures, 100); | ||
344 | |||
345 | // create portals | ||
346 | scene::IBillboardSceneNode* bill = 0; | ||
347 | for (int r = 0; r < 2; ++r) | ||
348 | { | ||
349 | bill = sm->addBillboardSceneNode(0, core::dimension2d<f32>(100, 100), waypoint[r]+ core::vector3df(0, 20, 0)); | ||
350 | bill->setMaterialFlag(video::EMF_LIGHTING, false); | ||
351 | bill->setMaterialTexture(0, driver->getTexture("media/Irrlicht/portal1.bmp")); | ||
352 | bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR); | ||
353 | bill->addAnimator(anim); | ||
354 | } | ||
355 | |||
356 | anim->drop(); | ||
357 | |||
358 | // create circle flying dynamic light with transparent billboard attached | ||
359 | scene::ILightSceneNode *light = 0; | ||
360 | |||
361 | light = sm->addLightSceneNode(0, core::vector3df(0, 0, 0), video::SColorf(1.0f, 1.0f, 1.f, 1.0f), 500.f); | ||
362 | anim = sm->createFlyCircleAnimator(core::vector3df(100, 150, 80), 80.0f, 0.0005f); | ||
363 | |||
364 | light->addAnimator(anim); | ||
365 | anim->drop(); | ||
366 | |||
367 | bill = device->getSceneManager()->addBillboardSceneNode(light, core::dimension2d<f32>(40, 40)); | ||
368 | bill->setMaterialFlag(video::EMF_LIGHTING, false); | ||
369 | bill->setMaterialTexture(0, driver->getTexture("media/Irrlicht/particlewhite.bmp")); | ||
370 | bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR); | ||
371 | |||
372 | // create meta triangle selector with all triangles selectors in it. | ||
373 | metaSelector = sm->createMetaTriangleSelector(); | ||
374 | metaSelector->addTriangleSelector(mapSelector); | ||
375 | |||
376 | // create camp fire | ||
377 | campFire = sm->addParticleSystemSceneNode(false); | ||
378 | campFire->setPosition(core::vector3df(100, 120, 600)); | ||
379 | campFire->setScale(core::vector3df(2, 2, 2)); | ||
380 | |||
381 | scene::IParticleEmitter *em = campFire->createBoxEmitter(core::aabbox3d<f32>(-7, 0, -7, 7, 1, 7), core::vector3df(0.0f, 0.06f, 0.0f), 80, 100, video::SColor(1, 255, 255, 255), video::SColor(1, 255, 255, 255), 800, 2000); | ||
382 | em->setMinStartSize(core::dimension2d<f32>(20.0f, 10.0f)); | ||
383 | em->setMaxStartSize(core::dimension2d<f32>(20.0f, 10.0f)); | ||
384 | campFire->setEmitter(em); | ||
385 | em->drop(); | ||
386 | |||
387 | scene::IParticleAffector *paf = campFire->createFadeOutParticleAffector(); | ||
388 | campFire->addAffector(paf); | ||
389 | paf->drop(); | ||
390 | |||
391 | campFire->setMaterialFlag(video::EMF_LIGHTING, false); | ||
392 | campFire->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false); | ||
393 | campFire->setMaterialTexture(0, driver->getTexture("media/Irrlicht/fireball.bmp")); | ||
394 | campFire->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR); | ||
395 | } | ||
396 | |||
397 | |||
398 | void CDemo::shoot() | ||
399 | { | ||
400 | scene::ISceneManager *sm = device->getSceneManager(); | ||
401 | scene::ICameraSceneNode *camera = sm->getActiveCamera(); | ||
402 | |||
403 | if ((!camera) || (!mapSelector)) | ||
404 | return; | ||
405 | |||
406 | SParticleImpact imp; | ||
407 | imp.when = 0; | ||
408 | |||
409 | // get line of camera | ||
410 | core::vector3df start = camera->getPosition(); | ||
411 | core::vector3df end = (camera->getTarget() - start); | ||
412 | end.normalize(); | ||
413 | start += end * 8.0f; | ||
414 | end = start + (end * camera->getFarValue()); | ||
415 | |||
416 | core::triangle3df triangle; | ||
417 | |||
418 | core::line3d<f32> line(start, end); | ||
419 | |||
420 | // get intersection point with map | ||
421 | scene::ISceneNode* hitNode; | ||
422 | if (sm->getSceneCollisionManager()->getCollisionPoint(line, mapSelector, end, triangle, hitNode)) | ||
423 | { | ||
424 | // collides with wall | ||
425 | core::vector3df out = triangle.getNormal(); | ||
426 | out.setLength(0.03f); | ||
427 | |||
428 | imp.when = 1; | ||
429 | imp.outVector = out; | ||
430 | imp.pos = end; | ||
431 | } | ||
432 | else | ||
433 | { | ||
434 | // doesnt collide with wall | ||
435 | core::vector3df start = camera->getPosition(); | ||
436 | core::vector3df end = (camera->getTarget() - start); | ||
437 | end.normalize(); | ||
438 | start += end * 8.0f; | ||
439 | end = start + (end * camera->getFarValue()); | ||
440 | } | ||
441 | |||
442 | // create fire ball | ||
443 | scene::ISceneNode *node = 0; | ||
444 | node = sm->addBillboardSceneNode(0, core::dimension2d<f32>(25, 25), start); | ||
445 | |||
446 | node->setMaterialFlag(video::EMF_LIGHTING, false); | ||
447 | node->setMaterialTexture(0, device->getVideoDriver()->getTexture("media/Irrlicht/fireball.bmp")); | ||
448 | node->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR); | ||
449 | |||
450 | f32 length = (f32)(end - start).getLength(); | ||
451 | const f32 speed = 0.6f; | ||
452 | u32 time = (u32) (length / speed); | ||
453 | |||
454 | scene::ISceneNodeAnimator *anim = 0; | ||
455 | |||
456 | // set flight line | ||
457 | anim = sm->createFlyStraightAnimator(start, end, time); | ||
458 | node->addAnimator(anim); | ||
459 | anim->drop(); | ||
460 | |||
461 | anim = sm->createDeleteAnimator(time); | ||
462 | node->addAnimator(anim); | ||
463 | anim->drop(); | ||
464 | |||
465 | if (imp.when) | ||
466 | { | ||
467 | // create impact note | ||
468 | imp.when = device->getTimer()->getTime() + (time - 100); | ||
469 | Impacts.push_back(imp); | ||
470 | } | ||
471 | } | ||
472 | |||
473 | |||
474 | void CDemo::createParticleImpacts() | ||
475 | { | ||
476 | u32 now = device->getTimer()->getTime(); | ||
477 | scene::ISceneManager *sm = device->getSceneManager(); | ||
478 | |||
479 | for (s32 i = 0; i < (s32) Impacts.size(); ++i) | ||
480 | if (now > Impacts[i].when) | ||
481 | { | ||
482 | // create smoke particle system | ||
483 | scene::IParticleSystemSceneNode *pas = 0; | ||
484 | |||
485 | pas = sm->addParticleSystemSceneNode(false, 0, -1, Impacts[i].pos); | ||
486 | |||
487 | pas->setParticleSize(core::dimension2d<f32>(10.0f, 10.0f)); | ||
488 | |||
489 | scene::IParticleEmitter* em = pas->createBoxEmitter(core::aabbox3d<f32>(-5, -5, -5, 5, 5, 5), Impacts[i].outVector, 20, 40, video::SColor(50, 255, 255, 255), video::SColor(50, 255, 255, 255), 1200, 1600, 20); | ||
490 | pas->setEmitter(em); | ||
491 | em->drop(); | ||
492 | |||
493 | scene::IParticleAffector *paf = campFire->createFadeOutParticleAffector(); | ||
494 | pas->addAffector(paf); | ||
495 | paf->drop(); | ||
496 | |||
497 | pas->setMaterialFlag(video::EMF_LIGHTING, false); | ||
498 | pas->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false); | ||
499 | pas->setMaterialTexture(0, device->getVideoDriver()->getTexture("media/Irrlicht/smoke.bmp")); | ||
500 | pas->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR); | ||
501 | |||
502 | scene::ISceneNodeAnimator *anim = sm->createDeleteAnimator(2000); | ||
503 | pas->addAnimator(anim); | ||
504 | anim->drop(); | ||
505 | |||
506 | // delete entry | ||
507 | Impacts.erase(i); | ||
508 | i--; | ||
509 | } | ||
510 | } | ||
diff --git a/src/extantz/CDemo.h b/src/extantz/CDemo.h new file mode 100644 index 0000000..035c01f --- /dev/null +++ b/src/extantz/CDemo.h | |||
@@ -0,0 +1,63 @@ | |||
1 | // This is a Demo of the Irrlicht Engine (c) 2006 by N.Gebhardt. | ||
2 | // This file is not documented. | ||
3 | |||
4 | #ifndef __C_DEMO_H_INCLUDED__ | ||
5 | #define __C_DEMO_H_INCLUDED__ | ||
6 | |||
7 | #ifdef _IRR_WINDOWS_ | ||
8 | #include <windows.h> | ||
9 | #endif | ||
10 | |||
11 | const int CAMERA_COUNT = 7; | ||
12 | |||
13 | class CDemo : public IEventReceiver | ||
14 | { | ||
15 | public: | ||
16 | |||
17 | CDemo(GLData *gld, bool additive); | ||
18 | |||
19 | ~CDemo(); | ||
20 | |||
21 | void setup(GLData *gld); | ||
22 | void preDraw(GLData *gld, u32 now); | ||
23 | |||
24 | virtual bool OnEvent(const SEvent& event); | ||
25 | |||
26 | private: | ||
27 | |||
28 | void createLoadingScreen(); | ||
29 | void loadSceneData(); | ||
30 | void switchToNextScene(GLData *gld); | ||
31 | void shoot(); | ||
32 | void createParticleImpacts(); | ||
33 | |||
34 | bool additive; | ||
35 | IrrlichtDevice *device; | ||
36 | |||
37 | struct SParticleImpact | ||
38 | { | ||
39 | u32 when; | ||
40 | core::vector3df pos; | ||
41 | core::vector3df outVector; | ||
42 | }; | ||
43 | |||
44 | int currentScene; | ||
45 | |||
46 | scene::IQ3LevelMesh* quakeLevelMesh; | ||
47 | scene::ISceneNode* quakeLevelNode; | ||
48 | scene::ISceneNode* skyboxNode; | ||
49 | scene::IAnimatedMeshSceneNode* model1; | ||
50 | scene::IAnimatedMeshSceneNode* model2; | ||
51 | scene::IParticleSystemSceneNode* campFire; | ||
52 | |||
53 | scene::IMetaTriangleSelector* metaSelector; | ||
54 | scene::ITriangleSelector* mapSelector; | ||
55 | |||
56 | s32 sceneStartTime; | ||
57 | s32 timeForThisScene; | ||
58 | |||
59 | core::array<SParticleImpact> Impacts; | ||
60 | }; | ||
61 | |||
62 | #endif | ||
63 | |||
diff --git a/src/extantz/build.lua b/src/extantz/build.lua new file mode 100755 index 0000000..0bb2a0b --- /dev/null +++ b/src/extantz/build.lua | |||
@@ -0,0 +1,26 @@ | |||
1 | #!/usr/bin/env lua | ||
2 | |||
3 | local dir = ... | ||
4 | |||
5 | if 'nil' == type(dir) then | ||
6 | local build, err = loadfile('../../build.lua') | ||
7 | if build then | ||
8 | setfenv(build, getfenv(2)) | ||
9 | build(2) | ||
10 | else | ||
11 | print("ERROR - " .. err) | ||
12 | end | ||
13 | dir = workingDir | ||
14 | end | ||
15 | |||
16 | CFLAGS = CFLAGS .. ' -I../../libraries/irrlicht-1.8.1/include -I/usr/X11R6/include' | ||
17 | LDFLAGS = LDFLAGS .. ' -L../../libraries/irrlicht-1.8.1/lib/Linux' | ||
18 | libs = libs .. ' -lIrrlicht -lGL -lbz2' | ||
19 | |||
20 | removeFiles(dir, {'../../extantz', 'crappisspuke.o', 'CDemo.o', 'extantzCamera.o', '../../media/extantz.edj'}) | ||
21 | |||
22 | runCommand('edje_cc', dir, 'edje_cc ' .. EDJE_FLAGS .. ' extantz.edc ../../media/extantz.edj') | ||
23 | runCommand('Irrlicht files', dir, 'g++ ' .. CFLAGS .. ' -O3 -ffast-math -c crappisspuke.cpp -o crappisspuke.o ' .. LDFLAGS) | ||
24 | runCommand(nil, dir, 'g++ ' .. CFLAGS .. ' -O3 -ffast-math -c CDemo.cpp -o CDemo.o ' .. LDFLAGS) | ||
25 | runCommand('extantz', dir, 'g++ ' .. CFLAGS .. ' -O3 -ffast-math -c extantzCamera.cpp -o extantzCamera.o ' .. LDFLAGS) | ||
26 | runCommand(nil, dir, 'gcc ' .. CFLAGS .. ' extantz.c crappisspuke.o CDemo.o extantzCamera.o -o ../../extantz ' .. LDFLAGS .. ' ' .. libs) | ||
diff --git a/src/extantz/crappisspuke.cpp b/src/extantz/crappisspuke.cpp new file mode 100644 index 0000000..2af9dde --- /dev/null +++ b/src/extantz/crappisspuke.cpp | |||
@@ -0,0 +1,294 @@ | |||
1 | |||
2 | #include <irrlicht.h> | ||
3 | #include "extantz.h" | ||
4 | #include "CDemo.h" | ||
5 | |||
6 | |||
7 | SExposedVideoData videoData; | ||
8 | |||
9 | IAnimatedMeshSceneNode *node; | ||
10 | CDemo *myDemo; | ||
11 | // This is the movement speed in units per second. | ||
12 | const f32 MOVEMENT_SPEED = 5.f; | ||
13 | // In order to do framerate independent movement, we have to know | ||
14 | // how long it was since the last frame | ||
15 | u32 then; | ||
16 | |||
17 | #ifdef __cplusplus | ||
18 | extern "C" { | ||
19 | #endif | ||
20 | |||
21 | EAPI int startIrr(GLData *gld) | ||
22 | { | ||
23 | SIrrlichtCreationParameters params; | ||
24 | IrrlichtDevice *device; | ||
25 | IVideoDriver *driver; | ||
26 | ISceneManager *smgr; | ||
27 | bool additive = true; | ||
28 | |||
29 | if (!gld->useIrr) | ||
30 | return 1; // Return 1 so that the caller stops asking on each frame. | ||
31 | |||
32 | #if USE_IRR | ||
33 | void *display = NULL; | ||
34 | unsigned long sfc = 0; | ||
35 | void *ctx = NULL; | ||
36 | // Evas_GL_API *gl = gld->glApi; | ||
37 | |||
38 | #if USE_DEMO | ||
39 | myDemo = new CDemo(gld, additive); | ||
40 | #endif | ||
41 | |||
42 | /* Raster says - | ||
43 | 4. evas exposes an opengl-es2 api. any existing engine needs to be adapted to | ||
44 | use this. that's pretty much the end of that. if the engine doesn't have a | ||
45 | gles2 port.. it will need one. once it has one, then it is a simple matter of | ||
46 | replacing all the gl calls as follows: | ||
47 | |||
48 | glDrawArrays() -> api->glDrawArrays() | ||
49 | glBindBuffer() -> api->glBindBuffer() | ||
50 | |||
51 | you could make the port switchable with a macro: | ||
52 | |||
53 | #ifdef EVASGL | ||
54 | #define EG() my_evas_gl_api-> | ||
55 | #else | ||
56 | #define EG() | ||
57 | #endif | ||
58 | |||
59 | then fix up all the gl calls to be | ||
60 | |||
61 | EG()glDrawArrays() | ||
62 | EG()glBindBuffer() | ||
63 | |||
64 | etc. | ||
65 | |||
66 | doing the above allows evas to decide how to share context. it may allocate a | ||
67 | separate context or share its own. either way as far as the evasgl api user is | ||
68 | concerned.. they get their own private context to play with. if it does NOT do | ||
69 | the above (use the api exposed by evas gl) then wrapping can't context switches | ||
70 | can't work. all gl calls HAVE to go through the wrapped api to work right. this | ||
71 | is because we can't REPLACE the internals of the gl driver which otherwise | ||
72 | would be managing context and state all internally and we have zero access to | ||
73 | that - especially with closed drivers. we'd end up writing a proxy gl library | ||
74 | which conflicts with real gl symbol-wise (thus taking over and replacing | ||
75 | normal gl calls) and i know i have no interest in maintaining a separate | ||
76 | libGLwhatever.so that is an exact copy of gl and it's api's just to wrap it | ||
77 | when we expose that wrapper without symbol complications via evas gl. | ||
78 | |||
79 | 5. the engine will need to be adapted so the draw function is callable - eg by | ||
80 | elm_glview. then it's easy to switch where rendering happens. evas offers a fast | ||
81 | path to avoid buffer copies and make the gl view draw part of the evas | ||
82 | rendering path directly. this would offer almost zero overhead vs doing it | ||
83 | directly with egl/gles etc. to your backbuffer yourself, BUT gets you the bonus | ||
84 | of having your 3d view as part of a larger scenegraph. combine 2 or 3 of them | ||
85 | in a single window. overlay with evas objects or elm widgets for hud etc. all | ||
86 | for free. this also implies the engine has to integrate to the efl mainloop | ||
87 | etc. of course. | ||
88 | */ | ||
89 | |||
90 | |||
91 | sfc = ecore_evas_window_get(gld->ee); | ||
92 | // This is the way Raster wants me to do things, but these functions are not actually available. Pffft | ||
93 | // ctx = gl->glGetCurrentContext(); | ||
94 | // display = gl->glGetCurrentDisplay(); | ||
95 | ctx = glXGetCurrentContext(); | ||
96 | display = glXGetCurrentDisplay(); | ||
97 | /* For using a pre existing X11 window (with optional OpenGL). */ | ||
98 | videoData = SExposedVideoData(); | ||
99 | videoData.OpenGLLinux.X11Display = display; // void * - Connection to the X server. | ||
100 | videoData.OpenGLLinux.X11Window = sfc; // unsigned long - Specifies a GLX drawable. Must be either an X window ID or a GLX pixmap ID. | ||
101 | videoData.OpenGLLinux.X11Context = ctx; // void * - Specifies a GLX rendering context that is to be attached to drawable. | ||
102 | |||
103 | /* | ||
104 | The most important function of the engine is the createDevice() | ||
105 | function. The IrrlichtDevice is created by it, which is the root | ||
106 | object for doing anything with the engine. createDevice() has 7 | ||
107 | parameters: | ||
108 | |||
109 | - deviceType: Type of the device. This can currently be the Null-device, | ||
110 | one of the two software renderers, D3D8, D3D9, or OpenGL. In this | ||
111 | example we use EDT_SOFTWARE, but to try out, you might want to | ||
112 | change it to EDT_BURNINGSVIDEO, EDT_NULL, EDT_DIRECT3D8, | ||
113 | EDT_DIRECT3D9, or EDT_OPENGL. | ||
114 | |||
115 | - windowSize: Size of the Window or screen in FullScreenMode to be | ||
116 | created. In this example we use 640x480. | ||
117 | |||
118 | - bits: Amount of color bits per pixel. This should be 16 or 32. The | ||
119 | parameter is often ignored when running in windowed mode. | ||
120 | |||
121 | - fullscreen: Specifies if we want the device to run in fullscreen mode | ||
122 | or not. | ||
123 | |||
124 | - stencilbuffer: Specifies if we want to use the stencil buffer (for | ||
125 | drawing shadows). | ||
126 | |||
127 | - vsync: Specifies if we want to have vsync enabled, this is only useful | ||
128 | in fullscreen mode. | ||
129 | |||
130 | - eventReceiver: An object to receive events. We do not want to use this | ||
131 | parameter here, and set it to 0. | ||
132 | |||
133 | Always check the return value to cope with unsupported drivers, | ||
134 | dimensions, etc. | ||
135 | */ | ||
136 | |||
137 | params.DeviceType = EIDT_X11; // EIDT_BEST might be preferable. | ||
138 | if (ctx) | ||
139 | params.DriverType = video::EDT_OPENGL; | ||
140 | else | ||
141 | params.DriverType = video::EDT_BURNINGSVIDEO; | ||
142 | params.WindowSize = dimension2d<u32>(gld->sfc_w, gld->sfc_h); | ||
143 | params.Bits = 32; // Ignored in windowed mode? | ||
144 | params.ZBufferBits = 16; // Default 16. | ||
145 | params.Fullscreen = false; // The default anyway. | ||
146 | params.Stencilbuffer = false; // For shadows. | ||
147 | params.Vsync = false; | ||
148 | params.AntiAlias=true; | ||
149 | params.WithAlphaChannel = true; | ||
150 | params.IgnoreInput = true; | ||
151 | params.EventReceiver = myDemo; // Probably useless, EFL might not let Irrlicht grab the input. | ||
152 | params.WindowId = (void *) videoData.OpenGLLinux.X11Window; | ||
153 | params.VideoData = &videoData; | ||
154 | |||
155 | device = createDeviceEx(params); | ||
156 | |||
157 | if (!device) | ||
158 | return 0; | ||
159 | gld->device = device; | ||
160 | |||
161 | /* | ||
162 | Get a pointer to the VideoDriver and the SceneManager so that we do not always have to write | ||
163 | device->getVideoDriver() or device->getSceneManager(). | ||
164 | */ | ||
165 | driver = device->getVideoDriver(); gld->driver = driver; | ||
166 | smgr = device->getSceneManager(); gld->smgr = smgr; | ||
167 | |||
168 | // FIXME - this is what makes the window vanish in EFL 1.8, but worked fine in 1.7 I think. | ||
169 | // device->setResizable(true); | ||
170 | driver->OnResize(dimension2d<u32>(gld->img_w, gld->img_h)); | ||
171 | // Just gives me a blank screen. grrrr | ||
172 | // driver->setViewPort(rect<s32>(0, 0, gld->img_w, gld->img_h)); | ||
173 | |||
174 | // set ambient light | ||
175 | smgr->setAmbientLight (video::SColorf(0x00c0c0c0)); | ||
176 | |||
177 | #if USE_DEMO | ||
178 | myDemo->setup(gld); | ||
179 | #else | ||
180 | /* | ||
181 | To show something interesting, we load a Quake 2 model and display it. | ||
182 | We only have to get the Mesh from the Scene Manager with getMesh() and add | ||
183 | a SceneNode to display the mesh with addAnimatedMeshSceneNode(). We | ||
184 | check the return value of getMesh() to become aware of loading problems | ||
185 | and other errors. | ||
186 | |||
187 | Instead of writing the filename sydney.md2, it would also be possible | ||
188 | to load a Maya object file (.obj), a complete Quake3 map (.bsp) or any | ||
189 | other supported file format. By the way, that cool Quake 2 model | ||
190 | called sydney was modelled by Brian Collins. | ||
191 | */ | ||
192 | IAnimatedMesh* mesh = smgr->getMesh("media/Irrlicht/sydney.md2"); | ||
193 | if (!mesh) | ||
194 | { | ||
195 | device->drop(); | ||
196 | return 0; | ||
197 | } | ||
198 | node = smgr->addAnimatedMeshSceneNode(mesh); | ||
199 | |||
200 | /* | ||
201 | To let the mesh look a little bit nicer, we change its material. We | ||
202 | disable lighting because we do not have a dynamic light in here, and | ||
203 | the mesh would be totally black otherwise. Then we set the frame loop, | ||
204 | such that the predefined STAND animation is used. And last, we apply a | ||
205 | texture to the mesh. Without it the mesh would be drawn using only a | ||
206 | color. | ||
207 | */ | ||
208 | if (node) | ||
209 | { | ||
210 | // node->setMaterialFlag(EMF_LIGHTING, false); | ||
211 | node->setMD2Animation(scene::EMAT_STAND); | ||
212 | node->setMaterialTexture(0, driver->getTexture("media/Irrlicht/sydney.bmp")); | ||
213 | } | ||
214 | |||
215 | /* | ||
216 | To look at the mesh, we place a camera into 3d space at the position | ||
217 | (0, 30, -40). The camera looks from there to (0,5,0), which is | ||
218 | approximately the place where our md2 model is. | ||
219 | */ | ||
220 | smgr->addCameraSceneNode(0, vector3df(50, 70, -65), vector3df(0, 50, 0)); | ||
221 | #endif | ||
222 | |||
223 | then = device->getTimer()->getTime(); | ||
224 | #endif | ||
225 | return 1; | ||
226 | } | ||
227 | |||
228 | EAPI void drawIrr_start(GLData *gld) | ||
229 | { | ||
230 | if (gld->useIrr) | ||
231 | { | ||
232 | IrrlichtDevice *device = gld->device; | ||
233 | IVideoDriver *driver = gld->driver; | ||
234 | ISceneManager *smgr = gld->smgr; | ||
235 | |||
236 | // Increase virtual timer time, instead of device->run() if doing our own input processing. | ||
237 | device->getTimer()->tick(); | ||
238 | |||
239 | // Work out a frame delta time. | ||
240 | const u32 now = device->getTimer()->getTime(); | ||
241 | // const f32 frameDeltaTime = (f32)(now - then) / 1000.f; // Time in seconds | ||
242 | then = now; | ||
243 | |||
244 | |||
245 | #if USE_DEMO | ||
246 | myDemo->preDraw(gld, now); | ||
247 | #else | ||
248 | core::vector3df nodePosition = node->getPosition(); | ||
249 | // nodePosition.Y -= MOVEMENT_SPEED * frameDeltaTime; | ||
250 | node->setPosition(nodePosition); | ||
251 | #endif | ||
252 | |||
253 | /* | ||
254 | Anything can be drawn between a beginScene() and an endScene() | ||
255 | call. The beginScene() call clears the screen with a color and | ||
256 | the depth buffer, if desired. Then we let the Scene Manager and | ||
257 | the GUI Environment draw their content. With the endScene() | ||
258 | call everything is presented on the screen. | ||
259 | */ | ||
260 | driver->beginScene(true, true, SColor(255, 255, 255, 255), videoData, NULL); // This does the context change, then clearBuffers() | ||
261 | |||
262 | smgr->drawAll(); | ||
263 | } | ||
264 | } | ||
265 | |||
266 | EAPI void drawIrr_end(GLData *gld) | ||
267 | { | ||
268 | IVideoDriver *driver = gld->driver; | ||
269 | |||
270 | if (gld->useIrr) | ||
271 | driver->endScene(); | ||
272 | } | ||
273 | |||
274 | EAPI void finishIrr(GLData *gld) | ||
275 | { | ||
276 | IrrlichtDevice *device = gld->device; | ||
277 | |||
278 | /* | ||
279 | After we are done with the render loop, we have to delete the Irrlicht | ||
280 | Device created before with createDevice(). In the Irrlicht Engine, you | ||
281 | have to delete all objects you created with a method or function which | ||
282 | starts with 'create'. The object is simply deleted by calling ->drop(). | ||
283 | See the documentation at irr::IReferenceCounted::drop() for more | ||
284 | information. | ||
285 | */ | ||
286 | if (gld->useIrr) | ||
287 | device->drop(); | ||
288 | } | ||
289 | |||
290 | |||
291 | #ifdef __cplusplus | ||
292 | } | ||
293 | #endif | ||
294 | |||
diff --git a/src/extantz/extantz.c b/src/extantz/extantz.c new file mode 100644 index 0000000..09b5196 --- /dev/null +++ b/src/extantz/extantz.c | |||
@@ -0,0 +1,1588 @@ | |||
1 | #include "extantz.h" | ||
2 | |||
3 | |||
4 | int _log_domain = -1; | ||
5 | |||
6 | Eina_Hash *grids; | ||
7 | Eina_Hash *viewers; | ||
8 | |||
9 | static char *gridTest[][3] = | ||
10 | { | ||
11 | {"3rd Rock Grid", "http://grid.3rdrockgrid.com:8002/", "http://grid.3rdrockgrid.com/3rg_login"}, | ||
12 | {"Infinite Grid", "http://grid.infinitegrid.org:8002/", "http://www.infinitegrid.org/loginscreen.php"}, | ||
13 | {"Second Life Grid", "https://login.agni.lindenlab.com/cgi-bin/login.cgi", "http://secondlife.com/"}, | ||
14 | {NULL, NULL, NULL} | ||
15 | }; | ||
16 | |||
17 | static char *accountTest[][3] = | ||
18 | { | ||
19 | {"3rd Rock Grid", "onefang rejected", "password"}, | ||
20 | {"Infinite Grid", "infinite onefang", "MyB1GSecrit"}, | ||
21 | {"Infinite Grid", "onefang rejected", "MySecrit"}, | ||
22 | {NULL, NULL, NULL} | ||
23 | }; | ||
24 | |||
25 | |||
26 | static char *viewerTest[][3] = | ||
27 | { | ||
28 | {"Imprudence", "1.4.0 beta 3", ""}, | ||
29 | {"Kokua", "3.4.4.25633", ""}, | ||
30 | {"meta-impy", "1.4.0 beta 1.5", ""}, | ||
31 | {"SL", "v3", ""}, | ||
32 | {NULL, NULL, NULL} | ||
33 | }; | ||
34 | |||
35 | |||
36 | static Elm_Genlist_Item_Class *grid_gic = NULL; | ||
37 | static Elm_Genlist_Item_Class *account_gic = NULL; | ||
38 | static Elm_Genlist_Item_Class *viewer_gic = NULL; | ||
39 | |||
40 | //static const char *img1 = PACKAGE_DATA_DIR "/media/plant_01.jpg"; | ||
41 | //static const char *img2 = PACKAGE_DATA_DIR "/media/sky_01.jpg"; | ||
42 | static const char *img3 = PACKAGE_DATA_DIR "/media/rock_01.jpg"; | ||
43 | |||
44 | |||
45 | #define EPHYSICS_TEST_THEME "extantz" | ||
46 | |||
47 | |||
48 | #if DO_GEARS | ||
49 | //--------------------------------// | ||
50 | // Gear Stuff. | ||
51 | |||
52 | static GLfloat *vert(GLfloat *p, GLfloat x, GLfloat y, GLfloat z, GLfloat *n) | ||
53 | { | ||
54 | p[0] = x; | ||
55 | p[1] = y; | ||
56 | p[2] = z; | ||
57 | p[3] = n[0]; | ||
58 | p[4] = n[1]; | ||
59 | p[5] = n[2]; | ||
60 | |||
61 | return p + 6; | ||
62 | } | ||
63 | |||
64 | /* Draw a gear wheel. You'll probably want to call this function when | ||
65 | * building a display list since we do a lot of trig here. | ||
66 | * | ||
67 | * Input: inner_radius - radius of hole at center | ||
68 | * outer_radius - radius at center of teeth | ||
69 | * width - width of gear | ||
70 | * teeth - number of teeth | ||
71 | * tooth_depth - depth of tooth | ||
72 | */ | ||
73 | static Gear *make_gear(GLData *gld, GLfloat inner_radius, GLfloat outer_radius, GLfloat width, GLint teeth, GLfloat tooth_depth) | ||
74 | { | ||
75 | GLint i; | ||
76 | GLfloat r0, r1, r2; | ||
77 | GLfloat da; | ||
78 | GLfloat *v; | ||
79 | Gear *gear; | ||
80 | double s[5], c[5]; | ||
81 | GLfloat normal[3]; | ||
82 | const int tris_per_tooth = 20; | ||
83 | Evas_GL_API *gl = gld->glApi; | ||
84 | |||
85 | gear = (Gear*)malloc(sizeof(Gear)); | ||
86 | if (gear == NULL) | ||
87 | return NULL; | ||
88 | |||
89 | r0 = inner_radius; | ||
90 | r1 = outer_radius - tooth_depth / 2.0; | ||
91 | r2 = outer_radius + tooth_depth / 2.0; | ||
92 | |||
93 | da = 2.0 * M_PI / teeth / 4.0; | ||
94 | |||
95 | gear->vertices = calloc(teeth * tris_per_tooth * 3 * 6, sizeof *gear->vertices); | ||
96 | s[4] = 0; | ||
97 | c[4] = 1; | ||
98 | v = gear->vertices; | ||
99 | for (i = 0; i < teeth; i++) | ||
100 | { | ||
101 | s[0] = s[4]; | ||
102 | c[0] = c[4]; | ||
103 | s[1] = sin(i * 2.0 * M_PI / teeth + da); | ||
104 | c[1] = cos(i * 2.0 * M_PI / teeth + da); | ||
105 | s[2] = sin(i * 2.0 * M_PI / teeth + da * 2); | ||
106 | c[2] = cos(i * 2.0 * M_PI / teeth + da * 2); | ||
107 | s[3] = sin(i * 2.0 * M_PI / teeth + da * 3); | ||
108 | c[3] = cos(i * 2.0 * M_PI / teeth + da * 3); | ||
109 | s[4] = sin(i * 2.0 * M_PI / teeth + da * 4); | ||
110 | c[4] = cos(i * 2.0 * M_PI / teeth + da * 4); | ||
111 | |||
112 | normal[0] = 0.0; | ||
113 | normal[1] = 0.0; | ||
114 | normal[2] = 1.0; | ||
115 | |||
116 | v = vert(v, r2 * c[1], r2 * s[1], width * 0.5, normal); | ||
117 | |||
118 | v = vert(v, r2 * c[1], r2 * s[1], width * 0.5, normal); | ||
119 | v = vert(v, r2 * c[2], r2 * s[2], width * 0.5, normal); | ||
120 | v = vert(v, r1 * c[0], r1 * s[0], width * 0.5, normal); | ||
121 | v = vert(v, r1 * c[3], r1 * s[3], width * 0.5, normal); | ||
122 | v = vert(v, r0 * c[0], r0 * s[0], width * 0.5, normal); | ||
123 | v = vert(v, r1 * c[4], r1 * s[4], width * 0.5, normal); | ||
124 | v = vert(v, r0 * c[4], r0 * s[4], width * 0.5, normal); | ||
125 | |||
126 | v = vert(v, r0 * c[4], r0 * s[4], width * 0.5, normal); | ||
127 | v = vert(v, r0 * c[0], r0 * s[0], width * 0.5, normal); | ||
128 | v = vert(v, r0 * c[4], r0 * s[4], -width * 0.5, normal); | ||
129 | v = vert(v, r0 * c[0], r0 * s[0], -width * 0.5, normal); | ||
130 | |||
131 | normal[0] = 0.0; | ||
132 | normal[1] = 0.0; | ||
133 | normal[2] = -1.0; | ||
134 | |||
135 | v = vert(v, r0 * c[4], r0 * s[4], -width * 0.5, normal); | ||
136 | |||
137 | v = vert(v, r0 * c[4], r0 * s[4], -width * 0.5, normal); | ||
138 | v = vert(v, r1 * c[4], r1 * s[4], -width * 0.5, normal); | ||
139 | v = vert(v, r0 * c[0], r0 * s[0], -width * 0.5, normal); | ||
140 | v = vert(v, r1 * c[3], r1 * s[3], -width * 0.5, normal); | ||
141 | v = vert(v, r1 * c[0], r1 * s[0], -width * 0.5, normal); | ||
142 | v = vert(v, r2 * c[2], r2 * s[2], -width * 0.5, normal); | ||
143 | v = vert(v, r2 * c[1], r2 * s[1], -width * 0.5, normal); | ||
144 | |||
145 | v = vert(v, r1 * c[0], r1 * s[0], width * 0.5, normal); | ||
146 | |||
147 | v = vert(v, r1 * c[0], r1 * s[0], width * 0.5, normal); | ||
148 | v = vert(v, r1 * c[0], r1 * s[0], -width * 0.5, normal); | ||
149 | v = vert(v, r2 * c[1], r2 * s[1], width * 0.5, normal); | ||
150 | v = vert(v, r2 * c[1], r2 * s[1], -width * 0.5, normal); | ||
151 | v = vert(v, r2 * c[2], r2 * s[2], width * 0.5, normal); | ||
152 | v = vert(v, r2 * c[2], r2 * s[2], -width * 0.5, normal); | ||
153 | v = vert(v, r1 * c[3], r1 * s[3], width * 0.5, normal); | ||
154 | v = vert(v, r1 * c[3], r1 * s[3], -width * 0.5, normal); | ||
155 | v = vert(v, r1 * c[4], r1 * s[4], width * 0.5, normal); | ||
156 | v = vert(v, r1 * c[4], r1 * s[4], -width * 0.5, normal); | ||
157 | |||
158 | v = vert(v, r1 * c[4], r1 * s[4], -width * 0.5, normal); | ||
159 | } | ||
160 | |||
161 | gear->count = (v - gear->vertices) / 6; | ||
162 | |||
163 | gl->glGenBuffers(1, &gear->vbo); | ||
164 | gl->glBindBuffer(GL_ARRAY_BUFFER, gear->vbo); | ||
165 | gl->glBufferData(GL_ARRAY_BUFFER, gear->count * 6 * 4, gear->vertices, GL_STATIC_DRAW); | ||
166 | |||
167 | |||
168 | return gear; | ||
169 | } | ||
170 | |||
171 | static void free_gear(Gear *gear) | ||
172 | { | ||
173 | free(gear->vertices); | ||
174 | free(gear); | ||
175 | gear = NULL; | ||
176 | } | ||
177 | |||
178 | static void multiply(GLfloat *m, const GLfloat *n) | ||
179 | { | ||
180 | GLfloat tmp[16]; | ||
181 | const GLfloat *row, *column; | ||
182 | div_t d; | ||
183 | int i, j; | ||
184 | |||
185 | for (i = 0; i < 16; i++) | ||
186 | { | ||
187 | tmp[i] = 0; | ||
188 | d = div(i, 4); | ||
189 | row = n + d.quot * 4; | ||
190 | column = m + d.rem; | ||
191 | for (j = 0; j < 4; j++) | ||
192 | tmp[i] += row[j] * column[j * 4]; | ||
193 | } | ||
194 | memcpy(m, &tmp, sizeof tmp); | ||
195 | } | ||
196 | |||
197 | static void rotate(GLfloat *m, GLfloat angle, GLfloat x, GLfloat y, GLfloat z) | ||
198 | { | ||
199 | double s, c; | ||
200 | |||
201 | s = sin(angle); | ||
202 | c = cos(angle); | ||
203 | GLfloat r[16] = | ||
204 | { | ||
205 | x * x * (1 - c) + c, y * x * (1 - c) + z * s, x * z * (1 - c) - y * s, 0, | ||
206 | x * y * (1 - c) - z * s, y * y * (1 - c) + c, y * z * (1 - c) + x * s, 0, | ||
207 | x * z * (1 - c) + y * s, y * z * (1 - c) - x * s, z * z * (1 - c) + c, 0, | ||
208 | 0, 0, 0, 1 | ||
209 | }; | ||
210 | |||
211 | multiply(m, r); | ||
212 | } | ||
213 | |||
214 | static void translate(GLfloat *m, GLfloat x, GLfloat y, GLfloat z) | ||
215 | { | ||
216 | GLfloat t[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1 }; | ||
217 | |||
218 | multiply(m, t); | ||
219 | } | ||
220 | |||
221 | static void draw_gear(GLData *gld, Gear *gear, GLfloat *m, GLfloat x, GLfloat y, GLfloat angle, const GLfloat *color) | ||
222 | { | ||
223 | Evas_GL_API *gl = gld->glApi; | ||
224 | GLfloat tmp[16]; | ||
225 | |||
226 | memcpy(tmp, m, sizeof tmp); | ||
227 | translate(tmp, x, y, 0); | ||
228 | rotate(tmp, 2 * M_PI * angle / 360.0, 0, 0, 1); | ||
229 | gl->glUniformMatrix4fv(gld->proj_location, 1, GL_FALSE, tmp); | ||
230 | gl->glUniform3fv(gld->light_location, 1, gld->light); | ||
231 | gl->glUniform4fv(gld->color_location, 1, color); | ||
232 | |||
233 | gl->glBindBuffer(GL_ARRAY_BUFFER, gear->vbo); | ||
234 | |||
235 | gl->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), NULL); | ||
236 | gl->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLfloat *) 0 + 3); | ||
237 | gl->glEnableVertexAttribArray(0); | ||
238 | gl->glEnableVertexAttribArray(1); | ||
239 | gl->glDrawArrays(GL_TRIANGLE_STRIP, 0, gear->count); | ||
240 | } | ||
241 | #endif | ||
242 | |||
243 | |||
244 | static void gldata_init(GLData *gld) | ||
245 | { | ||
246 | gld->useEGL = USE_EGL; | ||
247 | gld->useIrr = USE_IRR; | ||
248 | |||
249 | gld->view_rotx = -20.0; | ||
250 | gld->view_roty = -30.0; | ||
251 | gld->view_rotz = 0.0; | ||
252 | gld->angle = 0.0; | ||
253 | |||
254 | gld->light[0] = 1.0; | ||
255 | gld->light[1] = 1.0; | ||
256 | gld->light[2] = -5.0; | ||
257 | } | ||
258 | |||
259 | //-------------------------// | ||
260 | |||
261 | |||
262 | #if DO_GEARS | ||
263 | static const char vertex_shader[] = | ||
264 | "uniform mat4 proj;\n" | ||
265 | "attribute vec4 position;\n" | ||
266 | "attribute vec4 normal;\n" | ||
267 | "varying vec3 rotated_normal;\n" | ||
268 | "varying vec3 rotated_position;\n" | ||
269 | "vec4 tmp;\n" | ||
270 | "void main()\n" | ||
271 | "{\n" | ||
272 | " gl_Position = proj * position;\n" | ||
273 | " rotated_position = gl_Position.xyz;\n" | ||
274 | " tmp = proj * normal;\n" | ||
275 | " rotated_normal = tmp.xyz;\n" | ||
276 | "}\n"; | ||
277 | |||
278 | static const char fragment_shader[] = | ||
279 | "#ifdef GL_ES\n" | ||
280 | "precision mediump float;\n" | ||
281 | "#endif\n" | ||
282 | "uniform vec4 color;\n" | ||
283 | "uniform vec3 light;\n" | ||
284 | "varying vec3 rotated_normal;\n" | ||
285 | "varying vec3 rotated_position;\n" | ||
286 | "vec3 light_direction;\n" | ||
287 | "vec4 white = vec4(0.5, 0.5, 0.5, 1.0);\n" | ||
288 | "void main()\n" | ||
289 | "{\n" | ||
290 | " light_direction = normalize(light - rotated_position);\n" | ||
291 | " gl_FragColor = color + white * dot(light_direction, rotated_normal);\n" | ||
292 | "}\n"; | ||
293 | |||
294 | static GLuint load_shader(GLData *gld, GLenum type, const char *shader_src) | ||
295 | { | ||
296 | Evas_GL_API *gl = gld->glApi; | ||
297 | GLuint shader; | ||
298 | GLint compiled = 0; | ||
299 | |||
300 | // Create the shader object | ||
301 | if (!(shader = gl->glCreateShader(type))) return 0; | ||
302 | gl->glShaderSource(shader, 1, &shader_src, NULL); | ||
303 | // Compile the shader | ||
304 | gl->glCompileShader(shader); | ||
305 | gl->glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); | ||
306 | |||
307 | if (!compiled) | ||
308 | { | ||
309 | GLint len = 0; | ||
310 | |||
311 | gl->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len); | ||
312 | if (len > 1) | ||
313 | { | ||
314 | char *info = malloc(sizeof(char) * len); | ||
315 | |||
316 | if (info) | ||
317 | { | ||
318 | gl->glGetShaderInfoLog(shader, len, NULL, info); | ||
319 | printf("Error compiling shader:\n" | ||
320 | "%s\n", info); | ||
321 | free(info); | ||
322 | } | ||
323 | } | ||
324 | gl->glDeleteShader(shader); | ||
325 | return 0; | ||
326 | } | ||
327 | return shader; | ||
328 | } | ||
329 | |||
330 | static void gears_init(GLData *gld) | ||
331 | { | ||
332 | Evas_GL_API *gl = gld->glApi; | ||
333 | GLint linked = 0; | ||
334 | |||
335 | // char msg[512]; | ||
336 | |||
337 | gl->glEnable(GL_CULL_FACE); | ||
338 | gl->glEnable(GL_DEPTH_TEST); | ||
339 | gl->glEnable(GL_BLEND); | ||
340 | |||
341 | // Load the vertex/fragment shaders | ||
342 | gld->vtx_shader = load_shader(gld, GL_VERTEX_SHADER, vertex_shader); | ||
343 | gld->fgmt_shader = load_shader(gld, GL_FRAGMENT_SHADER, fragment_shader); | ||
344 | |||
345 | // Create the program object | ||
346 | if (!(gld->program = gl->glCreateProgram())) | ||
347 | return; | ||
348 | |||
349 | gl->glAttachShader(gld->program, gld->vtx_shader); | ||
350 | gl->glAttachShader(gld->program, gld->fgmt_shader); | ||
351 | |||
352 | // Bind shader attributes. | ||
353 | gl->glBindAttribLocation(gld->program, 0, "position"); | ||
354 | gl->glBindAttribLocation(gld->program, 1, "normal"); | ||
355 | |||
356 | // Link the program | ||
357 | gl->glLinkProgram(gld->program); | ||
358 | gld->glApi->glGetProgramiv(gld->program, GL_LINK_STATUS, &linked); | ||
359 | |||
360 | if (!linked) | ||
361 | { | ||
362 | GLint len = 0; | ||
363 | |||
364 | gld->glApi->glGetProgramiv(gld->program, GL_INFO_LOG_LENGTH, &len); | ||
365 | if (len > 1) | ||
366 | { | ||
367 | char *info = malloc(sizeof(char) * len); | ||
368 | |||
369 | if (info) | ||
370 | { | ||
371 | gld->glApi->glGetProgramInfoLog(gld->program, len, NULL, info); | ||
372 | printf("Error linking program:\n%s\n", info); | ||
373 | free(info); | ||
374 | } | ||
375 | } | ||
376 | gld->glApi->glDeleteProgram(gld->program); | ||
377 | } | ||
378 | |||
379 | gl->glUseProgram(gld->program); | ||
380 | gld->proj_location = gl->glGetUniformLocation(gld->program, "proj"); | ||
381 | gld->light_location = gl->glGetUniformLocation(gld->program, "light"); | ||
382 | gld->color_location = gl->glGetUniformLocation(gld->program, "color"); | ||
383 | |||
384 | /* make the gears */ | ||
385 | gld->gear1 = make_gear(gld, 1.0, 4.0, 1.0, 20, 0.7); | ||
386 | gld->gear2 = make_gear(gld, 0.5, 2.0, 2.0, 10, 0.7); | ||
387 | gld->gear3 = make_gear(gld, 1.3, 2.0, 0.5, 10, 0.7); | ||
388 | |||
389 | gld->gearsInited = EINA_TRUE; | ||
390 | } | ||
391 | #endif | ||
392 | |||
393 | static void _on_camera_input_down(void *data, Evas *evas, Evas_Object *obj, void *event_info) | ||
394 | { | ||
395 | GLData *gld = data; | ||
396 | Evas_Event_Key_Down *ev = event_info; | ||
397 | |||
398 | if (gld->move) | ||
399 | { | ||
400 | // TODO - Careful, gld->move MIGHT be read at the other end by another thread. MIGHT, coz I really don't know at what point the camera animate routine is actually called. | ||
401 | |||
402 | // Yes, we are dealing with the horrid Evas keyboard handling FUCKING STRING COMPARES! Soooo ... | ||
403 | // TODO - make this a hash lookup dammit. | ||
404 | if (0 == strcmp(ev->key, "Escape")) | ||
405 | { | ||
406 | } | ||
407 | else if (0 == strcmp(ev->key, "Left")) | ||
408 | gld->move->r = 2.0; | ||
409 | else if (0 == strcmp(ev->key, "Right")) | ||
410 | gld->move->r = -2.0; | ||
411 | else if (0 == strcmp(ev->key, "Up")) | ||
412 | gld->move->x = 2.0; | ||
413 | else if (0 == strcmp(ev->key, "Down")) | ||
414 | gld->move->x = -2.0; | ||
415 | // else if (0 == strcmp(ev->key, "Prior")) | ||
416 | // ; | ||
417 | // else if (0 == strcmp(ev->key, "Next")) | ||
418 | // ; | ||
419 | // else if (0 == strcmp(ev->key, "Home")) | ||
420 | // ; | ||
421 | // else if (0 == strcmp(ev->key, "End")) | ||
422 | // ; | ||
423 | else if (0 == strcmp(ev->key, "space")) | ||
424 | gld->move->jump = 1.0; | ||
425 | else | ||
426 | printf("Unexpected down keystroke - %s\n", ev->key); | ||
427 | } | ||
428 | else | ||
429 | printf("Camera input not ready\n"); | ||
430 | } | ||
431 | |||
432 | /* SL / OS camera controls | ||
433 | up / down / w / s moves avatar forward / backward | ||
434 | shifted version does the same | ||
435 | double tap triggers run mode / or fast fly mode | ||
436 | Running backwards turns your avatar to suit, walking does not. | ||
437 | left / right / a / d rotates avatar left / right, strafes in mouselook | ||
438 | shifted version turns the avatar to walk sideways, so not really a strafe. | ||
439 | So not sure if the "strafe" in mouse look turns the avatar as well? | ||
440 | PgDn / c crouch while it is held down move up in flight mode | ||
441 | PgUp jump move down in flight mode | ||
442 | Home toggle flying | ||
443 | End Nothing? | ||
444 | Esc return to third person view | ||
445 | m toggle mouse look | ||
446 | mouse wheel move view closer / further away from current focused object or avatar | ||
447 | Alt left click focus on some other object | ||
448 | Ins ??? | ||
449 | Del ??? | ||
450 | BS ??? | ||
451 | Tab ??? | ||
452 | |||
453 | Mouse look is just first person view, moving mouse looks left / right / up / down. | ||
454 | Not sure if the avatar rotates with left / right, but that's likely. | ||
455 | |||
456 | mouse moves With the left mouse button held down - | ||
457 | left / right up / down | ||
458 | --------------------------------- | ||
459 | for avatar swings avatar around zoom in and out of avatar | ||
460 | for object nothing | ||
461 | alt orbit left / right zoom in and out | ||
462 | alt ctrl orbit left / right orbit up / down | ||
463 | alt shift orbit left / right zoom in and out | ||
464 | alt ctrl shift shift view left / right / up / down | ||
465 | ctrl Nothing? | ||
466 | shift Nothing? | ||
467 | ctrl shift Nothing? | ||
468 | |||
469 | Need to also consider when looking at a moving object / avatar. | ||
470 | |||
471 | I think there are other letter keys that duplicate arrow keys and such. I'll look for them later, but I don't use them. | ||
472 | No idea what the function keys are mapped to, but think it's various non camera stuff. | ||
473 | I'm damn well leaving the Win/Command and Menu keys for the OS / window manager. lol | ||
474 | Keypad keys? Not interested, I don't have them. | ||
475 | Print Screen / SysRq, Pause / Break, other oddball keys, also not interested. | ||
476 | NOTE - gonna have an easily programmable "bind key to command" thingy, like E17s, so that can deal with other keys. | ||
477 | Should even let them be saveable so people can swap them with other people easily. | ||
478 | |||
479 | TODO - implement things like space mouse, sixaxis, phone as controller, joysticks, data gloves, etc. | ||
480 | */ | ||
481 | |||
482 | /* A moveRotate array of floats. | ||
483 | * X, Y, Z, and whatever the usual letters are for rotations. lol | ||
484 | * Each one means "move or rotate this much in this direction". | ||
485 | * Where 1.0 means "what ever the standard move is if that key is held down". | ||
486 | * So a keyboard move would just change it's part to 1.0 or -1.0 on key down, | ||
487 | * and back to 0.0 on key up. Or 2.0 / -2.0 if in run mode. | ||
488 | * Which would even work in fly mode. | ||
489 | * A joystick could be set to range over -2.0 to 2.0, and just set it's part directly. | ||
490 | * A mouse look rotate, well will come to that when we need to. B-) | ||
491 | * Setting the x or y to be the DIFFERENCE in window position of the mouse (-1.0 to 1.0) since the last frame. | ||
492 | * | ||
493 | * TODO - In the Elm_glview version, 2.0 seems to be correct speed for walking, but I thought 1.0 was in Evas_GL. | ||
494 | */ | ||
495 | |||
496 | static void _on_camera_input_up(void *data, Evas *evas, Evas_Object *obj, void *event_info) | ||
497 | { | ||
498 | GLData *gld = data; | ||
499 | Evas_Event_Key_Up *ev = event_info; | ||
500 | |||
501 | if (gld->move) | ||
502 | { | ||
503 | // TODO - Careful, gld->move MIGHT be read at the other end by another thread. MIGHT, coz I really don't know at what point the camera animate routine is actually called. | ||
504 | |||
505 | // Yes, we are dealing with the horrid Evas keyboard handling FUCKING STRING COMPARES! Soooo ... | ||
506 | // TODO - make this a hash lookup dammit. | ||
507 | if (0 == strcmp(ev->key, "Escape")) | ||
508 | { | ||
509 | } | ||
510 | else if (0 == strcmp(ev->key, "Left")) | ||
511 | gld->move->r = 0.0; | ||
512 | else if (0 == strcmp(ev->key, "Right")) | ||
513 | gld->move->r = 0.0; | ||
514 | else if (0 == strcmp(ev->key, "Up")) | ||
515 | gld->move->x = 0.0; | ||
516 | else if (0 == strcmp(ev->key, "Down")) | ||
517 | gld->move->x = 0.0; | ||
518 | // else if (0 == strcmp(ev->key, "Prior")) | ||
519 | // ; | ||
520 | // else if (0 == strcmp(ev->key, "Next")) | ||
521 | // ; | ||
522 | // else if (0 == strcmp(ev->key, "Home")) | ||
523 | // ; | ||
524 | // else if (0 == strcmp(ev->key, "End")) | ||
525 | // ; | ||
526 | else if (0 == strcmp(ev->key, "space")) | ||
527 | gld->move->jump = 0.0; | ||
528 | else | ||
529 | printf("Unexpected up keystroke - %s\n", ev->key); | ||
530 | } | ||
531 | else | ||
532 | printf("Camera input not ready\n"); | ||
533 | } | ||
534 | |||
535 | // Elm style event callback. | ||
536 | static Eina_Bool _cb_event_GL(void *data, Evas_Object *obj, Evas_Object *src, Evas_Callback_Type type, void *event_info) | ||
537 | { | ||
538 | GLData *gld = data; | ||
539 | Eina_Bool processed = EINA_FALSE; | ||
540 | |||
541 | switch (type) | ||
542 | { | ||
543 | case EVAS_CALLBACK_KEY_DOWN : | ||
544 | { | ||
545 | _on_camera_input_down(gld, evas_object_evas_get(obj), obj, event_info); | ||
546 | processed = EINA_TRUE; | ||
547 | break; | ||
548 | } | ||
549 | |||
550 | case EVAS_CALLBACK_KEY_UP : | ||
551 | { | ||
552 | _on_camera_input_up(gld, evas_object_evas_get(obj), obj, event_info); | ||
553 | processed = EINA_TRUE; | ||
554 | break; | ||
555 | } | ||
556 | |||
557 | default : | ||
558 | printf("Unknown GL input event.\n"); | ||
559 | } | ||
560 | |||
561 | return processed; | ||
562 | } | ||
563 | |||
564 | // Elm inlined image windows needs this to change focus on mouse click. | ||
565 | // Evas style event callback. | ||
566 | static void _cb_mouse_down_elm(void *data, Evas *evas, Evas_Object *obj, void *event_info) | ||
567 | { | ||
568 | // GLData *gld = data; | ||
569 | Evas_Event_Mouse_Down *ev = event_info; | ||
570 | |||
571 | if (1 == ev->button) | ||
572 | elm_object_focus_set(obj, EINA_TRUE); | ||
573 | } | ||
574 | |||
575 | static void _resize_winwin(GLData *gld) | ||
576 | { | ||
577 | Evas_Coord x, y, w, h; | ||
578 | |||
579 | evas_object_geometry_get(gld->elmGl, &x, &y, &w, &h); | ||
580 | evas_object_move(elm_win_inlined_image_object_get (gld->winwin), x, y); | ||
581 | evas_object_resize(elm_win_inlined_image_object_get(gld->winwin), w, h); | ||
582 | } | ||
583 | |||
584 | // Called from on_pixels (), or the Elm_gliew resize callback. | ||
585 | static void _resize(GLData *gld) | ||
586 | { | ||
587 | Evas_GL_API *gl = gld->glApi; | ||
588 | |||
589 | _resize_winwin(gld); | ||
590 | |||
591 | #if DO_GEARS | ||
592 | GLfloat ar, m[16] = { | ||
593 | 1.0, 0.0, 0.0, 0.0, | ||
594 | 0.0, 1.0, 0.0, 0.0, | ||
595 | 0.0, 0.0, 0.1, 0.0, | ||
596 | 0.0, 0.0, 0.0, 1.0 | ||
597 | }; | ||
598 | |||
599 | // GL Viewport stuff. you can avoid doing this if viewport is all the | ||
600 | // same as last frame if you want | ||
601 | if (gld->img_w < gld->img_h) | ||
602 | ar = gld->img_w; | ||
603 | else | ||
604 | ar = gld->img_h; | ||
605 | |||
606 | m[0] = 0.1 * ar / gld->img_w; | ||
607 | m[5] = 0.1 * ar / gld->img_h; | ||
608 | memcpy(gld->proj, m, sizeof gld->proj); | ||
609 | #endif | ||
610 | |||
611 | gl->glViewport(0, 0, (GLint) gld->img_w, (GLint) gld->img_h); | ||
612 | } | ||
613 | |||
614 | static void _resize_gl(Evas_Object *obj) | ||
615 | { | ||
616 | int w, h; | ||
617 | GLData *gld = evas_object_data_get(obj, "gld"); | ||
618 | |||
619 | elm_glview_size_get(obj, &w, &h); | ||
620 | |||
621 | gld->img_w = w; | ||
622 | gld->img_h = h; | ||
623 | _resize(gld); | ||
624 | } | ||
625 | |||
626 | static void on_pixels(void *data, Evas_Object *obj) | ||
627 | { | ||
628 | GLData *gld = data; | ||
629 | Evas_GL_API *gl = gld->glApi; | ||
630 | |||
631 | // get the image size in case it changed with evas_object_image_size_set() | ||
632 | if (gld->r1) | ||
633 | { | ||
634 | Evas_Coord w, h; | ||
635 | |||
636 | // Poor mans resize check. coz Elm wont do it easily. | ||
637 | evas_object_image_size_get(gld->r1, &w, &h); | ||
638 | if ((gld->img_w != w) || (gld->img_h != h)) | ||
639 | { | ||
640 | // No idea where this crap came from. | ||
641 | //float new_w = ((float) gld->scr_w / ((float) gld->scr_w * (float) w)); | ||
642 | //float new_h = ((float) gld->scr_h / ((float) gld->scr_h * (float) h)); | ||
643 | |||
644 | //gld->sfc_w = new_w; | ||
645 | //gld->sfc_h = new_h; | ||
646 | //evas_object_image_fill_set(gld->r1, 0, 0, gld->sfc_w, gld->sfc_h); | ||
647 | gld->img_w = w; | ||
648 | gld->img_h = h; | ||
649 | gld->resized = 1; | ||
650 | } | ||
651 | } | ||
652 | |||
653 | if (gld->useEGL) | ||
654 | { | ||
655 | // Yes, we DO need to do our own make current, coz aparently the Irrlicht one is useless. | ||
656 | // Hopefully Elm_GL has done this for us by now. | ||
657 | // Evas_GL needs it to. | ||
658 | if (gld->ctx) | ||
659 | evas_gl_make_current(gld->evasGl, gld->sfc, gld->ctx); | ||
660 | } | ||
661 | |||
662 | if (!gld->doneIrr) | ||
663 | gld->doneIrr = startIrr(gld); // Needs to be after gld->win is shown, and needs to be done in the render thread. | ||
664 | #if DO_GEARS | ||
665 | if (!gld->gearsInited) | ||
666 | gears_init(gld); | ||
667 | #endif | ||
668 | |||
669 | if (gld->resized) | ||
670 | _resize(gld); | ||
671 | |||
672 | drawIrr_start(gld); | ||
673 | |||
674 | #if DO_GEARS | ||
675 | if (gld->useEGL) | ||
676 | { | ||
677 | static const GLfloat red[4] = { 0.8, 0.1, 0.0, 1.0 }; | ||
678 | static const GLfloat green[4] = { 0.0, 0.8, 0.2, 1.0 }; | ||
679 | static const GLfloat blue[4] = { 0.2, 0.2, 1.0, 1.0 }; | ||
680 | GLfloat m[16]; | ||
681 | |||
682 | // Draw the gears. | ||
683 | if (!gld->useIrr) | ||
684 | { | ||
685 | gl->glClearColor(0.7, 0.0, 1.0, 1.0); | ||
686 | gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | ||
687 | } | ||
688 | |||
689 | memcpy(m, gld->proj, sizeof m); | ||
690 | rotate(m, 2 * M_PI * gld->view_rotx / 360.0, 1, 0, 0); | ||
691 | rotate(m, 2 * M_PI * gld->view_roty / 360.0, 0, 1, 0); | ||
692 | rotate(m, 2 * M_PI * gld->view_rotz / 360.0, 0, 0, 1); | ||
693 | |||
694 | draw_gear(gld, gld->gear1, m, -3.0, -2.0, gld->angle, red); | ||
695 | draw_gear(gld, gld->gear2, m, 3.1, -2.0, -2 * gld->angle - 9.0, green); | ||
696 | draw_gear(gld, gld->gear3, m, -3.1, 4.2, -2 * gld->angle - 25.0, blue); | ||
697 | gld->angle += 2.0; | ||
698 | } | ||
699 | #endif | ||
700 | |||
701 | drawIrr_end(gld); | ||
702 | |||
703 | #if USE_IR | ||
704 | #else | ||
705 | // This might get done deep within drawIrr_end, but only if we are using Irrlicht. | ||
706 | |||
707 | // Optional - Flush the GL pipeline | ||
708 | gl->glFlush(); | ||
709 | // gl->glFinish(); | ||
710 | #endif | ||
711 | |||
712 | gld->resized = 0; | ||
713 | } | ||
714 | |||
715 | static void _draw_gl(Evas_Object *obj) | ||
716 | { | ||
717 | // Evas_GL_API *gl = elm_glview_gl_api_get(obj); | ||
718 | GLData *gld = evas_object_data_get(obj, "gld"); | ||
719 | if (!gld) return; | ||
720 | |||
721 | on_pixels(gld, obj); | ||
722 | } | ||
723 | |||
724 | // Callback from Evas, also used as the general callback for deleting the GL stuff. | ||
725 | static void _clean_gl(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) | ||
726 | { | ||
727 | GLData *gld = data; | ||
728 | Evas_GL_API *gl = gld->glApi; | ||
729 | |||
730 | ecore_animator_del(gld->animator); | ||
731 | |||
732 | if (gld->useEGL) | ||
733 | { | ||
734 | // Do a make_current before deleting all the GL stuff. | ||
735 | evas_gl_make_current(gld->evasGl, gld->sfc, gld->ctx); | ||
736 | |||
737 | } | ||
738 | |||
739 | gl->glDeleteShader(gld->vtx_shader); | ||
740 | gl->glDeleteShader(gld->fgmt_shader); | ||
741 | gl->glDeleteProgram(gld->program); | ||
742 | |||
743 | if (gld->evasGl) | ||
744 | { | ||
745 | // Irrlicht wants to destroy the context and surface, so only do this if Irrlicht wont. | ||
746 | if (!gld->doneIrr) | ||
747 | { | ||
748 | evas_gl_surface_destroy(gld->evasGl, gld->sfc); | ||
749 | evas_gl_context_destroy(gld->evasGl, gld->ctx); | ||
750 | } | ||
751 | // TODO - hope this is OK, considering the context and surface might get dealt with by Irrlicht. | ||
752 | // Might be better to teach Irrlicht to not destroy shit it did not create. | ||
753 | evas_gl_config_free(gld->cfg); | ||
754 | evas_gl_free(gld->evasGl); | ||
755 | } | ||
756 | |||
757 | // TODO - Since this is created on the render thread, better hope this is being deleted on the render thread. | ||
758 | finishIrr(gld); | ||
759 | |||
760 | #if DO_GEARS | ||
761 | gl->glDeleteBuffers(1, &gld->gear1->vbo); | ||
762 | gl->glDeleteBuffers(1, &gld->gear2->vbo); | ||
763 | gl->glDeleteBuffers(1, &gld->gear3->vbo); | ||
764 | |||
765 | free_gear(gld->gear1); | ||
766 | free_gear(gld->gear2); | ||
767 | free_gear(gld->gear3); | ||
768 | #endif | ||
769 | } | ||
770 | |||
771 | // Callback from Elm, coz they do shit different. | ||
772 | static void _del_gl(Evas_Object *obj) | ||
773 | { | ||
774 | GLData *gld = evas_object_data_get(obj, "gld"); | ||
775 | if (!gld) | ||
776 | { | ||
777 | printf("Unable to get GLData. \n"); | ||
778 | return; | ||
779 | } | ||
780 | |||
781 | _clean_gl(gld, NULL, NULL, NULL); | ||
782 | |||
783 | evas_object_data_del((Evas_Object*)obj, "gld"); | ||
784 | } | ||
785 | |||
786 | // Callback for when the app quits. | ||
787 | static void _on_done(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) | ||
788 | { | ||
789 | GLData *gld = data; | ||
790 | |||
791 | evas_object_del(gld->win); | ||
792 | free(gld); | ||
793 | elm_exit(); | ||
794 | } | ||
795 | |||
796 | // Callback from the animator. | ||
797 | static Eina_Bool doFrame(void *data) | ||
798 | { | ||
799 | GLData *gld = data; | ||
800 | |||
801 | // Mark the pixels as dirty, so they get rerendered each frame, then Irrlicht can draw it's stuff each frame. | ||
802 | // This causes on_pixel to be triggered by Evas_GL, and _draw_gl for Elm_glview. | ||
803 | if (gld->r1) | ||
804 | evas_object_image_pixels_dirty_set(gld->r1, EINA_TRUE); | ||
805 | if (gld->elmGl) | ||
806 | elm_glview_changed_set(gld->elmGl); | ||
807 | |||
808 | // If not using Evas_GL, we need to call on_pixel() manually. | ||
809 | if (!gld->useEGL) | ||
810 | on_pixels(gld, gld->r1); | ||
811 | |||
812 | return EINA_TRUE; // Keep calling us. | ||
813 | } | ||
814 | |||
815 | static void init_evas_gl(GLData *gld) | ||
816 | { | ||
817 | if (!gld->useEGL) | ||
818 | return; | ||
819 | |||
820 | gld->sfc_w = gld->win_w; | ||
821 | gld->sfc_h = gld->win_h; | ||
822 | |||
823 | // Get the Evas / canvas from the elm window (that the Evas_Object "lives on"), which is itself an Evas_Object created by Elm, so not sure if it was created internally with Ecore_Evas. | ||
824 | gld->canvas = evas_object_evas_get(gld->win); | ||
825 | // An Ecore_Evas holds an Evas. | ||
826 | // Get the Ecore_Evas that wraps an Evas. | ||
827 | gld->ee = ecore_evas_ecore_evas_get(gld->canvas); // Only use this on Evas that was created with Ecore_Evas. | ||
828 | |||
829 | #if USE_ELM_GL | ||
830 | // Add a GLView | ||
831 | gld->elmGl = elm_glview_add(gld->win); | ||
832 | evas_object_size_hint_align_set(gld->elmGl, EVAS_HINT_FILL, EVAS_HINT_FILL); | ||
833 | evas_object_size_hint_weight_set(gld->elmGl, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); | ||
834 | elm_glview_mode_set(gld->elmGl, 0 | ELM_GLVIEW_ALPHA | ELM_GLVIEW_DEPTH | ELM_GLVIEW_DIRECT); | ||
835 | elm_glview_resize_policy_set(gld->elmGl, ELM_GLVIEW_RESIZE_POLICY_RECREATE); // Destroy the current surface on a resize and create a new one. | ||
836 | elm_glview_render_policy_set(gld->elmGl, ELM_GLVIEW_RENDER_POLICY_ON_DEMAND); | ||
837 | // elm_glview_render_policy_set(gld->elmGl, ELM_GLVIEW_RENDER_POLICY_ALWAYS); | ||
838 | // These get called in the render thread I think. | ||
839 | // None let me pass data, so this is why we are adding "gld" data to the object below. | ||
840 | // Maybe we can use elm_object_signal_callback_add or elm_object_item_signal_callback_add (edje signals)? | ||
841 | //elm_glview_init_func_set(gld->elmGl, _init_gl); // Not actually needed, it gets done in on_pixels. | ||
842 | elm_glview_del_func_set(gld->elmGl, _del_gl); | ||
843 | elm_glview_resize_func_set(gld->elmGl, _resize_gl); | ||
844 | elm_glview_render_func_set(gld->elmGl, (Elm_GLView_Func_Cb) _draw_gl); | ||
845 | |||
846 | // Not needed, the resize callback above deals with that. | ||
847 | //elm_win_resize_object_add(gld->win, gld->elmGl); | ||
848 | gld->glApi = elm_glview_gl_api_get(gld->elmGl); | ||
849 | evas_object_data_set(gld->elmGl, "gld", gld); | ||
850 | evas_object_show(gld->elmGl); | ||
851 | elm_box_pack_end(gld->bx, gld->elmGl); | ||
852 | #else | ||
853 | // get the evas gl handle for doing gl things | ||
854 | gld->evasGl = evas_gl_new(gld->canvas); | ||
855 | gld->glApi = evas_gl_api_get(gld->evasGl); | ||
856 | |||
857 | // Set a surface config | ||
858 | gld->cfg = evas_gl_config_new(); | ||
859 | gld->cfg->color_format = EVAS_GL_RGBA_8888; | ||
860 | gld->cfg->depth_bits = EVAS_GL_DEPTH_BIT_32; | ||
861 | gld->cfg->stencil_bits = EVAS_GL_STENCIL_NONE; | ||
862 | gld->cfg->options_bits = EVAS_GL_OPTIONS_DIRECT; | ||
863 | |||
864 | // create a surface and context | ||
865 | gld->sfc = evas_gl_surface_create(gld->evasGl, gld->cfg, gld->sfc_w, gld->sfc_h); | ||
866 | gld->ctx = evas_gl_context_create(gld->evasGl, NULL); // The second NULL is for sharing contexts I think, which might be what we want to do with Irrlicht. It's not documented. | ||
867 | |||
868 | // Set up the image object, a filled one by default. | ||
869 | gld->r1 = evas_object_image_filled_add(gld->canvas); | ||
870 | |||
871 | // attach important data we need to the object using key names. This just | ||
872 | // avoids some global variables and means we can do nice cleanup. You can | ||
873 | // avoid this if you are lazy | ||
874 | // Not actually needed, with evas we can pass data pointers to stuff. | ||
875 | //evas_object_data_set(gld->r1, "gld", gld); | ||
876 | |||
877 | // when the object is deleted - call the on_del callback. like the above, | ||
878 | // this is just being clean | ||
879 | evas_object_event_callback_add(gld->r1, EVAS_CALLBACK_DEL, _clean_gl, gld); | ||
880 | |||
881 | // set up an actual pixel size for the buffer data. it may be different | ||
882 | // to the output size. any windowing system has something like this, just | ||
883 | // evas has 2 sizes, a pixel size and the output object size | ||
884 | evas_object_image_size_set(gld->r1, gld->sfc_w, gld->sfc_h); | ||
885 | // Not actualy needed, as we create the image already filled. | ||
886 | //evas_object_image_fill_set(gld->r1, 0, 0, gld->sfc_w, gld->sfc_h); | ||
887 | |||
888 | // These two are not in the original example, but I get black r1 when I leave them out. | ||
889 | evas_object_size_hint_align_set(gld->r1, EVAS_HINT_FILL, EVAS_HINT_FILL); | ||
890 | evas_object_size_hint_weight_set(gld->r1, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); | ||
891 | |||
892 | // set up the native surface info to use the context and surface created | ||
893 | // above | ||
894 | evas_gl_native_surface_get(gld->evasGl, gld->sfc, &(gld->ns)); | ||
895 | evas_object_image_native_surface_set(gld->r1, &(gld->ns)); | ||
896 | evas_object_image_pixels_get_callback_set(gld->r1, on_pixels, gld); | ||
897 | |||
898 | // move the image object somewhere, resize it and show it. any windowing | ||
899 | // system would need this kind of thing - place a child "window" | ||
900 | // Hmm, no need to resize it anyway, it's sized above. | ||
901 | evas_object_move(gld->r1, 0, 0); | ||
902 | //evas_object_resize(gld->r1, gld->sfc_w, gld->sfc_h); | ||
903 | elm_win_resize_object_add(gld->win, gld->r1); | ||
904 | evas_object_show(gld->r1); | ||
905 | elm_box_pack_end(gld->bx, gld->r1); | ||
906 | |||
907 | evas_object_event_callback_add(gld->r1, EVAS_CALLBACK_MOUSE_DOWN, _cb_mouse_down_GL, gld); | ||
908 | // evas_object_event_callback_add(gld->r1, EVAS_CALLBACK_KEY_DOWN, _on_camera_input_down, gld); | ||
909 | // evas_object_event_callback_add(gld->r1, EVAS_CALLBACK_KEY_UP, _on_camera_input_up, gld); | ||
910 | #endif | ||
911 | |||
912 | // NOTE: if you delete r1, this animator will keep running trying to access | ||
913 | // r1 so you'd better delete this animator with ecore_animator_del() or | ||
914 | // structure how you do animation differently. you can also attach it like | ||
915 | // evasGl, sfc, etc. etc. if this animator is specific to this object | ||
916 | // only and delete it in the del handler for the obj. | ||
917 | // | ||
918 | // TODO - apparently the proper way to deal with the new async rendering is to have this animator do the dirty thing, and call the Irrlicht rendering stuff in the on_pixel call set above. | ||
919 | // That still leaves the problem of the Irrlicht setup being in the main thread. Which also should be done in on_pixel, as that's done in the correct thread. | ||
920 | |||
921 | // Jiggling this seems to produce a trade off between flickering and frame rate. Nothing else changed the flickering. | ||
922 | ecore_animator_frametime_set(0.04); // Default is 1/30, or 0.033333 | ||
923 | gld->animator = ecore_animator_add(doFrame, gld); // This animator will be called every frame tick, which defaults to 1/30 seconds. | ||
924 | |||
925 | return; | ||
926 | } | ||
927 | |||
928 | |||
929 | //-------------------------// | ||
930 | |||
931 | |||
932 | static Evas_Object *_content_image_new(Evas_Object *parent, const char *img) | ||
933 | { | ||
934 | Evas_Object *ic; | ||
935 | |||
936 | ic = elm_icon_add(parent); | ||
937 | elm_image_file_set(ic, img, NULL); | ||
938 | return ic; | ||
939 | } | ||
940 | |||
941 | static void _promote(void *data, Evas_Object *obj , void *event_info ) | ||
942 | { | ||
943 | elm_naviframe_item_promote(data); | ||
944 | } | ||
945 | |||
946 | static char *_grid_label_get(void *data, Evas_Object *obj, const char *part) | ||
947 | { | ||
948 | ezGrid *thisGrid = data; | ||
949 | char buf[256]; | ||
950 | |||
951 | if (!strcmp(part, "elm.text")) | ||
952 | { | ||
953 | int count = eina_clist_count(&(thisGrid->accounts)); | ||
954 | |||
955 | if (0 == count) | ||
956 | snprintf(buf, sizeof(buf), "%s (no accounts)", thisGrid->name); | ||
957 | else if (1 == count) | ||
958 | snprintf(buf, sizeof(buf), "%s (%d account)", thisGrid->name, count); | ||
959 | else | ||
960 | snprintf(buf, sizeof(buf), "%s (%d accounts)", thisGrid->name, count); | ||
961 | } | ||
962 | else | ||
963 | snprintf(buf, sizeof(buf), "%s", thisGrid->loginURI); | ||
964 | return strdup(buf); | ||
965 | } | ||
966 | |||
967 | static Evas_Object *_grid_content_get(void *data, Evas_Object *obj, const char *part) | ||
968 | { | ||
969 | ezGrid *thisGrid = data; | ||
970 | Evas_Object *ic = elm_icon_add(obj); | ||
971 | |||
972 | if (!strcmp(part, "elm.swallow.icon")) | ||
973 | elm_icon_standard_set(ic, thisGrid->icon); | ||
974 | |||
975 | evas_object_size_hint_aspect_set(ic, EVAS_ASPECT_CONTROL_VERTICAL, 1, 1); | ||
976 | return ic; | ||
977 | } | ||
978 | |||
979 | static char * _account_label_get(void *data, Evas_Object *obj, const char *part) | ||
980 | { | ||
981 | ezAccount *thisAccount = data; | ||
982 | char buf[256]; | ||
983 | |||
984 | buf[0] = '\0'; | ||
985 | if (!strcmp(part, "elm.text")) | ||
986 | snprintf(buf, sizeof(buf), "%s", thisAccount->name); | ||
987 | |||
988 | return strdup(buf); | ||
989 | } | ||
990 | |||
991 | static Evas_Object *_account_content_get(void *data, Evas_Object *obj, const char *part) | ||
992 | { | ||
993 | ezAccount *thisAccount = data; | ||
994 | Evas_Object *ic = elm_icon_add(obj); | ||
995 | |||
996 | if (!strcmp(part, "elm.swallow.icon")) | ||
997 | elm_icon_standard_set(ic, thisAccount->icon); | ||
998 | |||
999 | evas_object_size_hint_aspect_set(ic, EVAS_ASPECT_CONTROL_VERTICAL, 1, 1); | ||
1000 | return ic; | ||
1001 | } | ||
1002 | |||
1003 | static char *_viewer_label_get(void *data, Evas_Object *obj, const char *part) | ||
1004 | { | ||
1005 | ezViewer *thisViewer = data; | ||
1006 | char buf[256]; | ||
1007 | |||
1008 | if (!strcmp(part, "elm.text")) | ||
1009 | snprintf(buf, sizeof(buf), "%s", thisViewer->name); | ||
1010 | else | ||
1011 | snprintf(buf, sizeof(buf), "%s", thisViewer->version); | ||
1012 | return strdup(buf); | ||
1013 | } | ||
1014 | |||
1015 | static Evas_Object *_viewer_content_get(void *data, Evas_Object *obj, const char *part) | ||
1016 | { | ||
1017 | ezViewer *thisViewer = data; | ||
1018 | Evas_Object *ic = elm_icon_add(obj); | ||
1019 | |||
1020 | if (!strcmp(part, "elm.swallow.icon")) | ||
1021 | elm_icon_standard_set(ic, thisViewer->icon); | ||
1022 | |||
1023 | evas_object_size_hint_aspect_set(ic, EVAS_ASPECT_CONTROL_VERTICAL, 1, 1); | ||
1024 | return ic; | ||
1025 | } | ||
1026 | |||
1027 | |||
1028 | static void _grid_sel_cb(void *data, Evas_Object *obj, void *event_info) | ||
1029 | { | ||
1030 | ezGrid *thisGrid = data; | ||
1031 | GLData *gld = thisGrid->gld; | ||
1032 | char buf[PATH_MAX]; | ||
1033 | |||
1034 | // sprintf(buf, "dillo -f -g '%dx%d+%d+%d' %s &", gld->win_w - (gld->win_w / 5), gld->win_h - 30, gld->win_w / 5, gld->win_y, thisGrid->splashPage); | ||
1035 | sprintf(buf, "uzbl -g '%dx%d+%d+%d' -u %s &", gld->win_w - (gld->win_w / 5), gld->win_h - 30, gld->win_w / 5, gld->win_y, thisGrid->splashPage); | ||
1036 | printf("%s ### genlist obj [%p], item pointer [%p]\n", buf, obj, event_info); | ||
1037 | // comment this out for now, busy dealing with input stuff, don't want to trigger this multiple times. | ||
1038 | // system(buf); | ||
1039 | } | ||
1040 | |||
1041 | static void cb_mouse_move(void *data, Evas *evas, Evas_Object *obj, void *event_info) | ||
1042 | { | ||
1043 | Evas_Event_Mouse_Move *ev = event_info; | ||
1044 | Evas_Object *orig = data; | ||
1045 | Evas_Coord x, y; | ||
1046 | Evas_Map *p; | ||
1047 | int i, w, h; | ||
1048 | |||
1049 | if (!ev->buttons) return; | ||
1050 | evas_object_geometry_get(obj, &x, &y, NULL, NULL); | ||
1051 | evas_object_move(obj, | ||
1052 | x + (ev->cur.canvas.x - ev->prev.output.x), | ||
1053 | y + (ev->cur.canvas.y - ev->prev.output.y)); | ||
1054 | evas_object_image_size_get(orig, &w, &h); | ||
1055 | p = evas_map_new(4); | ||
1056 | evas_object_map_enable_set(orig, EINA_TRUE); | ||
1057 | // evas_object_raise(orig); | ||
1058 | for (i = 0; i < 4; i++) | ||
1059 | { | ||
1060 | Evas_Object *hand; | ||
1061 | char key[32]; | ||
1062 | |||
1063 | snprintf(key, sizeof(key), "h-%i\n", i); | ||
1064 | hand = evas_object_data_get(orig, key); | ||
1065 | evas_object_raise(hand); | ||
1066 | evas_object_geometry_get(hand, &x, &y, NULL, NULL); | ||
1067 | x += 15; | ||
1068 | y += 15; | ||
1069 | evas_map_point_coord_set(p, i, x, y, 0); | ||
1070 | if (i == 0) evas_map_point_image_uv_set(p, i, 0, 0); | ||
1071 | else if (i == 1) evas_map_point_image_uv_set(p, i, w, 0); | ||
1072 | else if (i == 2) evas_map_point_image_uv_set(p, i, w, h); | ||
1073 | else if (i == 3) evas_map_point_image_uv_set(p, i, 0, h); | ||
1074 | } | ||
1075 | evas_object_map_set(orig, p); | ||
1076 | evas_map_free(p); | ||
1077 | } | ||
1078 | |||
1079 | static void create_handles(Evas_Object *obj) | ||
1080 | { | ||
1081 | int i; | ||
1082 | Evas_Coord x, y, w, h; | ||
1083 | |||
1084 | evas_object_geometry_get(obj, &x, &y, &w, &h); | ||
1085 | for (i = 0; i < 4; i++) | ||
1086 | { | ||
1087 | Evas_Object *hand; | ||
1088 | char buf[PATH_MAX]; | ||
1089 | char key[32]; | ||
1090 | |||
1091 | hand = evas_object_image_filled_add(evas_object_evas_get(obj)); | ||
1092 | evas_object_resize(hand, 31, 31); | ||
1093 | snprintf(buf, sizeof(buf), "%s/media/pt.png", elm_app_data_dir_get()); | ||
1094 | evas_object_image_file_set(hand, buf, NULL); | ||
1095 | if (i == 0) evas_object_move(hand, x - 15, y - 15); | ||
1096 | else if (i == 1) evas_object_move(hand, x + w - 15, y - 15); | ||
1097 | else if (i == 2) evas_object_move(hand, x + w - 15, y + h - 15); | ||
1098 | else if (i == 3) evas_object_move(hand, x - 15, y + h - 15); | ||
1099 | evas_object_event_callback_add(hand, EVAS_CALLBACK_MOUSE_MOVE, cb_mouse_move, obj); | ||
1100 | evas_object_show(hand); | ||
1101 | snprintf(key, sizeof(key), "h-%i\n", i); | ||
1102 | evas_object_data_set(obj, key, hand); | ||
1103 | } | ||
1104 | } | ||
1105 | |||
1106 | static Evas_Object *_toolbar_menu_add(Evas_Object *win, Evas_Object *tb, char *label) | ||
1107 | { | ||
1108 | Evas_Object *menu= NULL; | ||
1109 | Elm_Object_Item *tb_it; | ||
1110 | //, *menu_it; | ||
1111 | |||
1112 | tb_it = elm_toolbar_item_append(tb, NULL, label, NULL, NULL); | ||
1113 | elm_toolbar_item_menu_set(tb_it, EINA_TRUE); | ||
1114 | // Priority is for when toolbar items are set to hide or menu when there are too many of them. They get hidden or put on the menu based on priority. | ||
1115 | elm_toolbar_item_priority_set(tb_it, 9999); | ||
1116 | elm_toolbar_menu_parent_set(tb, win); | ||
1117 | menu = elm_toolbar_item_menu_get(tb_it); | ||
1118 | |||
1119 | return menu; | ||
1120 | } | ||
1121 | |||
1122 | static Evas_Object *fang_win_add(GLData *gld) | ||
1123 | { | ||
1124 | Evas_Object *win, *bg; | ||
1125 | |||
1126 | // In theory this should create an EWS window, in practice, I'm not seeing any difference. | ||
1127 | // Guess I'll have to implement my own internal window manager. I don't think a basic one will be that hard. Famous last words. | ||
1128 | // elm_config_engine_set("ews"); | ||
1129 | win = elm_win_add(gld->win, "inlined", ELM_WIN_INLINED_IMAGE); | ||
1130 | // On mouse down we try to shift focus to the backing image, this seems to be the correct thing to force focus onto it's widgets. | ||
1131 | // According to the Elm inlined image window example, this is what's needed to. | ||
1132 | evas_object_event_callback_add(elm_win_inlined_image_object_get(win), EVAS_CALLBACK_MOUSE_DOWN, _cb_mouse_down_elm, gld); | ||
1133 | elm_win_alpha_set(win, EINA_TRUE); | ||
1134 | |||
1135 | // Apparently transparent is not good enough for ELM backgrounds, so make it a rectangle. | ||
1136 | // Apparently coz ELM prefers stuff to have edjes. A bit over the top if all I want is a transparent rectangle. | ||
1137 | bg = evas_object_rectangle_add(evas_object_evas_get(win)); | ||
1138 | evas_object_color_set(bg, 50, 0, 100, 100); | ||
1139 | evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); | ||
1140 | elm_win_resize_object_add(win, bg); | ||
1141 | evas_object_show(bg); | ||
1142 | |||
1143 | return win; | ||
1144 | } | ||
1145 | |||
1146 | static void fang_win_complete(GLData *gld, Evas_Object *win, int x, int y, int w, int h) | ||
1147 | { | ||
1148 | // image object for win is unlinked to its pos/size - so manual control | ||
1149 | // this allows also for using map and other things with it. | ||
1150 | evas_object_move(elm_win_inlined_image_object_get(win), x, y); | ||
1151 | // Odd, it needs to be resized twice. WTF? | ||
1152 | evas_object_resize(win, w, h); | ||
1153 | evas_object_resize(elm_win_inlined_image_object_get(win), w, h); | ||
1154 | evas_object_show(win); | ||
1155 | create_handles(elm_win_inlined_image_object_get(win)); | ||
1156 | } | ||
1157 | |||
1158 | static void overlay_add(GLData *gld) | ||
1159 | { | ||
1160 | Evas_Object *bg; | ||
1161 | //, *bx, *tb, *menu; | ||
1162 | // Elm_Object_Item *tb_it, *menu_it; | ||
1163 | |||
1164 | // There many are reasons for this window. | ||
1165 | // The first is to cover the GL and provide something to click on to change focus. | ||
1166 | // The second is to provide something to click on for all the GL type clicking stuff that needs to be done. In other words, no click through,we catch the clicks here. | ||
1167 | // So we can probably avoid the following issue - | ||
1168 | // How to do click through? evas_object_pass_events_set(rectangle, EINA_TRUE), and maybe need to do that to the underlaying window to? | ||
1169 | // Though if the rectangle is entirely transparent, or even hidden, events might pass through anyway. | ||
1170 | // Gotta have click through on the parts where there's no other window. | ||
1171 | // The third is to have the other windows live here. | ||
1172 | // This idea doesn't work, as it breaks the damn focus again. | ||
1173 | // Don't think it's needed anyway. | ||
1174 | // While on the subject of layers, need a HUD layer of some sort, but Irrlicht might support that itself. | ||
1175 | |||
1176 | gld->winwin = elm_win_add(gld->win, "inlined", ELM_WIN_INLINED_IMAGE); | ||
1177 | // On mouse down we try to shift focus to the backing image, this seems to be the correct thing to force focus onto it's widgets. | ||
1178 | // According to the Elm inlined image window example, this is what's needed to. | ||
1179 | evas_object_event_callback_add(elm_win_inlined_image_object_get(gld->winwin), EVAS_CALLBACK_MOUSE_DOWN, _cb_mouse_down_elm, gld); | ||
1180 | // In this code, we are making our own camera, so grab it's input when we are focused. | ||
1181 | evas_object_event_callback_add(gld->winwin, EVAS_CALLBACK_KEY_DOWN, _on_camera_input_down, gld); | ||
1182 | evas_object_event_callback_add(gld->winwin, EVAS_CALLBACK_KEY_UP, _on_camera_input_up, gld); | ||
1183 | elm_object_event_callback_add(gld->winwin, _cb_event_GL, gld); | ||
1184 | |||
1185 | elm_win_alpha_set(gld->winwin, EINA_TRUE); | ||
1186 | // Apparently transparent is not good enough for ELM backgrounds, so make it a rectangle. | ||
1187 | // Apparently coz ELM prefers stuff to have edjes. A bit over the top if all I want is a transparent rectangle. | ||
1188 | bg = evas_object_rectangle_add(evas_object_evas_get(gld->winwin)); | ||
1189 | evas_object_color_set(bg, 0, 0, 0, 0); | ||
1190 | evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); | ||
1191 | elm_win_resize_object_add(gld->winwin, bg); | ||
1192 | evas_object_show(bg); | ||
1193 | |||
1194 | // image object for win is unlinked to its pos/size - so manual control | ||
1195 | // this allows also for using map and other things with it. | ||
1196 | evas_object_move(elm_win_inlined_image_object_get(gld->winwin), 0, 0); | ||
1197 | // Odd, it needs to be resized twice. WTF? | ||
1198 | evas_object_resize(gld->winwin, gld->win_w, gld->win_h); | ||
1199 | evas_object_resize(elm_win_inlined_image_object_get(gld->winwin), gld->win_w, gld->win_h); | ||
1200 | evas_object_show(gld->winwin); | ||
1201 | } | ||
1202 | |||
1203 | static void chat_add(GLData *gld) | ||
1204 | { | ||
1205 | Evas_Object *win, *bx, *en; | ||
1206 | |||
1207 | win = fang_win_add(gld); | ||
1208 | |||
1209 | bx = elm_box_add(win); | ||
1210 | elm_win_resize_object_add(win, bx); | ||
1211 | evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); | ||
1212 | evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, EVAS_HINT_FILL); | ||
1213 | |||
1214 | en = elm_entry_add(win); | ||
1215 | elm_entry_scrollable_set(en, EINA_TRUE); | ||
1216 | evas_object_size_hint_weight_set(en, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); | ||
1217 | evas_object_size_hint_align_set(en, EVAS_HINT_FILL, EVAS_HINT_FILL); | ||
1218 | elm_object_text_set(en, "History is shown here"); | ||
1219 | elm_entry_editable_set(en, EINA_FALSE); | ||
1220 | evas_object_show(en); | ||
1221 | elm_box_pack_end(bx, en); | ||
1222 | |||
1223 | en = elm_entry_add(win); | ||
1224 | elm_entry_scrollable_set(en, EINA_TRUE); | ||
1225 | evas_object_size_hint_weight_set(en, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); | ||
1226 | evas_object_size_hint_align_set(en, EVAS_HINT_FILL, EVAS_HINT_FILL); | ||
1227 | elm_object_text_set(en, ""); | ||
1228 | elm_entry_editable_set(en, EINA_TRUE); | ||
1229 | evas_object_show(en); | ||
1230 | elm_box_pack_end(bx, en); | ||
1231 | |||
1232 | evas_object_show(bx); | ||
1233 | |||
1234 | fang_win_complete(gld, win, 30, 500, gld->win_w / 3, gld->win_h / 3); | ||
1235 | } | ||
1236 | |||
1237 | |||
1238 | static void woMan_add(GLData *gld) | ||
1239 | { | ||
1240 | // Evas_Object *win, *bg, *bx, *ic, *bb, *av, *en, *bt, *nf, *tab, *tb, *gridList, *viewerList, *menu; | ||
1241 | Evas_Object *win, *bx, *bt, *nf, *tab, *tb, *gridList, *viewerList, *menu; | ||
1242 | Elm_Object_Item *tb_it, *menu_it, *tab_it; | ||
1243 | // char buf[PATH_MAX]; | ||
1244 | int i; | ||
1245 | |||
1246 | win = fang_win_add(gld); | ||
1247 | |||
1248 | bx = elm_box_add(win); | ||
1249 | elm_win_resize_object_add(win, bx); | ||
1250 | evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); | ||
1251 | evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, EVAS_HINT_FILL); | ||
1252 | |||
1253 | // A tab thingy. | ||
1254 | tb = elm_toolbar_add(win); | ||
1255 | evas_object_size_hint_weight_set(tb, EVAS_HINT_EXPAND, 0.0); | ||
1256 | evas_object_size_hint_align_set(tb, EVAS_HINT_FILL, EVAS_HINT_FILL); | ||
1257 | elm_toolbar_shrink_mode_set(tb, ELM_TOOLBAR_SHRINK_SCROLL); | ||
1258 | |||
1259 | // Menu. | ||
1260 | tb_it = elm_toolbar_item_append(tb, NULL, "Menu", NULL, NULL); | ||
1261 | elm_toolbar_item_menu_set(tb_it, EINA_TRUE); | ||
1262 | // Priority is for when toolbar items are set to hide or menu when there are too many of them. They get hidden or put on the menu based on priority. | ||
1263 | elm_toolbar_item_priority_set(tb_it, 9999); | ||
1264 | elm_toolbar_menu_parent_set(tb, win); | ||
1265 | menu = elm_toolbar_item_menu_get(tb_it); | ||
1266 | |||
1267 | menu_it = elm_menu_item_add(menu, NULL, NULL, "edit", NULL, NULL); | ||
1268 | elm_menu_item_add(menu, menu_it, NULL, "preferences", NULL, NULL); | ||
1269 | menu_it = elm_menu_item_add(menu, NULL, NULL, "help", NULL, NULL); | ||
1270 | elm_menu_item_add(menu, menu_it, NULL, "about woMan", NULL, NULL); | ||
1271 | elm_menu_item_separator_add(menu, NULL); | ||
1272 | menu_it = elm_menu_item_add(menu, NULL, NULL, "advanced", NULL, NULL); | ||
1273 | elm_menu_item_add(menu, menu_it, NULL, "debug settings", NULL, NULL); | ||
1274 | |||
1275 | // The toolbar needs to be packed into the box AFTER the menus are added. | ||
1276 | elm_box_pack_end(bx, tb); | ||
1277 | evas_object_show(tb); | ||
1278 | |||
1279 | gridList = elm_genlist_add(win); | ||
1280 | grids = eina_hash_stringshared_new(free); | ||
1281 | |||
1282 | grid_gic = elm_genlist_item_class_new(); | ||
1283 | grid_gic->item_style = "double_label"; | ||
1284 | grid_gic->func.text_get = _grid_label_get; | ||
1285 | grid_gic->func.content_get = _grid_content_get; | ||
1286 | grid_gic->func.state_get = NULL; | ||
1287 | grid_gic->func.del = NULL; | ||
1288 | for (i = 0; NULL != gridTest[i][0]; i++) | ||
1289 | { | ||
1290 | ezGrid *thisGrid = calloc(1, sizeof(ezGrid)); | ||
1291 | |||
1292 | if (thisGrid) | ||
1293 | { | ||
1294 | eina_clist_init(&(thisGrid->accounts)); | ||
1295 | eina_clist_init(&(thisGrid->landmarks)); | ||
1296 | thisGrid->name = gridTest[i][0]; | ||
1297 | thisGrid->loginURI = gridTest[i][1]; | ||
1298 | thisGrid->splashPage = gridTest[i][2]; | ||
1299 | thisGrid->icon = "folder"; | ||
1300 | thisGrid->gld = gld; | ||
1301 | thisGrid->item = elm_genlist_item_append(gridList, grid_gic, thisGrid, NULL, ELM_GENLIST_ITEM_TREE, _grid_sel_cb, thisGrid); | ||
1302 | eina_hash_add(grids, thisGrid->name, thisGrid); | ||
1303 | } | ||
1304 | } | ||
1305 | |||
1306 | account_gic = elm_genlist_item_class_new(); | ||
1307 | account_gic->item_style = "default"; | ||
1308 | account_gic->func.text_get = _account_label_get; | ||
1309 | account_gic->func.content_get = _account_content_get; | ||
1310 | account_gic->func.state_get = NULL; | ||
1311 | account_gic->func.del = NULL; | ||
1312 | for (i = 0; NULL != accountTest[i][0]; i++) | ||
1313 | { | ||
1314 | ezAccount *thisAccount = calloc(1, sizeof(ezAccount)); | ||
1315 | ezGrid *grid = eina_hash_find(grids, accountTest[i][0]); | ||
1316 | |||
1317 | if (thisAccount && grid) | ||
1318 | { | ||
1319 | thisAccount->name = accountTest[i][1]; | ||
1320 | thisAccount->password = accountTest[i][2]; | ||
1321 | thisAccount->icon = "file"; | ||
1322 | elm_genlist_item_append(gridList, account_gic, thisAccount, grid->item, ELM_GENLIST_ITEM_NONE, NULL, NULL); | ||
1323 | eina_clist_add_tail(&(grid->accounts), &(thisAccount->grid)); | ||
1324 | } | ||
1325 | } | ||
1326 | |||
1327 | // Viewers stuff | ||
1328 | viewerList = elm_genlist_add(win); | ||
1329 | viewer_gic = elm_genlist_item_class_new(); | ||
1330 | viewer_gic->item_style = "double_label"; | ||
1331 | viewer_gic->func.text_get = _viewer_label_get; | ||
1332 | viewer_gic->func.content_get = _viewer_content_get; | ||
1333 | viewer_gic->func.state_get = NULL; | ||
1334 | viewer_gic->func.del = NULL; | ||
1335 | for (i = 0; NULL != viewerTest[i][0]; i++) | ||
1336 | { | ||
1337 | ezViewer *thisViewer = calloc(1, sizeof(ezViewer)); | ||
1338 | |||
1339 | if (thisViewer) | ||
1340 | { | ||
1341 | thisViewer->name = viewerTest[i][0]; | ||
1342 | thisViewer->version = viewerTest[i][1]; | ||
1343 | thisViewer->path = viewerTest[i][2]; | ||
1344 | thisViewer->icon = "file"; | ||
1345 | thisViewer->item = elm_genlist_item_append(viewerList, viewer_gic, thisViewer, NULL, ELM_GENLIST_ITEM_NONE, NULL, NULL); | ||
1346 | } | ||
1347 | } | ||
1348 | |||
1349 | // Toolbar pages | ||
1350 | nf = elm_naviframe_add(win); | ||
1351 | evas_object_size_hint_weight_set(nf, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); | ||
1352 | evas_object_size_hint_align_set(nf, EVAS_HINT_FILL, EVAS_HINT_FILL); | ||
1353 | evas_object_show(nf); | ||
1354 | |||
1355 | tab = viewerList; tab_it = elm_naviframe_item_push(nf, NULL, NULL, NULL, tab, NULL); elm_naviframe_item_title_enabled_set(tab_it, EINA_FALSE, EINA_TRUE); elm_toolbar_item_append(tb, NULL, "Viewers", _promote, tab_it); | ||
1356 | tab = _content_image_new(win, img3); tab_it = elm_naviframe_item_push(nf, NULL, NULL, NULL, tab, NULL); elm_naviframe_item_title_enabled_set(tab_it, EINA_FALSE, EINA_TRUE); elm_toolbar_item_append(tb, NULL, "Landmarks", _promote, tab_it); | ||
1357 | tab = gridList; tab_it = elm_naviframe_item_push(nf, NULL, NULL, NULL, tab, NULL); elm_naviframe_item_title_enabled_set(tab_it, EINA_FALSE, EINA_TRUE); elm_toolbar_item_append(tb, NULL, "Grids", _promote, tab_it); | ||
1358 | elm_box_pack_end(bx, nf); | ||
1359 | |||
1360 | #if USE_EO | ||
1361 | // Not ready for prime time yet, or I'm missing a step. Causes it to hang after closing the window. | ||
1362 | // Slightly better now, it bitches instead of hanging. | ||
1363 | bt = eo_add(ELM_OBJ_BUTTON_CLASS, win); | ||
1364 | elm_object_text_set(bt, "Login"); // No eo interface for this that I can find. | ||
1365 | eo_do(bt, | ||
1366 | // evas_obj_text_set("Login"), | ||
1367 | evas_obj_size_hint_align_set(EVAS_HINT_FILL, EVAS_HINT_FILL), | ||
1368 | evas_obj_size_hint_weight_set(EVAS_HINT_EXPAND, 0.0), | ||
1369 | evas_obj_visibility_set(EINA_TRUE) | ||
1370 | ); | ||
1371 | #else | ||
1372 | bt = elm_button_add(win); | ||
1373 | elm_object_text_set(bt, "Login"); | ||
1374 | evas_object_size_hint_align_set(bt, EVAS_HINT_FILL, EVAS_HINT_FILL); | ||
1375 | evas_object_size_hint_weight_set(bt, EVAS_HINT_EXPAND, 0.0); | ||
1376 | evas_object_show(bt); | ||
1377 | #endif | ||
1378 | // evas_object_smart_callback_add(bt, "clicked", NULL, NULL); | ||
1379 | elm_box_pack_end(bx, bt); | ||
1380 | evas_object_show(bx); | ||
1381 | |||
1382 | fang_win_complete(gld, win, 30, 30, gld->win_w / 3, gld->win_h / 3); | ||
1383 | } | ||
1384 | |||
1385 | EAPI_MAIN int elm_main(int argc, char **argv) | ||
1386 | { | ||
1387 | // Evas_Object *bg, *menu, *bt, *tb; | ||
1388 | Evas_Object *menu, *tb; | ||
1389 | Elm_Object_Item *tb_it; | ||
1390 | //, *menu_it; | ||
1391 | EPhysics_Body *boundary; | ||
1392 | EPhysics_World *world; | ||
1393 | EPhysics_Body *box_body1, *box_body2; | ||
1394 | Evas_Object *box1, *box2; | ||
1395 | GLData *gld = NULL; | ||
1396 | // char buf[PATH_MAX]; | ||
1397 | // int i; | ||
1398 | // Eina_Bool gotWebKit = elm_need_web(); // Initialise ewebkit if it exists, or return EINA_FALSE if it don't. | ||
1399 | |||
1400 | _log_domain = eina_log_domain_register("extantz", NULL); | ||
1401 | // Don't do this, we need to clean up other stuff to, so set a clean up function below. | ||
1402 | //elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED); | ||
1403 | |||
1404 | // If you want efl to handle finding your bin/lib/data dirs, you must do this below. | ||
1405 | elm_app_compile_bin_dir_set(PACKAGE_BIN_DIR); | ||
1406 | elm_app_compile_data_dir_set(PACKAGE_DATA_DIR); | ||
1407 | elm_app_info_set(elm_main, "datadir", "media/sky_03.jpg"); | ||
1408 | fprintf(stdout, "prefix was set to: %s\n", elm_app_prefix_dir_get()); | ||
1409 | fprintf(stdout, "data directory is: %s\n", elm_app_data_dir_get()); | ||
1410 | fprintf(stdout, "library directory is: %s\n", elm_app_lib_dir_get()); | ||
1411 | fprintf(stdout, "locale directory is: %s\n", elm_app_locale_dir_get()); | ||
1412 | |||
1413 | // These are set via the elementary_config tool, which is hard to find. | ||
1414 | elm_config_finger_size_set(0); | ||
1415 | elm_config_scale_set(1.0); | ||
1416 | |||
1417 | // alloc a data struct to hold our relevant gl info in | ||
1418 | if (!(gld = calloc(1, sizeof(GLData)))) return 1; | ||
1419 | gldata_init(gld); | ||
1420 | |||
1421 | // Set the engine to opengl_x11, then open our window. | ||
1422 | if (gld->useEGL) | ||
1423 | elm_config_preferred_engine_set("opengl_x11"); | ||
1424 | gld->win = elm_win_add(NULL, "extantz", ELM_WIN_BASIC); | ||
1425 | // Set preferred engine back to default from config | ||
1426 | elm_config_preferred_engine_set(NULL); | ||
1427 | |||
1428 | #if USE_PHYSICS | ||
1429 | if (!ephysics_init()) | ||
1430 | return 1; | ||
1431 | #endif | ||
1432 | |||
1433 | elm_win_title_set(gld->win, "extantz virtual world manager"); | ||
1434 | evas_object_smart_callback_add(gld->win, "delete,request", _on_done, gld); | ||
1435 | |||
1436 | // Get the screen size. | ||
1437 | elm_win_screen_size_get(gld->win, &gld->win_x, &gld->win_y, &gld->scr_w, &gld->scr_h); | ||
1438 | gld->win_x = gld->win_x + (gld->scr_w / 3); | ||
1439 | gld->win_w = gld->scr_w / 2; | ||
1440 | gld->win_h = gld->scr_h - 30; | ||
1441 | |||
1442 | // Note, we don't need an Elm_bg, the entire thing gets covered with the GL rendering surface anyway. | ||
1443 | #if 0 | ||
1444 | bg = elm_bg_add(gld->win); | ||
1445 | elm_bg_load_size_set(bg, gld->win_w, gld->win_h); | ||
1446 | elm_bg_option_set(bg, ELM_BG_OPTION_CENTER); | ||
1447 | snprintf(buf, sizeof(buf), "%s/media/sky_03.jpg", elm_app_data_dir_get()); | ||
1448 | elm_bg_file_set(bg, buf, NULL); | ||
1449 | evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); | ||
1450 | elm_win_resize_object_add(gld->win, bg); | ||
1451 | evas_object_show(bg); | ||
1452 | #endif | ||
1453 | |||
1454 | gld->bx = elm_box_add(gld->win); | ||
1455 | evas_object_size_hint_weight_set(gld->bx, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); | ||
1456 | evas_object_size_hint_align_set(gld->bx, EVAS_HINT_FILL, EVAS_HINT_FILL); | ||
1457 | elm_win_resize_object_add(gld->win, gld->bx); | ||
1458 | evas_object_show(gld->bx); | ||
1459 | |||
1460 | overlay_add(gld); | ||
1461 | woMan_add(gld); | ||
1462 | chat_add(gld); | ||
1463 | |||
1464 | // Gotta do this after adding the windows, otherwise the menu renders under the window. | ||
1465 | // This sucks, gotta redefine this menu each time we create a new window? | ||
1466 | // Also, GL focus gets lost when any menu is used. sigh | ||
1467 | |||
1468 | // A toolbar thingy. | ||
1469 | tb = elm_toolbar_add(gld->win); | ||
1470 | evas_object_size_hint_weight_set(tb, EVAS_HINT_EXPAND, 0.0); | ||
1471 | evas_object_size_hint_align_set(tb, EVAS_HINT_FILL, EVAS_HINT_FILL); | ||
1472 | elm_toolbar_shrink_mode_set(tb, ELM_TOOLBAR_SHRINK_SCROLL); | ||
1473 | elm_toolbar_align_set(tb, 0.0); | ||
1474 | |||
1475 | // Menus. | ||
1476 | menu = _toolbar_menu_add(gld->win, tb, "file"); | ||
1477 | elm_menu_item_add(menu, NULL, NULL, "quit", _on_done, gld); | ||
1478 | |||
1479 | menu = _toolbar_menu_add(gld->win, tb, "edit"); | ||
1480 | elm_menu_item_add(menu, NULL, NULL, "preferences", NULL, NULL); | ||
1481 | |||
1482 | menu = _toolbar_menu_add(gld->win, tb, "view"); | ||
1483 | menu = _toolbar_menu_add(gld->win, tb, "world"); | ||
1484 | menu = _toolbar_menu_add(gld->win, tb, "tools"); | ||
1485 | |||
1486 | menu = _toolbar_menu_add(gld->win, tb, "help"); | ||
1487 | elm_menu_item_add(menu, NULL, NULL, "grid help", NULL, NULL); | ||
1488 | elm_menu_item_separator_add(menu, NULL); | ||
1489 | elm_menu_item_add(menu, NULL, NULL, "extantz blogs", NULL, NULL); | ||
1490 | elm_menu_item_add(menu, NULL, NULL, "extantz forum", NULL, NULL); | ||
1491 | elm_menu_item_separator_add(menu, NULL); | ||
1492 | elm_menu_item_add(menu, NULL, NULL, "about extantz", NULL, NULL); | ||
1493 | |||
1494 | menu = _toolbar_menu_add(gld->win, tb, "advanced"); | ||
1495 | elm_menu_item_add(menu, NULL, NULL, "debug settings", NULL, NULL); | ||
1496 | |||
1497 | menu = _toolbar_menu_add(gld->win, tb, "god"); | ||
1498 | |||
1499 | // Other stuff in the toolbar. | ||
1500 | tb_it = elm_toolbar_item_append(tb, NULL, NULL, NULL, NULL); | ||
1501 | elm_toolbar_item_separator_set(tb_it, EINA_TRUE); | ||
1502 | tb_it = elm_toolbar_item_append(tb, NULL, "restriction icons", NULL, NULL); | ||
1503 | tb_it = elm_toolbar_item_append(tb, NULL, NULL, NULL, NULL); | ||
1504 | elm_toolbar_item_separator_set(tb_it, EINA_TRUE); | ||
1505 | tb_it = elm_toolbar_item_append(tb, NULL, "hop://localhost/Anarchadia 152, 155, 51 - Lost plot (Adult)", NULL, NULL); | ||
1506 | tb_it = elm_toolbar_item_append(tb, NULL, NULL, NULL, NULL); | ||
1507 | elm_toolbar_item_separator_set(tb_it, EINA_TRUE); | ||
1508 | tb_it = elm_toolbar_item_append(tb, NULL, "date time:o'clock", NULL, NULL); | ||
1509 | |||
1510 | // The toolbar needs to be packed into the box AFTER the menus are added. | ||
1511 | evas_object_show(tb); | ||
1512 | elm_box_pack_start(gld->bx, tb); | ||
1513 | |||
1514 | // This does elm_box_pack_end(), so needs to be after the others. | ||
1515 | init_evas_gl(gld); | ||
1516 | |||
1517 | evas_object_show(gld->bx); | ||
1518 | |||
1519 | #if USE_PHYSICS | ||
1520 | // ePhysics stuff. | ||
1521 | world = ephysics_world_new(); | ||
1522 | ephysics_world_render_geometry_set(world, 0, 0, -50, gld->win_w, gld->win_h, 100); | ||
1523 | |||
1524 | boundary = ephysics_body_bottom_boundary_add(world); | ||
1525 | ephysics_body_restitution_set(boundary, 1); | ||
1526 | ephysics_body_friction_set(boundary, 0); | ||
1527 | |||
1528 | boundary = ephysics_body_top_boundary_add(world); | ||
1529 | ephysics_body_restitution_set(boundary, 1); | ||
1530 | ephysics_body_friction_set(boundary, 0); | ||
1531 | |||
1532 | boundary = ephysics_body_left_boundary_add(world); | ||
1533 | ephysics_body_restitution_set(boundary, 1); | ||
1534 | ephysics_body_friction_set(boundary, 0); | ||
1535 | |||
1536 | boundary = ephysics_body_right_boundary_add(world); | ||
1537 | ephysics_body_restitution_set(boundary, 1); | ||
1538 | ephysics_body_friction_set(boundary, 0); | ||
1539 | |||
1540 | box1 = elm_image_add(gld->win); | ||
1541 | elm_image_file_set(box1, PACKAGE_DATA_DIR "/media/" EPHYSICS_TEST_THEME ".edj", "blue-cube"); | ||
1542 | evas_object_move(box1, gld->win_w / 2 - 80, gld->win_h - 200); | ||
1543 | evas_object_resize(box1, 70, 70); | ||
1544 | evas_object_show(box1); | ||
1545 | |||
1546 | box_body1 = ephysics_body_box_add(world); | ||
1547 | ephysics_body_evas_object_set(box_body1, box1, EINA_TRUE); | ||
1548 | ephysics_body_restitution_set(box_body1, 0.7); | ||
1549 | ephysics_body_friction_set(box_body1, 0); | ||
1550 | ephysics_body_linear_velocity_set(box_body1, -150, 200, 0); | ||
1551 | ephysics_body_angular_velocity_set(box_body1, 0, 0, 36); | ||
1552 | ephysics_body_sleeping_threshold_set(box_body1, 0.1, 0.1); | ||
1553 | |||
1554 | box2 = elm_image_add(gld->win); | ||
1555 | elm_image_file_set(box2, PACKAGE_DATA_DIR "/media/" EPHYSICS_TEST_THEME ".edj", "purple-cube"); | ||
1556 | evas_object_move(box2, gld->win_w / 2 + 10, gld->win_h - 200); | ||
1557 | evas_object_resize(box2, 70, 70); | ||
1558 | evas_object_show(box2); | ||
1559 | |||
1560 | box_body2 = ephysics_body_box_add(world); | ||
1561 | ephysics_body_evas_object_set(box_body2, box2, EINA_TRUE); | ||
1562 | ephysics_body_restitution_set(box_body2, 0.7); | ||
1563 | ephysics_body_friction_set(box_body2, 0); | ||
1564 | ephysics_body_linear_velocity_set(box_body2, 80, -60, 0); | ||
1565 | ephysics_body_angular_velocity_set(box_body2, 0, 0, 360); | ||
1566 | ephysics_body_sleeping_threshold_set(box_body2, 0.1, 0.1); | ||
1567 | |||
1568 | ephysics_world_gravity_set(world, 0, 0, 0); | ||
1569 | #endif | ||
1570 | |||
1571 | evas_object_move(gld->win, gld->win_x, gld->win_y); | ||
1572 | evas_object_resize(gld->win, gld->win_w, gld->win_h); | ||
1573 | evas_object_show(gld->win); | ||
1574 | |||
1575 | _resize_winwin(gld); | ||
1576 | |||
1577 | elm_run(); | ||
1578 | |||
1579 | #if USE_PHYSICS | ||
1580 | ephysics_world_del(world); | ||
1581 | ephysics_shutdown(); | ||
1582 | #endif | ||
1583 | |||
1584 | elm_shutdown(); | ||
1585 | |||
1586 | return 0; | ||
1587 | } | ||
1588 | ELM_MAIN() | ||
diff --git a/src/extantz/extantz.edc b/src/extantz/extantz.edc new file mode 100644 index 0000000..b4ed0b3 --- /dev/null +++ b/src/extantz/extantz.edc | |||
@@ -0,0 +1,30 @@ | |||
1 | externals { | ||
2 | external: "elm"; | ||
3 | } | ||
4 | |||
5 | collections { | ||
6 | #define ADD_CUBE(_group, _file) \ | ||
7 | images { \ | ||
8 | image: #_file##".png" COMP; \ | ||
9 | } \ | ||
10 | group { \ | ||
11 | name: #_group; \ | ||
12 | parts { \ | ||
13 | part { \ | ||
14 | name: "cube"; \ | ||
15 | type: IMAGE; \ | ||
16 | mouse_events: 1; \ | ||
17 | repeat_events: 0; \ | ||
18 | description { \ | ||
19 | state: "default" 0.0; \ | ||
20 | image.normal: #_file##".png"; \ | ||
21 | } \ | ||
22 | } \ | ||
23 | } \ | ||
24 | } | ||
25 | |||
26 | ADD_CUBE(blue-cube, cube-blue); | ||
27 | ADD_CUBE(purple-cube, cube-purple); | ||
28 | |||
29 | #undef ADD_CUBE | ||
30 | } | ||
diff --git a/src/extantz/extantz.h b/src/extantz/extantz.h new file mode 100644 index 0000000..afb94af --- /dev/null +++ b/src/extantz/extantz.h | |||
@@ -0,0 +1,215 @@ | |||
1 | #define USE_EO 0 | ||
2 | #define USE_PHYSICS 1 | ||
3 | #define USE_EGL 1 // If using Evas_GL, though it might be via Elm. | ||
4 | #define USE_ELM_GL 1 | ||
5 | #define USE_IRR 1 | ||
6 | #define USE_DEMO 1 | ||
7 | #define DO_GEARS 0 | ||
8 | |||
9 | #if USE_EO | ||
10 | /* Enable access to unstable EFL API that are still in beta */ | ||
11 | #define EFL_BETA_API_SUPPORT 1 | ||
12 | /* Enable access to unstable EFL EO API. */ | ||
13 | #define EFL_EO_API_SUPPORT 1 | ||
14 | #endif | ||
15 | |||
16 | #include <Elementary.h> | ||
17 | #include <elm_widget_glview.h> | ||
18 | #include <Evas_GL.h> | ||
19 | #include <EPhysics.h> | ||
20 | #include "extantzCamera.h" | ||
21 | |||
22 | |||
23 | #ifdef GL_GLES | ||
24 | #include <EGL/egl.h> | ||
25 | #include <EGL/eglext.h> | ||
26 | #else | ||
27 | # include <GL/glext.h> | ||
28 | # include <GL/glx.h> | ||
29 | #endif | ||
30 | |||
31 | |||
32 | #ifdef __cplusplus | ||
33 | /* | ||
34 | In the Irrlicht Engine, everything can be found in the namespace 'irr'. So if | ||
35 | you want to use a class of the engine, you have to write irr:: before the name | ||
36 | of the class. For example to use the IrrlichtDevice write: irr::IrrlichtDevice. | ||
37 | To get rid of the irr:: in front of the name of every class, we tell the | ||
38 | compiler that we use that namespace from now on, and we will not have to write | ||
39 | irr:: anymore. | ||
40 | */ | ||
41 | using namespace irr; | ||
42 | |||
43 | /* | ||
44 | There are 5 sub namespaces in the Irrlicht Engine. Take a look at them, you can | ||
45 | read a detailed description of them in the documentation by clicking on the top | ||
46 | menu item 'Namespace List' or by using this link: | ||
47 | http://irrlicht.sourceforge.net/docu/namespaces.html | ||
48 | Like the irr namespace, we do not want these 5 sub namespaces now, to keep this | ||
49 | example simple. Hence, we tell the compiler again that we do not want always to | ||
50 | write their names. | ||
51 | */ | ||
52 | using namespace core; | ||
53 | using namespace scene; | ||
54 | using namespace video; | ||
55 | |||
56 | extern "C"{ | ||
57 | #else | ||
58 | |||
59 | // Irrlicht stuff. It's C++, so we gotta use incomplete types. | ||
60 | typedef struct IrrlichtDevice IrrlichtDevice; | ||
61 | typedef struct IVideoDriver IVideoDriver; | ||
62 | typedef struct ISceneManager ISceneManager; | ||
63 | typedef struct ICameraSceneNode ICameraSceneNode; | ||
64 | |||
65 | #endif | ||
66 | |||
67 | |||
68 | #define CRI(...) EINA_LOG_DOM_CRIT(_log_domain, _VA_ARGS__) | ||
69 | #define ERR(...) EINA_LOG_DOM_ERR(_log_domain, __VA_ARGS__) | ||
70 | #define WRN(...) EINA_LOG_DOM_WARN(_log_domain, __VA_ARGS__) | ||
71 | #define INF(...) EINA_LOG_DOM_INFO(_log_domain, __VA_ARGS__) | ||
72 | #define DBG(...) EINA_LOG_DOM_DBG(_log_domain, __VA_ARGS__) | ||
73 | |||
74 | extern int _log_domain; | ||
75 | |||
76 | |||
77 | typedef struct _Gear Gear; | ||
78 | typedef struct _GLData GLData; | ||
79 | |||
80 | typedef enum | ||
81 | { | ||
82 | EZP_NONE, | ||
83 | EZP_AURORA, | ||
84 | EZP_OPENSIM, | ||
85 | EZP_SECOND_LIFE, | ||
86 | EZP_SLEDJHAMR, | ||
87 | EZP_TRITIUM | ||
88 | } ezPlatform; | ||
89 | |||
90 | typedef struct | ||
91 | { | ||
92 | char *name; | ||
93 | char *version; // Version string. | ||
94 | char *path; // OS filesystem path to the viewer install. | ||
95 | char *icon; | ||
96 | uint16_t tag; // The UUID of the texture used in the avatar bake hack. | ||
97 | uint8_t r, g, b; // Colour used for the in world tag. | ||
98 | Elm_Object_Item *item; | ||
99 | } ezViewer; | ||
100 | |||
101 | typedef struct | ||
102 | { | ||
103 | Eina_Clist accounts; | ||
104 | Eina_Clist landmarks; | ||
105 | char *name; | ||
106 | char *loginURI; | ||
107 | char *splashPage; | ||
108 | char *helperURI; | ||
109 | char *website; | ||
110 | char *supportPage; | ||
111 | char *registerPage; | ||
112 | char *passwordPage; | ||
113 | char *icon; | ||
114 | ezPlatform platform; | ||
115 | ezViewer *viewer; | ||
116 | Elm_Object_Item *item; | ||
117 | GLData *gld; // Just a temporary evil hack to pass gld to _grid_sel_cb(). | ||
118 | } ezGrid; | ||
119 | |||
120 | typedef struct | ||
121 | { | ||
122 | Eina_Clist grid; | ||
123 | char *name; | ||
124 | char *password; // Think we need to pass unencrypted passwords to the viewer. B-( | ||
125 | char *icon; | ||
126 | ezViewer *viewer; | ||
127 | } ezAccount; | ||
128 | |||
129 | typedef struct | ||
130 | { | ||
131 | Eina_Clist grid; | ||
132 | char *name; | ||
133 | char *sim; | ||
134 | char *screenshot; | ||
135 | short x, y, z; | ||
136 | } ezLandmark; | ||
137 | |||
138 | |||
139 | |||
140 | struct _Gear | ||
141 | { | ||
142 | GLfloat *vertices; | ||
143 | GLuint vbo; | ||
144 | int count; | ||
145 | }; | ||
146 | |||
147 | // GL related data here. | ||
148 | struct _GLData | ||
149 | { | ||
150 | Evas_Object *win, *winwin; | ||
151 | |||
152 | Ecore_Evas *ee; | ||
153 | Evas *canvas; | ||
154 | Evas_Native_Surface ns; | ||
155 | |||
156 | Evas_GL_Context *ctx; | ||
157 | Evas_GL_Surface *sfc; | ||
158 | Evas_GL_Config *cfg; | ||
159 | Evas_GL *evasGl; // The Evas way. | ||
160 | Evas_Object *elmGl; // The Elm way. | ||
161 | Evas_GL_API *glApi; | ||
162 | |||
163 | GLuint program; | ||
164 | GLuint vtx_shader; | ||
165 | GLuint fgmt_shader; | ||
166 | int scr_w, scr_h; // The size of the screen. | ||
167 | int win_w, win_h; // The size of the window. | ||
168 | int win_x, win_y; // The position of the window. | ||
169 | int sfc_w, sfc_h; // This is what Irrlicht is using, size of the GL image surface / glview. | ||
170 | int img_w, img_h; // Size of the viewport. DON'T reuse sfc_* here. Despite the fach that sfc_* is only used in the init when Irricht is disabled? WTF? | ||
171 | int useEGL : 1; | ||
172 | int useIrr : 1; | ||
173 | int doneIrr : 1; | ||
174 | int gearsInited : 1; | ||
175 | int resized : 1; | ||
176 | |||
177 | Evas_Object *bx, *r1; | ||
178 | Ecore_Animator *animator; | ||
179 | |||
180 | IrrlichtDevice *device; | ||
181 | IVideoDriver *driver; | ||
182 | ISceneManager *smgr; | ||
183 | ICameraSceneNode *camera; | ||
184 | |||
185 | cameraMove *move; | ||
186 | |||
187 | // Gear Stuff | ||
188 | GLfloat view_rotx; | ||
189 | GLfloat view_roty; | ||
190 | GLfloat view_rotz; | ||
191 | |||
192 | Gear *gear1; | ||
193 | Gear *gear2; | ||
194 | Gear *gear3; | ||
195 | |||
196 | GLfloat angle; | ||
197 | |||
198 | GLuint proj_location; | ||
199 | GLuint light_location; | ||
200 | GLuint color_location; | ||
201 | |||
202 | GLfloat proj[16]; | ||
203 | GLfloat light[3]; | ||
204 | }; | ||
205 | |||
206 | |||
207 | EAPI int startIrr(GLData *gld); | ||
208 | EAPI void drawIrr_start(GLData *gld); | ||
209 | EAPI void drawIrr_end(GLData *gld); | ||
210 | EAPI void finishIrr(GLData *gld); | ||
211 | |||
212 | #ifdef __cplusplus | ||
213 | } | ||
214 | #endif | ||
215 | |||
diff --git a/src/extantz/extantzCamera.cpp b/src/extantz/extantzCamera.cpp new file mode 100644 index 0000000..6a7d36a --- /dev/null +++ b/src/extantz/extantzCamera.cpp | |||
@@ -0,0 +1,282 @@ | |||
1 | // Copyright (C) 2002-2012 Nikolaus Gebhardt | ||
2 | // This file is part of the "Irrlicht Engine". | ||
3 | // For conditions of distribution and use, see copyright notice in irrlicht.h | ||
4 | |||
5 | // The above is the copyright notice for CSceneNodeAnimatorCameraFPS.cpp, | ||
6 | // According to the Irrlicht docs, that's just a demo and you are supposed to use it as an example for writing your own FPS style camera. | ||
7 | // I'll be writing my own camera code, that includes first person, third person, and free camera styles. | ||
8 | // I'll start with CSceneNodeAnimatorCameraFPS.cpp and morph it until it suits me. | ||
9 | // As such, I expect lots of Nikolaus Gebhardt's code to go away. | ||
10 | // To be replaced by my code, which will be copyright and licensed under the same license as the rest of extantz. | ||
11 | |||
12 | // Initally I'll make it SecondLife like, coz that's what my muscle memory is used to. | ||
13 | // It will get extended and made generic though. | ||
14 | |||
15 | #include "extantzCamera.h" | ||
16 | #include "IVideoDriver.h" | ||
17 | #include "ISceneManager.h" | ||
18 | #include "Keycodes.h" | ||
19 | #include "ICursorControl.h" | ||
20 | #include "ICameraSceneNode.h" | ||
21 | #include "ISceneNodeAnimatorCollisionResponse.h" | ||
22 | |||
23 | namespace irr | ||
24 | { | ||
25 | namespace scene | ||
26 | { | ||
27 | |||
28 | // Irrlicht hard codes a reference to the original FPS camera code inside it's scene manager. This is that code extracted so we can be more flexible. | ||
29 | // TODO - Hmmm, Where's CursorControl come from? Ah, passed to the scene manager constructor, it's a GUI thing that we need to replace with an EFL thing. But only for mouselook mode. | ||
30 | ICameraSceneNode *addExtantzCamera(ISceneManager* sm, ISceneNode* parent, s32 id) | ||
31 | { | ||
32 | ICameraSceneNode* node = sm->addCameraSceneNode(parent, core::vector3df(), core::vector3df(0, 0, 100), id, true); | ||
33 | if (node) | ||
34 | { | ||
35 | // ISceneNodeAnimator* anm = new extantzCamera(CursorControl); | ||
36 | ISceneNodeAnimator* anm = new extantzCamera(); | ||
37 | |||
38 | // Bind the node's rotation to its target. This is consistent with 1.4.2 and below. | ||
39 | node->bindTargetAndRotation(true); | ||
40 | node->addAnimator(anm); | ||
41 | anm->drop(); | ||
42 | } | ||
43 | |||
44 | return node; | ||
45 | } | ||
46 | |||
47 | |||
48 | //! constructor | ||
49 | //extantzCamera::extantzCamera(gui::ICursorControl* cursorControl) | ||
50 | // : CursorControl(cursorControl), MaxVerticalAngle(88.0f), MoveSpeed(0.4f), RotateSpeed(100.0f), JumpSpeed(3.0f), | ||
51 | extantzCamera::extantzCamera() | ||
52 | : MaxVerticalAngle(88.0f), MouseYDirection(1.0f), LastAnimationTime(0), NoVerticalMovement(false) | ||
53 | { | ||
54 | #ifdef _DEBUG | ||
55 | setDebugName("extantzCamera"); | ||
56 | #endif | ||
57 | |||
58 | move.jump = 0.0; // Coz otherwise we start in jumping mode. | ||
59 | move.MoveSpeed = 0.1f; | ||
60 | move.RotateSpeed = 1.0f; | ||
61 | move.JumpSpeed = 3.0f; | ||
62 | |||
63 | // if (CursorControl) | ||
64 | // CursorControl->grab(); | ||
65 | } | ||
66 | |||
67 | |||
68 | //! destructor | ||
69 | extantzCamera::~extantzCamera() | ||
70 | { | ||
71 | // if (CursorControl) | ||
72 | // CursorControl->drop(); | ||
73 | } | ||
74 | |||
75 | |||
76 | void extantzCamera::animateNode(ISceneNode* node, u32 timeMs) | ||
77 | { | ||
78 | if (!node || node->getType() != ESNT_CAMERA) | ||
79 | return; | ||
80 | |||
81 | ICameraSceneNode* camera = static_cast<ICameraSceneNode*>(node); | ||
82 | |||
83 | if (0 == LastAnimationTime) | ||
84 | { | ||
85 | camera->updateAbsolutePosition(); | ||
86 | // if (CursorControl ) | ||
87 | // { | ||
88 | // CursorControl->setPosition(0.5f, 0.5f); | ||
89 | // CursorPos = CenterCursor = CursorControl->getRelativePosition(); | ||
90 | // } | ||
91 | |||
92 | LastAnimationTime = timeMs; | ||
93 | } | ||
94 | |||
95 | // If the camera isn't the active camera, and receiving input, then don't process it. | ||
96 | // TODO - it never is, coz we are bypassing that, but can we replace this with something else? | ||
97 | if(!camera->isInputReceiverEnabled()) | ||
98 | { | ||
99 | // return; | ||
100 | } | ||
101 | |||
102 | scene::ISceneManager * smgr = camera->getSceneManager(); | ||
103 | if(smgr && smgr->getActiveCamera() != camera) | ||
104 | return; | ||
105 | |||
106 | // get time | ||
107 | f32 timeDiff = (f32) (timeMs - LastAnimationTime); | ||
108 | LastAnimationTime = timeMs; | ||
109 | |||
110 | // update position | ||
111 | core::vector3df pos = camera->getPosition(); | ||
112 | |||
113 | // Update rotation | ||
114 | core::vector3df target = (camera->getTarget() - camera->getAbsolutePosition()); | ||
115 | core::vector3df relativeRotation = target.getHorizontalAngle(); | ||
116 | |||
117 | #if 0 | ||
118 | if (CursorControl) | ||
119 | { | ||
120 | if (CursorPos != CenterCursor) | ||
121 | { | ||
122 | relativeRotation.Y -= (0.5f - CursorPos.X) * move.RotateSpeed; | ||
123 | relativeRotation.X -= (0.5f - CursorPos.Y) * move.RotateSpeed * MouseYDirection; | ||
124 | |||
125 | // X < MaxVerticalAngle or X > 360-MaxVerticalAngle | ||
126 | |||
127 | if (relativeRotation.X > MaxVerticalAngle*2 && | ||
128 | relativeRotation.X < 360.0f-MaxVerticalAngle) | ||
129 | { | ||
130 | relativeRotation.X = 360.0f-MaxVerticalAngle; | ||
131 | } | ||
132 | else | ||
133 | if (relativeRotation.X > MaxVerticalAngle && | ||
134 | relativeRotation.X < 360.0f-MaxVerticalAngle) | ||
135 | { | ||
136 | relativeRotation.X = MaxVerticalAngle; | ||
137 | } | ||
138 | |||
139 | // Do the fix as normal, special case below | ||
140 | // reset cursor position to the centre of the window. | ||
141 | CursorControl->setPosition(0.5f, 0.5f); | ||
142 | CenterCursor = CursorControl->getRelativePosition(); | ||
143 | |||
144 | // needed to avoid problems when the event receiver is disabled | ||
145 | CursorPos = CenterCursor; | ||
146 | } | ||
147 | |||
148 | // Special case, mouse is whipped outside of window before it can update. | ||
149 | video::IVideoDriver* driver = smgr->getVideoDriver(); | ||
150 | core::vector2d<u32> mousepos(u32(CursorControl->getPosition().X), u32(CursorControl->getPosition().Y)); | ||
151 | core::rect<u32> screenRect(0, 0, driver->getScreenSize().Width, driver->getScreenSize().Height); | ||
152 | |||
153 | // Only if we are moving outside quickly. | ||
154 | bool reset = !screenRect.isPointInside(mousepos); | ||
155 | |||
156 | if(reset) | ||
157 | { | ||
158 | // Force a reset. | ||
159 | CursorControl->setPosition(0.5f, 0.5f); | ||
160 | CenterCursor = CursorControl->getRelativePosition(); | ||
161 | CursorPos = CenterCursor; | ||
162 | } | ||
163 | } | ||
164 | #else | ||
165 | relativeRotation.Y -= move.r * move.RotateSpeed; | ||
166 | relativeRotation.X -= move.s * move.RotateSpeed * MouseYDirection; | ||
167 | |||
168 | // X < MaxVerticalAngle or X > 360-MaxVerticalAngle | ||
169 | |||
170 | if ((relativeRotation.X > (MaxVerticalAngle * 2)) && (relativeRotation.X < (360.0f - MaxVerticalAngle))) | ||
171 | relativeRotation.X = 360.0f - MaxVerticalAngle; | ||
172 | else if ((relativeRotation.X > MaxVerticalAngle) && (relativeRotation.X < (360.0f - MaxVerticalAngle))) | ||
173 | relativeRotation.X = MaxVerticalAngle; | ||
174 | #endif | ||
175 | |||
176 | // set target | ||
177 | |||
178 | target.set(0,0, core::max_(1.f, pos.getLength())); | ||
179 | core::vector3df movedir = target; | ||
180 | |||
181 | core::matrix4 mat; | ||
182 | mat.setRotationDegrees(core::vector3df(relativeRotation.X, relativeRotation.Y, 0)); | ||
183 | mat.transformVect(target); | ||
184 | |||
185 | if (NoVerticalMovement) | ||
186 | { | ||
187 | mat.setRotationDegrees(core::vector3df(0, relativeRotation.Y, 0)); | ||
188 | mat.transformVect(movedir); | ||
189 | } | ||
190 | else | ||
191 | { | ||
192 | movedir = target; | ||
193 | } | ||
194 | |||
195 | movedir.normalize(); | ||
196 | |||
197 | pos += movedir * timeDiff * move.MoveSpeed * move.x; | ||
198 | |||
199 | // strafing | ||
200 | core::vector3df strafevect = target; | ||
201 | strafevect = strafevect.crossProduct(camera->getUpVector()); | ||
202 | |||
203 | if (NoVerticalMovement) | ||
204 | strafevect.Y = 0.0f; | ||
205 | |||
206 | strafevect.normalize(); | ||
207 | |||
208 | pos += strafevect * timeDiff * move.MoveSpeed * move.y; | ||
209 | |||
210 | // For jumping, we find the collision response animator attached to our camera | ||
211 | // and if it's not falling, we tell it to jump. | ||
212 | if (0.0 < move.jump) | ||
213 | { | ||
214 | const ISceneNodeAnimatorList& animators = camera->getAnimators(); | ||
215 | ISceneNodeAnimatorList::ConstIterator it = animators.begin(); | ||
216 | while(it != animators.end()) | ||
217 | { | ||
218 | if(ESNAT_COLLISION_RESPONSE == (*it)->getType()) | ||
219 | { | ||
220 | ISceneNodeAnimatorCollisionResponse * collisionResponse = | ||
221 | static_cast<ISceneNodeAnimatorCollisionResponse *>(*it); | ||
222 | |||
223 | if(!collisionResponse->isFalling()) | ||
224 | collisionResponse->jump(move.JumpSpeed); | ||
225 | } | ||
226 | |||
227 | it++; | ||
228 | } | ||
229 | } | ||
230 | |||
231 | // write translation | ||
232 | camera->setPosition(pos); | ||
233 | |||
234 | // write right target | ||
235 | target += pos; | ||
236 | camera->setTarget(target); | ||
237 | } | ||
238 | |||
239 | |||
240 | ISceneNodeAnimator* extantzCamera::createClone(ISceneNode* node, ISceneManager* newManager) | ||
241 | { | ||
242 | // extantzCamera *newAnimator = new extantzCamera(CursorControl); | ||
243 | extantzCamera *newAnimator = new extantzCamera(); | ||
244 | return newAnimator; | ||
245 | } | ||
246 | |||
247 | #ifdef __cplusplus | ||
248 | extern "C" { | ||
249 | #endif | ||
250 | |||
251 | cameraMove *getCameraMove(ICameraSceneNode *camera) | ||
252 | { | ||
253 | cameraMove *cm = NULL; | ||
254 | |||
255 | if (camera) | ||
256 | { | ||
257 | const ISceneNodeAnimatorList &animators = camera->getAnimators(); | ||
258 | ISceneNodeAnimatorList::ConstIterator it = animators.begin(); | ||
259 | while(it != animators.end()) | ||
260 | { | ||
261 | // TODO - We assume FPS == extantzCamera, coz Irrlicht hard codes the camera types in an enum, which is a pain to add to from outside. | ||
262 | if(ESNAT_CAMERA_FPS == (*it)->getType()) | ||
263 | { | ||
264 | extantzCamera *ec = static_cast<extantzCamera *>(*it); | ||
265 | |||
266 | cm = &(ec->move); | ||
267 | } | ||
268 | |||
269 | it++; | ||
270 | } | ||
271 | } | ||
272 | return cm; | ||
273 | } | ||
274 | |||
275 | #ifdef __cplusplus | ||
276 | } | ||
277 | #endif | ||
278 | |||
279 | |||
280 | } // namespace scene | ||
281 | } // namespace irr | ||
282 | |||
diff --git a/src/extantz/extantzCamera.h b/src/extantz/extantzCamera.h new file mode 100644 index 0000000..9d74236 --- /dev/null +++ b/src/extantz/extantzCamera.h | |||
@@ -0,0 +1,101 @@ | |||
1 | // Copyright (C) 2002-2012 Nikolaus Gebhardt | ||
2 | // This file is part of the "Irrlicht Engine". | ||
3 | // For conditions of distribution and use, see copyright notice in irrlicht.h | ||
4 | |||
5 | #ifndef __EXTANTZ_CAMERA_H_INCLUDED__ | ||
6 | #define __EXTANTZ_CAMERA_H_INCLUDED__ | ||
7 | |||
8 | |||
9 | #ifdef __cplusplus | ||
10 | #include <ISceneNodeAnimator.h> | ||
11 | #include <vector2d.h> | ||
12 | #include <position2d.h> | ||
13 | #include <SKeyMap.h> | ||
14 | #include <irrArray.h> | ||
15 | #include <ICameraSceneNode.h> | ||
16 | |||
17 | using namespace irr; | ||
18 | using namespace scene; | ||
19 | |||
20 | extern "C"{ | ||
21 | #else | ||
22 | typedef struct extantzCamera extantzCamera; | ||
23 | typedef struct ICameraSceneNode ICameraSceneNode; | ||
24 | #endif | ||
25 | |||
26 | typedef struct | ||
27 | { | ||
28 | float x, y, z; | ||
29 | float r, s, t; | ||
30 | float jump; | ||
31 | float JumpSpeed, RotateSpeed, MoveSpeed; | ||
32 | } cameraMove; | ||
33 | |||
34 | cameraMove *getCameraMove(ICameraSceneNode *camera); | ||
35 | |||
36 | #ifdef __cplusplus | ||
37 | } | ||
38 | |||
39 | |||
40 | //namespace irr::gui | ||
41 | //{ | ||
42 | // class ICursorControl; | ||
43 | //} | ||
44 | |||
45 | |||
46 | namespace irr | ||
47 | { | ||
48 | namespace scene | ||
49 | { | ||
50 | ICameraSceneNode *addExtantzCamera(ISceneManager* sm, ISceneNode* parent, s32 id); | ||
51 | |||
52 | class extantzCamera : public ISceneNodeAnimator | ||
53 | { | ||
54 | public: | ||
55 | |||
56 | //! Constructor | ||
57 | // extantzCamera(gui::ICursorControl* cursorControl); | ||
58 | extantzCamera(); | ||
59 | |||
60 | //! Destructor | ||
61 | virtual ~extantzCamera(); | ||
62 | |||
63 | //! Animates the scene node, currently only works on cameras | ||
64 | virtual void animateNode(ISceneNode* node, u32 timeMs); | ||
65 | |||
66 | //! This animator will receive events when attached to the active camera | ||
67 | virtual bool isEventReceiverEnabled() const | ||
68 | { | ||
69 | return false; | ||
70 | } | ||
71 | |||
72 | //! Returns the type of this animator | ||
73 | virtual ESCENE_NODE_ANIMATOR_TYPE getType() const | ||
74 | { | ||
75 | return ESNAT_CAMERA_FPS; | ||
76 | } | ||
77 | |||
78 | //! Creates a clone of this animator. | ||
79 | /** Please note that you will have to drop | ||
80 | (IReferenceCounted::drop()) the returned pointer once you're | ||
81 | done with it. */ | ||
82 | virtual ISceneNodeAnimator* createClone(ISceneNode* node, ISceneManager* newManager=0); | ||
83 | |||
84 | bool NoVerticalMovement; | ||
85 | // -1.0f for inverted mouse, defaults to 1.0f | ||
86 | f32 MouseYDirection; | ||
87 | |||
88 | cameraMove move; | ||
89 | |||
90 | private: | ||
91 | f32 MaxVerticalAngle; | ||
92 | s32 LastAnimationTime; | ||
93 | // core::position2d<f32> CenterCursor, CursorPos; | ||
94 | }; | ||
95 | }; | ||
96 | }; | ||
97 | #endif | ||
98 | |||
99 | |||
100 | #endif // __EXTANTZ_CAMERA_H_INCLUDED__ | ||
101 | |||