Symphony Of Empires
luavm.cpp
Go to the documentation of this file.
1 // Eng3D - General purpouse game engine
2 // Copyright (C) 2021, Eng3D contributors
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <https://www.gnu.org/licenses/>.
16 //
17 // ----------------------------------------------------------------------------
18 // Name:
19 // luavm.cpp
20 //
21 // Abstract:
22 // Does some important stuff.
23 // ----------------------------------------------------------------------------
24 
25 #include <cstdio>
26 #include <memory>
27 #include <unordered_map>
28 #include <map>
29 
30 #include <lua.hpp>
31 extern "C" {
32 #include <lualib.h>
33 #include <lauxlib.h>
34 }
35 
36 #include "eng3d/state.hpp"
37 #include "eng3d/log.hpp"
38 #include "eng3d/ui/ui.hpp"
39 #include "eng3d/ui/widget.hpp"
40 #include "eng3d/ui/components.hpp"
41 #include "eng3d/luavm.hpp"
42 
44 {
45  this->state = luaL_newstate();
46  luaL_openlibs(this->state);
47 
48  // No translation is done
49  lua_register(this->state, "_", [](lua_State* L) {
50  std::string msgid = luaL_checkstring(L, 1);
51  lua_pushstring(L, msgid.c_str());
52  return 1;
53  });
54  // And for the UI too
55  lua_register(this->state, "ui_new_button", Eng3D::LuaVM::ui_new_button);
56  lua_register(this->state, "ui_new_div", Eng3D::LuaVM::ui_new_div);
57  lua_register(this->state, "ui_new_group", Eng3D::LuaVM::ui_new_group);
58  lua_register(this->state, "ui_new_image", Eng3D::LuaVM::ui_new_image);
59  lua_register(this->state, "ui_new_checkbox", Eng3D::LuaVM::ui_new_checkbox);
60  lua_register(this->state, "ui_set_checkbox_value", Eng3D::LuaVM::ui_set_checkbox_value);
61  lua_register(this->state, "ui_get_checkbox_value", Eng3D::LuaVM::ui_get_checkbox_value);
62  lua_register(this->state, "ui_new_slider", Eng3D::LuaVM::ui_new_slider);
63  lua_register(this->state, "ui_get_slider_value", Eng3D::LuaVM::ui_get_slider_value);
64  lua_register(this->state, "ui_set_slider_value", Eng3D::LuaVM::ui_set_slider_value);
65  lua_register(this->state, "ui_new_label", Eng3D::LuaVM::ui_new_label);
66  lua_register(this->state, "ui_new_window", Eng3D::LuaVM::ui_new_window);
67  lua_register(this->state, "ui_get_image", Eng3D::LuaVM::ui_get_image);
68  lua_register(this->state, "ui_set_image", Eng3D::LuaVM::ui_set_image);
69  lua_register(this->state, "ui_set_scroll", Eng3D::LuaVM::ui_set_scroll);
70  lua_register(this->state, "ui_set_text", Eng3D::LuaVM::ui_set_text);
71  lua_register(this->state, "ui_set_on_click", Eng3D::LuaVM::ui_set_on_click);
72  lua_register(this->state, "ui_set_window_on_click_close_btn", Eng3D::LuaVM::ui_set_window_on_click_close_btn);
73  lua_register(this->state, "ui_get_widget", Eng3D::LuaVM::ui_get_widget);
74  lua_register(this->state, "ui_widget_kill", Eng3D::LuaVM::ui_widget_kill);
75  lua_register(this->state, "ui_widget_set_tooltip", Eng3D::LuaVM::ui_widget_set_tooltip);
76  lua_register(this->state, "UI_RegisterCallback", Eng3D::LuaVM::ui_register_callback);
77  lua_register(this->state, "ui_widget_set_flex", Eng3D::LuaVM::ui_widget_set_flex);
78 }
79 
80 std::map<int, UI::Widget*> lua_widgets;
81 std::map<int, std::shared_ptr<Eng3D::Texture>> lua_textures;
82 std::map<std::string, int> lua_ui_callbacks;
83 
84 // TODO: Make this thread-safe
85 static int id = 1;
86 static inline int get_unique_id() {
87  id++;
88  return id;
89 }
90 
91 int Eng3D::LuaVM::ui_new_button(lua_State* L) {
92  int x = luaL_checkinteger(L, 1); // x
93  int y = luaL_checkinteger(L, 2); // y
94  int w = luaL_checkinteger(L, 3); // w
95  int h = luaL_checkinteger(L, 4); // h
96  int parent_ref = luaL_checkinteger(L, 5); // parent
97  UI::Widget* parent = !parent_ref ? nullptr : lua_widgets[parent_ref];
98 
99  const auto widget_id = get_unique_id();
100  lua_widgets[widget_id] = new UI::Button(x, y, w, h, parent);
101  lua_pushinteger(L, widget_id);
102  return 1;
103 }
104 
105 int Eng3D::LuaVM::ui_new_image(lua_State* L) {
106  int x = luaL_checkinteger(L, 1); // x
107  int y = luaL_checkinteger(L, 2); // y
108  int w = luaL_checkinteger(L, 3); // w
109  int h = luaL_checkinteger(L, 4); // h
110  int parent_ref = luaL_checkinteger(L, 5); // parent
111  UI::Widget* parent = !parent_ref ? nullptr : lua_widgets[parent_ref];
112 
113  const auto widget_id = get_unique_id();
114  lua_widgets[widget_id] = new UI::Image(x, y, w, h, parent);
115  lua_pushinteger(L, widget_id);
116  return 1;
117 }
118 
119 int Eng3D::LuaVM::ui_new_group(lua_State* L) {
120  int x = luaL_checkinteger(L, 1); // x
121  int y = luaL_checkinteger(L, 2); // y
122  int w = luaL_checkinteger(L, 3); // w
123  int h = luaL_checkinteger(L, 4); // h
124  int parent_ref = luaL_checkinteger(L, 5); // parent
125  UI::Widget* parent = !parent_ref ? nullptr : lua_widgets[parent_ref];
126 
127  const auto widget_id = get_unique_id();
128  lua_widgets[widget_id] = new UI::Group(x, y, w, h, parent);
129  lua_pushinteger(L, widget_id);
130  return 1;
131 }
132 
133 int Eng3D::LuaVM::ui_new_div(lua_State* L) {
134  int x = luaL_checkinteger(L, 1); // x
135  int y = luaL_checkinteger(L, 2); // y
136  int w = luaL_checkinteger(L, 3); // w
137  int h = luaL_checkinteger(L, 4); // h
138  int parent_ref = luaL_checkinteger(L, 5); // parent
139  UI::Widget* parent = !parent_ref ? nullptr : lua_widgets[parent_ref];
140 
141  const auto widget_id = get_unique_id();
142  lua_widgets[widget_id] = new UI::Div(x, y, w, h, parent);
143  lua_pushinteger(L, widget_id);
144  return 1;
145 }
146 
147 int Eng3D::LuaVM::ui_new_window(lua_State* L) {
148  int x = luaL_checkinteger(L, 1); // x
149  int y = luaL_checkinteger(L, 2); // y
150  int w = luaL_checkinteger(L, 3); // w
151  int h = luaL_checkinteger(L, 4); // h
152  int parent_ref = luaL_checkinteger(L, 5); // parent
153  UI::Widget* parent = !parent_ref ? nullptr : lua_widgets[parent_ref];
154 
155  const auto widget_id = get_unique_id();
156  lua_widgets[widget_id] = new UI::Window(x, y, w, h, parent);
157  lua_pushinteger(L, widget_id);
158  return 1;
159 }
160 
161 int Eng3D::LuaVM::ui_new_checkbox(lua_State* L) {
162  int x = luaL_checkinteger(L, 1); // x
163  int y = luaL_checkinteger(L, 2); // y
164  int w = luaL_checkinteger(L, 3); // w
165  int h = luaL_checkinteger(L, 4); // h
166  int parent_ref = luaL_checkinteger(L, 5); // parent
167  UI::Widget* parent = !parent_ref ? nullptr : lua_widgets[parent_ref];
168 
169  const auto widget_id = get_unique_id();
170  lua_widgets[widget_id] = new UI::Checkbox(x, y, w, h, parent);
171  lua_pushinteger(L, widget_id);
172  return 1;
173 }
174 
175 int Eng3D::LuaVM::ui_set_checkbox_value(lua_State* L) {
176  UI::Checkbox* widget = static_cast<UI::Checkbox*>(lua_widgets[luaL_checkinteger(L, 1)]);
177  widget->set_value(lua_toboolean(L, 2));
178  return 0;
179 }
180 
181 int Eng3D::LuaVM::ui_get_checkbox_value(lua_State* L) {
182  UI::Checkbox* widget = static_cast<UI::Checkbox*>(lua_widgets[luaL_checkinteger(L, 1)]);
183  lua_pushboolean(L, widget->get_value());
184  return 1;
185 }
186 
187 int Eng3D::LuaVM::ui_new_slider(lua_State* L) {
188  int x = luaL_checkinteger(L, 1); // x
189  int y = luaL_checkinteger(L, 2); // y
190  int w = luaL_checkinteger(L, 3); // w
191  int h = luaL_checkinteger(L, 4); // h
192  int parent_ref = luaL_checkinteger(L, 5); // parent
193  UI::Widget* parent = !parent_ref ? nullptr : lua_widgets[parent_ref];
194 
195  const auto widget_id = get_unique_id();
196  lua_widgets[widget_id] = new UI::Slider(x, y, w, h, 0.f, 1.f, parent);
197  lua_pushinteger(L, widget_id);
198  return 1;
199 }
200 
201 int Eng3D::LuaVM::ui_get_slider_value(lua_State* L) {
202  UI::Slider* widget = static_cast<UI::Slider*>(lua_widgets[luaL_checkinteger(L, 1)]);
203  lua_pushnumber(L, widget->get_value());
204  return 1;
205 }
206 
207 int Eng3D::LuaVM::ui_set_slider_value(lua_State* L) {
208  UI::Slider* widget = static_cast<UI::Slider*>(lua_widgets[luaL_checkinteger(L, 1)]);
209  widget->set_value(luaL_checknumber(L, 2));
210  return 0;
211 }
212 
213 int Eng3D::LuaVM::ui_new_label(lua_State* L) {
214  int x = luaL_checkinteger(L, 1); // x
215  int y = luaL_checkinteger(L, 2); // y
216  int parent_ref = luaL_checkinteger(L, 3); // parent
217  UI::Widget* parent = !parent_ref ? nullptr : lua_widgets[parent_ref];
218 
219  const auto widget_id = get_unique_id();
220  lua_widgets[widget_id] = new UI::Label(x, y, " ", parent);
221  lua_pushinteger(L, widget_id);
222  return 1;
223 }
224 
225 int Eng3D::LuaVM::ui_set_text(lua_State* L) {
226  UI::Widget* widget = lua_widgets[luaL_checkinteger(L, 1)];
227  widget->set_text(luaL_checkstring(L, 2));
228  return 0;
229 }
230 
231 int Eng3D::LuaVM::ui_get_image(lua_State* L) {
232  const std::string path = luaL_checkstring(L, 1);
233  auto& s = Eng3D::State::get_instance();
234 
235  const auto widget_id = get_unique_id();
236  lua_textures[widget_id] = s.tex_man.load(s.package_man.get_unique(path));
237  lua_pushinteger(L, widget_id);
238  return 1;
239 }
240 
241 int Eng3D::LuaVM::ui_set_image(lua_State* L) {
242  UI::Widget* widget = lua_widgets[luaL_checkinteger(L, 1)];
243  auto tex = lua_textures[luaL_checkinteger(L, 2)];
244  widget->current_texture = tex;
245  return 0;
246 }
247 
248 int Eng3D::LuaVM::ui_set_scroll(lua_State* L) {
249  UI::Widget* widget = lua_widgets[luaL_checkinteger(L, 1)];
250  widget->is_scroll = lua_toboolean(L, 2);
251  return 0;
252 }
253 
254 int Eng3D::LuaVM::ui_set_on_click(lua_State* L) {
255  UI::Widget* widget = lua_widgets[luaL_checkinteger(L, 1)];
256  lua_pushvalue(L, 2); // Obtain closure id
257  widget->lua_on_click = luaL_ref(L, LUA_REGISTRYINDEX);
258  widget->set_on_click([L](UI::Widget& w) { // Special callback for handling this closure
259  lua_getglobal(L, "UI_DriverCallOnClick");
260  lua_rawgeti(L, LUA_REGISTRYINDEX, w.lua_on_click);
261  // Find the widget on the map
262  auto it = std::find_if(lua_widgets.begin(), lua_widgets.end(), [&w](const auto& e) {
263  return e.second == &w;
264  });
265  lua_pushinteger(L, it->first);
266  if(Eng3D::LuaVM::call_func(L, 2, 0)) {
267  const std::string err_msg = lua_tostring(L, -1);
268  Eng3D::Log::error("lua", "lua_pcall failed: " + err_msg);
269  lua_pop(L, 1);
270  CXX_THROW(Eng3D::LuaException, "Failure on UI callback: " + err_msg);
271  }
272  });
273  return 0;
274 }
275 
276 int Eng3D::LuaVM::ui_set_window_on_click_close_btn(lua_State* L) {
277  UI::Window* widget = static_cast<UI::Window*>(lua_widgets[luaL_checkinteger(L, 1)]);
278  lua_pushvalue(L, 2); // Obtain closure id
279  widget->lua_on_close_btn = luaL_ref(L, LUA_REGISTRYINDEX);
280  widget->set_close_btn_function([L](UI::Widget& w) { // Special callback for handling this closure
281  auto& o = (UI::Window&)(*w.parent->parent);
282  lua_getglobal(L, "UI_DriverCallOnClick");
283  lua_rawgeti(L, LUA_REGISTRYINDEX, o.lua_on_close_btn);
284  // Find the widget on the map
285  auto it = std::find_if(lua_widgets.begin(), lua_widgets.end(), [&o](const auto& e) {
286  return e.second == &o;
287  });
288  lua_pushinteger(L, it->first);
289  if(call_func(L, 2, 0)) {
290  const std::string err_msg = lua_tostring(L, -1);
291  Eng3D::Log::error("lua", "lua_pcall failed: " + err_msg);
292  lua_pop(L, 1);
293  CXX_THROW(Eng3D::LuaException, "Failure on UI callback: " + err_msg);
294  }
295  o.kill(); // Implicitly kill object
296  });
297  return 0;
298 }
299 
300 int Eng3D::LuaVM::ui_get_widget(lua_State* L) {
301  UI::Widget* widget = lua_widgets[luaL_checkinteger(L, 1)];
302  lua_pushinteger(L, widget->width);
303  lua_pushinteger(L, widget->height);
304  lua_pushinteger(L, widget->x);
305  lua_pushinteger(L, widget->y);
306  return 4;
307 }
308 
309 int Eng3D::LuaVM::ui_widget_kill(lua_State* L) {
310  const int widget_id = luaL_checkinteger(L, 1);
311  UI::Widget* widget = lua_widgets[widget_id];
312  widget->kill(); // Kill the widget (not now, but later...)
313  lua_widgets.erase(widget_id); // Delete from lua_widgets to avoid dead-pointer referencing
314  return 0;
315 }
316 
317 int Eng3D::LuaVM::ui_widget_set_tooltip(lua_State* L) {
318  UI::Widget* widget = lua_widgets[luaL_checkinteger(L, 1)];
319  widget->set_tooltip(luaL_checkstring(L, 2));
320  return 0;
321 }
322 
323 int Eng3D::LuaVM::ui_register_callback(lua_State* L) {
324  const std::string name = luaL_checkstring(L, 1);
325  lua_pushvalue(L, 2); // Obtain closure id
326  lua_ui_callbacks[name] = luaL_ref(L, LUA_REGISTRYINDEX);
327  // Now the UI callback is registered
328  return 0;
329 }
330 
331 int Eng3D::LuaVM::ui_widget_set_flex(lua_State* L) {
332  UI::Widget* widget = lua_widgets[luaL_checkinteger(L, 1)];
333  int flex_mode = luaL_checkinteger(L, 2);
334  if(flex_mode == 1) {
335  widget->flex = UI::Flex::COLUMN;
336  } else if(flex_mode == 2) {
337  widget->flex = UI::Flex::ROW;
338  } else {
339  widget->flex = UI::Flex::NONE;
340  }
341  return 0;
342 }
343 
344 int Eng3D::LuaVM::call_func(lua_State* L, int nargs, int nret) {
345  /* calculate stack position for message handler */
346  int hpos = lua_gettop(L) - nargs;
347  /* push custom error message handler */
348  lua_pushcfunction(L, [](lua_State* Lua) {
349  lua_getglobal(Lua, "debug");
350  lua_getfield(Lua, -1, "traceback");
351  lua_pushvalue(Lua, 1);
352  lua_pushinteger(Lua, 1);
353  lua_call(Lua, 2, 1);
354  return 1;
355  });
356  /* move it before function and arguments */
357  lua_insert(L, hpos);
358  /* call lua_pcall function with custom handler */
359  int r = lua_pcall(L, nargs, nret, hpos);
360  /* remove custom error message handler from stack */
361  lua_remove(L, hpos);
362  /* pass return value of lua_pcall */
363  return r;
364 }
365 
366 int Eng3D::LuaVM::call_func(int nargs, int nret) {
367  return this->call_func(this->state, nargs, nret);
368 }
369 
376 void Eng3D::LuaVM::invoke_registered_callback(const std::string& name) {
377  lua_rawgeti(this->state, LUA_REGISTRYINDEX, lua_ui_callbacks[name]);
378  if(this->call_func(0, 0)) {
379  const std::string err_msg = lua_tostring(this->state, -1);
380  Eng3D::Log::error("lua", "lua_pcall failed: " + err_msg);
381  lua_pop(this->state, 1);
382  CXX_THROW(Eng3D::LuaException, "Failure on UI callback: " + err_msg);
383  }
384 }
385 
387 {
388  if(this->state != nullptr)
389  lua_close(this->state);
390 }
static State & get_instance()
Definition: state.cpp:514
Button widget.
Definition: button.hpp:32
Grouping to keep widgets together without triggering events.
Definition: group.hpp:44
Image widget, can display pictures or effects on the screen.
Definition: image.hpp:43
Simple widget for drawing text on the screen, no multiline support.
Definition: label.hpp:40
void set_value(const float _value)
float get_value() const
Slider widget.
Definition: slider.hpp:38
The master widget all the other widgets inherit from, do not use directly instead use one of the many...
Definition: widget.hpp:176
virtual void set_on_click(std::function< void(UI::Widget &)> _on_click)
Sets the on_click function of this widget.
Definition: widget.hpp:259
virtual void set_tooltip(UI::Tooltip *tooltip)
Set the tooltip to be shown when this widget is hovered, overrides the previous tooltip.
Definition: widget.cpp:458
bool is_scroll
Definition: widget.hpp:306
size_t width
Definition: widget.hpp:325
std::shared_ptr< Eng3D::Texture > current_texture
Definition: widget.hpp:327
UI::Flex flex
Definition: widget.hpp:337
void kill()
Kills the current widget, setting it up for deletion when dead widgets are cleared by the UI context.
Definition: widget.hpp:283
virtual void set_text(const std::string &text)
Generates text for the widget and overrides the current text texture.
Definition: widget.cpp:445
size_t height
Definition: widget.hpp:325
int lua_on_click
Definition: widget.hpp:359
UI::Widget * parent
Definition: widget.hpp:314
Window widget, this widget is similar to a Group widget, the key difference is that this one can be m...
Definition: window.hpp:39
int lua_on_close_btn
Definition: window.hpp:48
void set_close_btn_function(std::function< void(Widget &)> on_click)
Definition: window.cpp:65
std::map< int, std::shared_ptr< Eng3D::Texture > > lua_textures
Definition: luavm.cpp:81
std::map< int, UI::Widget * > lua_widgets
Definition: luavm.cpp:80
std::map< std::string, int > lua_ui_callbacks
Definition: luavm.cpp:82
void error(const std::string_view category, const std::string_view msg)
Definition: log.cpp:68
#define L
Definition: stb_vorbis.c:5131
void invoke_registered_callback(const std::string &name)
Some UI functions are hardcoded, for example the main menu is hardcoded to appear when the game start...
Definition: luavm.cpp:376
static int call_func(lua_State *L, int nargs, int nret)
Definition: luavm.cpp:344
lua_State * state
Definition: luavm.hpp:49
Checkbox widget.
Definition: checkbox.hpp:39
void set_value(bool checked)
Definition: checkbox.cpp:73
bool get_value() const
Definition: checkbox.cpp:69
A basic widget without any presets.
Definition: div.hpp:38
#define CXX_THROW(class,...)
Definition: utils.hpp:98