ffi_accel/init.lua

285 lines
8.4 KiB
Lua

--[===================================================================[--
ffi_accel - Mod for Minetest to accelerate handling of LuaVoxelManip
and PerlinNoiseMap through the foreign function interface
Copyright © 2019 Pedro Gimeno Fortea. All rights reserved.
Permission is hereby granted to everyone to copy, modify, distribute
and use this file, for any purpose, in whole or in part, free of
charge, under the sole condition that the above copyright notice,
together with this permission grant and the disclaimer below, are
included in all copies of this software or of a substantial portion
of it.
THIS SOFTWARE COMES WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED.
--]===================================================================]--
-- Based on an idea of EvidenceBKidsCode.
-- This mod needs to apply a patch to the engine in order to work; see
--
local modname = "Mod ffi_accel"
local err_prefix = modname .. " won't have any effect because "
local ie = minetest.request_insecure_environment()
if not ie then
minetest.log("warning", err_prefix .. "it's not declared in secure.trusted_mods")
return
end
-- We need raw read access to the metatables for monkey-patching
local getmetatable = ie.debug.getmetatable
local setmetatable = setmetatable
local ok, ffi = pcall(ie.require, 'ffi')
if not ok or not ffi then
minetest.log("warning", err_prefix .. "FFI is not available in this system")
return
end
ffi.cdef[[
typedef uint8_t u8;
typedef uint16_t u16;
typedef int32_t s32;
typedef struct {
u16 nodeid;
u8 light;
u8 param2;
} MapNode;
int get_mapnode_version(void);
void VoxelManip_get_pointer(void **lvmp, void **ptr);
s32 VoxelManip_get_volume(void **lvmp);
void PerlinNoiseMap_get_pointer(void **pnmp, void **ptr);
size_t PerlinNoiseMap_get_area(void **pnmp);
size_t PerlinNoiseMap_get_volume(void **pnmp);
]]
local ffiC = ffi.C
local ffinew = ffi.new
local VoidPtrPtr = ffi.typeof('void*[1]')
local MapNodePtr = ffi.typeof('MapNode *')
local mt_set_data, mt_set_light_data, mt_set_param2_data
if not pcall(function ()
assert(ffiC.get_mapnode_version and ffiC.VoxelManip_get_pointer
and ffiC.VoxelManip_get_volume and ffiC.PerlinNoiseMap_get_pointer)
end)
then
minetest.log("warning", err_prefix .. "your Minetest executable has not been patched to support FFI")
return
end
if ffiC.get_mapnode_version() ~= 1 then
minetest.log("warning", err_prefix .. "of incompatible map node structure")
return
end
-- Table of pointers (keys are weak refs)
local ptrtable = setmetatable({}, {__mode = 'k'})
-- Table of sizes (keys are weak refs)
local sizetable = setmetatable({}, {__mode = 'k'})
-- Metamethod: get a value
local function mm_get(t, idx)
return idx >= 1 and idx <= sizetable[t] and ptrtable[t][idx - 1] or rawget(t, idx)
end
-- Metamethod: set a value
local function mm_set(t, idx, value)
if idx >= 1 and idx <= sizetable[t] then
ptrtable[t][idx - 1] = value
else
rawset(t, idx, value)
end
end
-- Metamethod: get the nodeid of an index
local function mm_get_nodeid(t, idx)
return idx >= 1 and idx <= sizetable[t] and ptrtable[t][idx - 1].nodeid or rawget(t, idx)
end
-- Metamethod: set the nodeid of an index to a value
local function mm_set_nodeid(t, idx, value)
if idx >= 1 and idx <= sizetable[t] then
ptrtable[t][idx - 1].nodeid = value
else
rawset(t, idx, value)
end
end
-- Metamethod: get the light of an index
local function mm_get_light(t, idx)
return idx >= 1 and idx <= sizetable[t] and ptrtable[t][idx - 1].light or rawget(t, idx)
end
-- Metamethod: set the light of an index to a value
local function mm_set_light(t, idx, value)
if idx >= 1 and idx <= sizetable[t] then
ptrtable[t][idx - 1].light = value
else
rawset(t, idx, value)
end
end
-- Metamethod: get the param2 of an index
local function mm_get_param2(t, idx)
return idx >= 1 and idx <= sizetable[t] and ptrtable[t][idx - 1].param2 or rawget(t, idx)
end
-- Metamethod: set the param2 of an index to a value
local function mm_set_param2(t, idx, value)
if idx >= 1 and idx <= sizetable[t] then
ptrtable[t][idx - 1].param2 = value
else
rawset(t, idx, value)
end
end
-- Metatables for VoxelManip nodeid, light and param2
local VoxelManip_nodeid_mt = { __index = mm_get_nodeid, __newindex = mm_set_nodeid }
local VoxelManip_light_mt = { __index = mm_get_light, __newindex = mm_set_light }
local VoxelManip_param2_mt = { __index = mm_get_param2, __newindex = mm_set_param2 }
-- Replacement VoxelManip.get_data and VoxelManip.set_data methods
local function VoxelManip_get_data(self, buffer)
-- Set up the metatable and pointer
buffer = setmetatable(buffer or {}, VoxelManip_nodeid_mt)
do
local ptr = VoidPtrPtr()
ffiC.VoxelManip_get_pointer(self, ptr)
ptrtable[buffer] = MapNodePtr(ptr[0])
end
sizetable[buffer] = ffiC.VoxelManip_get_volume(self)
return buffer
end
local function VoxelManip_set_data(self, buffer)
-- Call the original set_data method if the metatable is not
-- the one we've set
if getmetatable(buffer) ~= VoxelManip_nodeid_mt then
mt_set_data(self, buffer)
end
end
-- Replacement VoxelManip.get/set_light_data methods
local function VoxelManip_get_light_data(self, buffer)
-- Set up the metatable and pointer
buffer = setmetatable(buffer or {}, VoxelManip_light_mt)
do
local ptr = VoidPtrPtr()
ffiC.VoxelManip_get_pointer(self, ptr)
ptrtable[buffer] = MapNodePtr(ptr[0])
end
sizetable[buffer] = ffiC.VoxelManip_get_volume(self)
return buffer
end
local function VoxelManip_set_light_data(self, buffer)
-- Call the original set_light_data method if the metatable is not
-- the one we've set
if getmetatable(buffer) ~= VoxelManip_light_mt then
mt_set_light_data(self, buffer)
end
end
-- Replacement VoxelManip.get/set_param2_data methods
local function VoxelManip_get_param2_data(self, buffer)
-- Set up the metatable and pointer
buffer = setmetatable(buffer or {}, VoxelManip_param2_mt)
do
local ptr = VoidPtrPtr()
ffiC.VoxelManip_get_pointer(self, ptr)
ptrtable[buffer] = MapNodePtr(ptr[0])
end
sizetable[buffer] = ffiC.VoxelManip_get_volume(self)
return buffer
end
local function VoxelManip_set_param2_data(self, buffer)
-- Call the original set_param2_data method if the metatable is not
-- the one we've set
if getmetatable(buffer) ~= VoxelManip_param2_mt then
mt_set_param2_data(self, buffer)
end
end
-- Metatable for PerlinNoiseMap
local PerlinNoiseMap_mt = { __index = mm_get }
-- Replacement methods for PerlinNoiseMap.*
local function PerlinNoiseMap_get_2d_map_flat(self, pos, buffer)
-- Calculate the noise
self:calc_2d_map(pos)
-- Prepare the metatable for direct access to the data
buffer = setmetatable(buffer or {}, PerlinNoiseMap_mt)
do
local ptr = VoidPtrPtr()
ffiC.PerlinNoiseMap_get_pointer(self, ptr)
ptrtable[buffer] = MapNodePtr(ptr[0])
end
sizetable[buffer] = ffiC.PerlinNoiseMap_get_area(self)
return buffer
end
local function PerlinNoiseMap_get_3d_map_flat(self, pos, buffer)
-- Calculate the noise
self:calc_3d_map(pos)
-- Prepare the metatable for direct access to the data
buffer = setmetatable(buffer or {}, PerlinNoiseMap_mt)
do
local ptr = VoidPtrPtr()
ffiC.PerlinNoiseMap_get_pointer(self, ptr)
ptrtable[buffer] = MapNodePtr(ptr[0])
end
sizetable[buffer] = ffiC.PerlinNoiseMap_get_volume(self)
return buffer
end
--[[
local function PerlinNoiseMap_get_map_slice(slice_offset, slice_size, buffer)
-- TODO
end
--]]
-- Monkey-patch VoxelManip and PerlinNoiseMap
minetest.after(0, function()
-- VoxelManip buffer-intensive methods
local mt = getmetatable(minetest.get_voxel_manip())
mt_set_data = mt.set_data
mt_set_light_data = mt.set_light_data
mt_set_param2_data = mt.set_param2_data
mt.get_data = VoxelManip_get_data
mt.set_data = VoxelManip_set_data
mt.get_light_data = VoxelManip_get_light_data
mt.set_light_data = VoxelManip_set_light_data
mt.get_param2_data = VoxelManip_get_param2_data
mt.set_param2_data = VoxelManip_set_param2_data
-- PerlinNoiseMap buffer-intensive methods
mt = getmetatable(minetest.get_perlin_map({spread = {x=100, y=100, z=100}}, {x=1, y=1, z=1}))
mt.get_2d_map_flat = PerlinNoiseMap_get_2d_map_flat
mt.get_3d_map_flat = PerlinNoiseMap_get_3d_map_flat
-- mt.get_map_slice = PerlinNoiseMap_get_map_slice
-- Aliases
mt.get2DMap_flat = mt.get_2d_map_flat
mt.get3DMap_flat = mt.get_3d_map_flat
-- mt.getMapSlice = mt.get_map_slice
end)
minetest.log("action", modname .. " activated!")