#2 Add LuaJIT FFI-friendly memory-intensive functions

Open
pgimeno wants to merge 4 commits from pgimeno/add-ffi-friendly-functions into pgimeno/master

+ 5 - 0
builtin/game/features.lua

@@ -14,6 +14,11 @@ core.features = {
 	object_independent_selectionbox = true,
 	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)
 function core.has_feature(arg)
 	if type(arg) == "table" then
 	if type(arg) == "table" then
 		local missing_features = {}
 		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
 If you have any difficulty in understanding this, please read
 [Programming in Lua](http://www.lua.org/pil/).
 [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
 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
 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()`
 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
 ### Flat array format
 
 
 Let
 Let
@@ -3532,6 +3579,10 @@ Utilities
           -- Object selectionbox is settable independently from collisionbox
           -- Object selectionbox is settable independently from collisionbox
           -- (5.0)
           -- (5.0)
           object_independent_selectionbox = true,
           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`
 * `minetest.has_feature(arg)`: returns `boolean, missing_features`
@@ -5460,6 +5511,33 @@ table.
   `noise:calc_3d_map({x=1000, y=1000, z=1000})`
   `noise:calc_3d_map({x=1000, y=1000, z=1000})`
   `noisevals = noise:get_map_slice({x=24, z=1}, {x=1, z=1})`
   `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`
 `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.
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 */
 
 
+#include "config.h"
 #include "lua_api/l_noise.h"
 #include "lua_api/l_noise.h"
 #include "lua_api/l_internal.h"
 #include "lua_api/l_internal.h"
 #include "common/c_converter.h"
 #include "common/c_converter.h"
@@ -401,6 +402,94 @@ luaL_Reg LuaPerlinNoiseMap::methods[] = {
 	{0,0}
 	{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
 	LuaPseudoRandom

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

@@ -91,6 +91,8 @@ public:
 	static LuaPerlinNoiseMap *checkobject(lua_State *L, int narg);
 	static LuaPerlinNoiseMap *checkobject(lua_State *L, int narg);
 
 
 	static void Register(lua_State *L);
 	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 "mapgen/mapgen.h"
 #include "voxelalgorithms.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
 // garbage collector
 int LuaVoxelManip::gc_object(lua_State *L)
 int LuaVoxelManip::gc_object(lua_State *L)
 {
 {