#2 Add LuaJIT FFI-friendly memory-intensive functions

Отворени
pgimeno заяви обединяване на 4 ревизии от pgimeno/add-ffi-friendly-functions във pgimeno/master
променени са 5 файла, в които са добавени 284 реда и са изтрити 0 реда
  1. 5 0
      builtin/game/features.lua
  2. 78 0
      doc/lua_api.txt
  3. 89 0
      src/script/lua_api/l_noise.cpp
  4. 2 0
      src/script/lua_api/l_noise.h
  5. 110 0
      src/script/lua_api/l_vmanip.cpp

+ 5 - 0
builtin/game/features.lua

@@ -14,6 +14,11 @@ core.features = {
 	object_independent_selectionbox = true,
 }
 
+if jit then
+	core.features.ffi_voxel_manip = true
+	core.features.ffi_perlin_flat_map = true
+end
+
 function core.has_feature(arg)
 	if type(arg) == "table" then
 		local missing_features = {}

+ 78 - 0
doc/lua_api.txt

@@ -25,6 +25,29 @@ Programming in Lua
 If you have any difficulty in understanding this, please read
 [Programming in Lua](http://www.lua.org/pil/).
 
+LuaJIT
+------
+
+LuaJIT is an optional Lua interpreter, only bundled and activated in the
+Windows version of the official Minetest builds, but it can be activated in
+other platforms when compiling Minetest from sources.
+
+Availability of LuaJIT can be checked by mods through the global variable `jit`.
+
+Its main advantage over normal Lua is speed, thanks to its just-in-time (JIT)
+compilation. It has however a stricter memory limit for Lua objects than normal
+Lua, which is why some functions are provided to take advantage of the foreign
+function interface (FFI) of LuaJIT, to work around these limitations, should it
+be necessary. For an introduction to FFI, please read [FFI
+Library](http://luajit.org/ext_ffi.html) and associated links. To take
+advantage of this feature, you need to use `require`, which is one of the
+functions restricted by mod security, therefore when security is active, only
+mods marked as trusted can use FFI.
+
+Keep in mind that FFI cdata arrays are 0-based, rather than 1-based, and access
+to them is not protected against going out of bounds; accessing an element
+beyond the array boundary can crash Minetest.
+
 Startup
 -------
 
@@ -3085,6 +3108,30 @@ to be a table retrieved from `get_data()`.
 Once the internal VoxelManip state has been modified to your liking, the
 changes can be committed back to the map by calling `VoxelManip:write_to_map()`
 
+There are LuaJIT FFI-friendly versions of `get_data()`, `set_data()`,
+`get_light_data()`, `set_light_data()`, `get_param2_data()` and
+`set_param2_data()` that accept CDATA parameters instead of tables, and an
+auxiliary function `VoxelManip_get_volume()` to get the total size of the
+array. They can be used through `ffi.C` after creating these declarations
+(assuming `ffi` is the result of `require('ffi')`):
+
+    ffi.cdef([[
+        int VoxelManip_get_volume(void **vm);
+        void VoxelManip_get_data(void **vm, uint16_t *data);
+        void VoxelManip_set_data(void **vm, uint16_t *data);
+        void VoxelManip_get_light_data(void **vm, uint8_t *data);
+        void VoxelManip_set_light_data(void **vm, uint8_t *data);
+        void VoxelManip_get_param2_data(void **vm, uint8_t *data);
+        void VoxelManip_set_param2_data(void **vm, uint8_t *data);
+    ]])
+
+For example, `ffi.C.VoxelManip_get_data(vm, data)` can be used instead of
+`vm:get_data(data)` if `data` is allocated as a CDATA array of sufficient size.
+The size necessary can be obtained through `ffi.C.VoxelManip_get_volume(vm)`.
+
+Make sure the buffers allocated have the correct pointer type (`uint16_t *` for
+`get_data()` and `set_data()`, `uint8_t *` for the others).
+
 ### Flat array format
 
 Let
@@ -3532,6 +3579,10 @@ Utilities
           -- Object selectionbox is settable independently from collisionbox
           -- (5.0)
           object_independent_selectionbox = true,
+          -- Supports LuaJIT FFI C functions for LuaVoxelManip (5.1)
+          ffi_voxel_manip = true,
+          -- Supports LuaJIT FFI C functions for PerlinNoiseMap (5.1)
+          ffi_perlin_flat_map = true,
       }
 
 * `minetest.has_feature(arg)`: returns `boolean, missing_features`
@@ -5460,6 +5511,33 @@ table.
   `noise:calc_3d_map({x=1000, y=1000, z=1000})`
   `noisevals = noise:get_map_slice({x=24, z=1}, {x=1, z=1})`
 
+There are LuaJIT FFI-friendly versions of `get_2d_map_flat()`, `get_3d_map_flat()`
+and `get_map_slice()` that accept CDATA objects instead of Lua tables. They can
+be accessed through `ffi.C` after the following declarations (assuming `ffi` is
+the result of `require('ffi')`):
+
+    ffi.cdef([[
+        typedef uint16_t u16;
+        void PerlinNoiseMap_get_2d_map_flat(void **pnmp, double px, double py,
+            float *buffer);
+        void PerlinNoiseMap_get_3d_map_flat(void **pnmp,
+            double px, double py, double pz, float *buffer);
+        void PerlinNoiseMap_get_map_slice(void **pnmp,
+            u16 ofsx, u16 ofsy, u16 ofsz, u16 sizex, u16 sizey, u16 sizez,
+            float *buffer);
+    ]])
+
+For `PerlinNoiseMap_get_map_slice()` the offsets are also 1-based; if you want
+to use the whole extent for a coordinate, use 0 in that place where you would
+omit the coordinate in the Lua counterpart.
+
+For example: `ffi.C.PerlinNoiseMap_get_2d_map_flat(pnm, 5, 7, buffer)` could be
+used in place of `pnm:get_2d_map_flat({x = 5, y = 7}, buffer)` if `buffer` is
+allocated as a CDATA array instead of a table.
+
+Make sure the buffers allocated have the correct type (`float *`) and enough
+space to hold the requested data.
+
 `PlayerMetaRef`
 ---------------
 

+ 89 - 0
src/script/lua_api/l_noise.cpp

@@ -17,6 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
+#include "config.h"
 #include "lua_api/l_noise.h"
 #include "lua_api/l_internal.h"
 #include "common/c_converter.h"
@@ -401,6 +402,94 @@ luaL_Reg LuaPerlinNoiseMap::methods[] = {
 	{0,0}
 };
 
+#if USE_LUAJIT
+
+extern "C" {
+
+void PerlinNoiseMap_get_2d_map_flat(void **pnmp, double px, double py, float *buffer)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	if (pnmp == nullptr)
+		throw ModError("Nil pointer in C call");
+
+	LuaPerlinNoiseMap *o = *(LuaPerlinNoiseMap **)pnmp;
+	Noise *n = o->getNoise();
+
+	n->perlinMap2D(px, py);
+	memcpy(buffer, n->result, n->sx * n->sy * sizeof(float));
+}
+
+void PerlinNoiseMap_get_3d_map_flat(void **pnmp, double px, double py, double pz, float *buffer)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	if (pnmp == nullptr)
+		throw ModError("Nil pointer in C call");
+
+	LuaPerlinNoiseMap *o = *(LuaPerlinNoiseMap **)pnmp;
+	Noise *n = o->getNoise();
+
+	n->perlinMap3D(px, py, pz);
+	memcpy(buffer, n->result, n->sx * n->sy * n->sz * sizeof(float));
+}
+
+void PerlinNoiseMap_get_map_slice(void **pnmp, u16 px, u16 py, u16 pz, u16 sx, u16 sy, u16 sz, float *buffer)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	if (pnmp == nullptr)
+		throw ModError("Nil pointer in C call");
+
+	LuaPerlinNoiseMap *o = *(LuaPerlinNoiseMap **)pnmp;
+	Noise *n = o->getNoise();
+
+	v3u16 pmin, pmax(n->sx, n->sy, n->sz);
+	if (px > 0) {
+		px--;
+		pmin.X = px;
+		pmax.X = std::min((u32)(px + sx), (u32)n->sx);
+	}
+
+	if (py > 0) {
+		py--;
+		pmin.Y = py;
+		pmax.Y = std::min((u32)(py + sy), (u32)n->sy);
+	}
+
+	if (pz > 0) {
+		pz--;
+		pmin.Z = pz;
+		pmax.Z = std::min((u32)(pz + sz), (u32)n->sz);
+	}
+
+	if (pmin.X >= pmax.X)
+		return;
+
+	const size_t xrange = pmax.X - pmin.X;
+
+	const  size_t xsize   = xrange * sizeof(float);
+	const  size_t ystride = n->sx;
+	const  size_t zstride = n->sy  * ystride;
+	const  size_t zstart  = pmin.Z * zstride;
+	const  size_t zlimit  = pmax.Z * zstride;
+	size_t elem_index  = 0;
+
+	for (size_t zbase = zstart; zbase < zlimit; zbase += zstride) {
+		const size_t ystart = pmin.Y * ystride + zbase;
+		const size_t ylimit = pmax.Y * ystride + zbase;
+		for (size_t ybase = ystart; ybase < ylimit; ybase += ystride) {
+			memcpy(&buffer[elem_index], &n->result[ybase + pmin.X], xsize);
+			elem_index += xrange;
+		}
+	}
+}
+
+
+} // extern "C"
+
+#endif // USE_LUAJIT
+
 ///////////////////////////////////////
 /*
 	LuaPseudoRandom

+ 2 - 0
src/script/lua_api/l_noise.h

@@ -91,6 +91,8 @@ public:
 	static LuaPerlinNoiseMap *checkobject(lua_State *L, int narg);
 
 	static void Register(lua_State *L);
+
+	Noise *getNoise() const { return noise; }
 };
 
 /*

+ 110 - 0
src/script/lua_api/l_vmanip.cpp

@@ -30,6 +30,116 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "mapgen/mapgen.h"
 #include "voxelalgorithms.h"
 
+#if USE_LUAJIT
+
+extern "C" {
+
+s32 VoxelManip_get_volume(void **lvmp)
+{
+	if (lvmp == nullptr)
+		throw ModError("Nil pointer in C call");
+
+	return (*(LuaVoxelManip **)lvmp)->vm->m_area.getVolume();
+}
+
+void VoxelManip_get_data(void **lvmp, u16 *data)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	if (lvmp == nullptr)
+		throw ModError("Nil pointer in C call");
+
+	MMVManip *vm = (*(LuaVoxelManip **)lvmp)->vm;
+
+	u32 volume = vm->m_area.getVolume();
+
+	for (u32 i = 0; i != volume; i++) {
+		data[i] = vm->m_data[i].getContent();
+	}
+}
+
+void VoxelManip_set_data(void **lvmp, u16 *data)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	if (lvmp == nullptr)
+		throw ModError("Nil pointer in C call");
+
+	MMVManip *vm = (*(LuaVoxelManip **)lvmp)->vm;
+
+	u32 volume = vm->m_area.getVolume();
+
+	for (u32 i = 0; i != volume; i++) {
+		vm->m_data[i].setContent(data[i]);
+	}
+}
+
+void VoxelManip_get_light_data(void **lvmp, u8 *data)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	if (lvmp == nullptr)
+		throw ModError("Nil pointer in C call");
+
+	MMVManip *vm = (*(LuaVoxelManip **)lvmp)->vm;
+
+	u32 volume = vm->m_area.getVolume();
+
+	for (u32 i = 0; i != volume; i++) {
+		data[i] = vm->m_data[i].param1;
+	}
+}
+
+void VoxelManip_set_light_data(void **lvmp, u8 *data)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	if (lvmp == nullptr)
+		throw ModError("Nil pointer in C call");
+
+	MMVManip *vm = (*(LuaVoxelManip **)lvmp)->vm;
+
+	u32 volume = vm->m_area.getVolume();
+	for (u32 i = 0; i != volume; i++) {
+		vm->m_data[i].param1 = data[i];
+	}
+}
+
+void VoxelManip_get_param2_data(void **lvmp, u8 *data)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	if (lvmp == nullptr)
+		throw ModError("Nil pointer in C call");
+
+	MMVManip *vm = (*(LuaVoxelManip **)lvmp)->vm;
+
+	u32 volume = vm->m_area.getVolume();
+
+	for (u32 i = 0; i != volume; i++) {
+		data[i] = vm->m_data[i].param2;
+	}
+}
+
+void VoxelManip_set_param2_data(void **lvmp, u8 *data)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	if (lvmp == nullptr)
+		throw ModError("Nil pointer in C call");
+
+	MMVManip *vm = (*(LuaVoxelManip **)lvmp)->vm;
+
+	u32 volume = vm->m_area.getVolume();
+	for (u32 i = 0; i != volume; i++) {
+		vm->m_data[i].param2 = data[i];
+	}
+}
+
+} // extern "C"
+
+#endif
+
 // garbage collector
 int LuaVoxelManip::gc_object(lua_State *L)
 {