]> git.lizzy.rs Git - minetest.git/blob - src/script/lua_api/l_metadata.cpp
Improve `MetaDataRef:{get,set}_float` precision (#13130)
[minetest.git] / src / script / lua_api / l_metadata.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4 Copyright (C) 2017-8 rubenwardy <rw@rubenwardy.com>
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include "lua_api/l_metadata.h"
22 #include "lua_api/l_internal.h"
23 #include "common/c_content.h"
24 #include "serverenvironment.h"
25 #include "map.h"
26 #include "server.h"
27 #include "util/basic_macros.h"
28
29 MetaDataRef *MetaDataRef::checkAnyMetadata(lua_State *L, int narg)
30 {
31         void *ud = lua_touserdata(L, narg);
32
33         bool ok = ud && luaL_getmetafield(L, narg, "metadata_class");
34         if (ok) {
35                 ok = lua_isstring(L, -1);
36                 lua_pop(L, 1);
37         }
38
39         if (!ok)
40                 luaL_typerror(L, narg, "MetaDataRef");
41
42         return *(MetaDataRef **)ud; // unbox pointer
43 }
44
45 int MetaDataRef::gc_object(lua_State *L)
46 {
47         MetaDataRef *o = *(MetaDataRef **)lua_touserdata(L, 1);
48         delete o;
49         return 0;
50 }
51
52 // Exported functions
53
54 // contains(self, name)
55 int MetaDataRef::l_contains(lua_State *L)
56 {
57         MAP_LOCK_REQUIRED;
58
59         MetaDataRef *ref = checkAnyMetadata(L, 1);
60         std::string name = luaL_checkstring(L, 2);
61
62         IMetadata *meta = ref->getmeta(false);
63         if (meta == NULL)
64                 return 0;
65
66         lua_pushboolean(L, meta->contains(name));
67         return 1;
68 }
69
70 // get(self, name)
71 int MetaDataRef::l_get(lua_State *L)
72 {
73         MAP_LOCK_REQUIRED;
74
75         MetaDataRef *ref = checkAnyMetadata(L, 1);
76         std::string name = luaL_checkstring(L, 2);
77
78         IMetadata *meta = ref->getmeta(false);
79         if (meta == NULL)
80                 return 0;
81
82         std::string str;
83         if (meta->getStringToRef(name, str)) {
84                 lua_pushlstring(L, str.c_str(), str.size());
85         } else {
86                 lua_pushnil(L);
87         }
88         return 1;
89 }
90
91 // get_string(self, name)
92 int MetaDataRef::l_get_string(lua_State *L)
93 {
94         MAP_LOCK_REQUIRED;
95
96         MetaDataRef *ref = checkAnyMetadata(L, 1);
97         std::string name = luaL_checkstring(L, 2);
98
99         IMetadata *meta = ref->getmeta(false);
100         if (meta == NULL) {
101                 lua_pushlstring(L, "", 0);
102                 return 1;
103         }
104
105         std::string str_;
106         const std::string &str = meta->getString(name, &str_);
107         lua_pushlstring(L, str.c_str(), str.size());
108         return 1;
109 }
110
111 // set_string(self, name, var)
112 int MetaDataRef::l_set_string(lua_State *L)
113 {
114         MAP_LOCK_REQUIRED;
115
116         MetaDataRef *ref = checkAnyMetadata(L, 1);
117         std::string name = luaL_checkstring(L, 2);
118         size_t len = 0;
119         const char *s = lua_tolstring(L, 3, &len);
120         std::string str(s, len);
121
122         IMetadata *meta = ref->getmeta(!str.empty());
123         if (meta != NULL && meta->setString(name, str))
124                 ref->reportMetadataChange(&name);
125         return 0;
126 }
127
128 // get_int(self, name)
129 int MetaDataRef::l_get_int(lua_State *L)
130 {
131         MAP_LOCK_REQUIRED;
132
133         MetaDataRef *ref = checkAnyMetadata(L, 1);
134         std::string name = luaL_checkstring(L, 2);
135
136         IMetadata *meta = ref->getmeta(false);
137         if (meta == NULL) {
138                 lua_pushnumber(L, 0);
139                 return 1;
140         }
141
142         std::string str_;
143         const std::string &str = meta->getString(name, &str_);
144         lua_pushnumber(L, stoi(str));
145         return 1;
146 }
147
148 // set_int(self, name, var)
149 int MetaDataRef::l_set_int(lua_State *L)
150 {
151         MAP_LOCK_REQUIRED;
152
153         MetaDataRef *ref = checkAnyMetadata(L, 1);
154         std::string name = luaL_checkstring(L, 2);
155         int a = luaL_checkint(L, 3);
156         std::string str = itos(a);
157
158         IMetadata *meta = ref->getmeta(true);
159         if (meta != NULL && meta->setString(name, str))
160                 ref->reportMetadataChange(&name);
161         return 0;
162 }
163
164 // get_float(self, name)
165 int MetaDataRef::l_get_float(lua_State *L)
166 {
167         MAP_LOCK_REQUIRED;
168
169         MetaDataRef *ref = checkAnyMetadata(L, 1);
170         std::string name = luaL_checkstring(L, 2);
171
172         IMetadata *meta = ref->getmeta(false);
173         if (meta == NULL) {
174                 lua_pushnumber(L, 0);
175                 return 1;
176         }
177
178         std::string str_;
179         const std::string &str = meta->getString(name, &str_);
180         // Convert with Lua, as is done in set_float.
181         lua_pushlstring(L, str.data(), str.size());
182         lua_pushnumber(L, lua_tonumber(L, -1));
183         return 1;
184 }
185
186 // set_float(self, name, var)
187 int MetaDataRef::l_set_float(lua_State *L)
188 {
189         MAP_LOCK_REQUIRED;
190
191         MetaDataRef *ref = checkAnyMetadata(L, 1);
192         std::string name = luaL_checkstring(L, 2);
193         luaL_checknumber(L, 3);
194         // Convert number to string with Lua as it gives good precision.
195         std::string str = readParam<std::string>(L, 3);
196
197         IMetadata *meta = ref->getmeta(true);
198         if (meta != NULL && meta->setString(name, str))
199                 ref->reportMetadataChange(&name);
200         return 0;
201 }
202
203 // get_keys(self)
204 int MetaDataRef::l_get_keys(lua_State *L)
205 {
206         MAP_LOCK_REQUIRED;
207
208         MetaDataRef *ref = checkAnyMetadata(L, 1);
209
210         IMetadata *meta = ref->getmeta(false);
211         if (meta == NULL) {
212                 lua_newtable(L);
213                 return 1;
214         }
215
216         std::vector<std::string> keys_;
217         const std::vector<std::string> &keys = meta->getKeys(&keys_);
218
219         int i = 0;
220         lua_createtable(L, keys.size(), 0);
221         for (const std::string &key : keys) {
222                 lua_pushlstring(L, key.c_str(), key.size());
223                 lua_rawseti(L, -2, ++i);
224         }
225         return 1;
226 }
227
228 // to_table(self)
229 int MetaDataRef::l_to_table(lua_State *L)
230 {
231         MAP_LOCK_REQUIRED;
232
233         MetaDataRef *ref = checkAnyMetadata(L, 1);
234
235         IMetadata *meta = ref->getmeta(true);
236         if (meta == NULL) {
237                 lua_pushnil(L);
238                 return 1;
239         }
240         lua_newtable(L);
241
242         ref->handleToTable(L, meta);
243
244         return 1;
245 }
246
247 // from_table(self, table)
248 int MetaDataRef::l_from_table(lua_State *L)
249 {
250         MAP_LOCK_REQUIRED;
251
252         MetaDataRef *ref = checkAnyMetadata(L, 1);
253         int base = 2;
254
255         ref->clearMeta();
256
257         if (!lua_istable(L, base)) {
258                 // No metadata
259                 lua_pushboolean(L, true);
260                 return 1;
261         }
262
263         // Create new metadata
264         IMetadata *meta = ref->getmeta(true);
265         if (meta == NULL) {
266                 lua_pushboolean(L, false);
267                 return 1;
268         }
269
270         bool was_successful = ref->handleFromTable(L, base, meta);
271         ref->reportMetadataChange();
272         lua_pushboolean(L, was_successful);
273         return 1;
274 }
275
276 void MetaDataRef::handleToTable(lua_State *L, IMetadata *meta)
277 {
278         lua_newtable(L);
279         {
280                 StringMap fields_;
281                 const StringMap &fields = meta->getStrings(&fields_);
282                 for (const auto &field : fields) {
283                         const std::string &name = field.first;
284                         const std::string &value = field.second;
285                         lua_pushlstring(L, name.c_str(), name.size());
286                         lua_pushlstring(L, value.c_str(), value.size());
287                         lua_settable(L, -3);
288                 }
289         }
290         lua_setfield(L, -2, "fields");
291 }
292
293 bool MetaDataRef::handleFromTable(lua_State *L, int table, IMetadata *meta)
294 {
295         // Set fields
296         lua_getfield(L, table, "fields");
297         if (lua_istable(L, -1)) {
298                 int fieldstable = lua_gettop(L);
299                 lua_pushnil(L);
300                 while (lua_next(L, fieldstable) != 0) {
301                         // key at index -2 and value at index -1
302                         std::string name = readParam<std::string>(L, -2);
303                         size_t cl;
304                         const char *cs = lua_tolstring(L, -1, &cl);
305                         meta->setString(name, std::string(cs, cl));
306                         lua_pop(L, 1); // Remove value, keep key for next iteration
307                 }
308                 lua_pop(L, 1);
309         }
310
311         return true;
312 }
313
314 // equals(self, other)
315 int MetaDataRef::l_equals(lua_State *L)
316 {
317         MetaDataRef *ref1 = checkAnyMetadata(L, 1);
318         IMetadata *data1 = ref1->getmeta(false);
319         MetaDataRef *ref2 = checkAnyMetadata(L, 2);
320         IMetadata *data2 = ref2->getmeta(false);
321         if (data1 == NULL || data2 == NULL)
322                 lua_pushboolean(L, data1 == data2);
323         else
324                 lua_pushboolean(L, *data1 == *data2);
325         return 1;
326 }
327
328 void MetaDataRef::registerMetadataClass(lua_State *L, const char *name,
329                 const luaL_Reg *methods)
330 {
331         const luaL_Reg metamethods[] = {
332                 {"__eq", l_equals},
333                 {"__gc", gc_object},
334                 {0, 0}
335         };
336         registerClass(L, name, methods, metamethods);
337
338         // Set metadata_class in the metatable for MetaDataRef::checkAnyMetadata.
339         luaL_getmetatable(L, name);
340         lua_pushstring(L, name);
341         lua_setfield(L, -2, "metadata_class");
342         lua_pop(L, 1);
343 }