forked from enlightenment/efl
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
471 lines
14 KiB
471 lines
14 KiB
-- EFL LuaJIT bindings: Eo |
|
-- For use with Elua |
|
|
|
local ffi = require("ffi") |
|
|
|
ffi.cdef [[ |
|
typedef unsigned char Eina_Bool; |
|
typedef struct _Eina_Iterator Eina_Iterator; |
|
|
|
typedef struct _Eo_Opaque Eo; |
|
typedef Eo Eo_Class; |
|
|
|
typedef short Eo_Callback_Priority; |
|
|
|
typedef void (*eo_key_data_free_func)(void *); |
|
|
|
struct _Eo_Event_Description { |
|
const char *name; |
|
const char *doc; |
|
Eina_Bool unfreezable; |
|
}; |
|
typedef struct _Eo_Event_Description Eo_Event_Description; |
|
|
|
typedef Eina_Bool (*Eo_Event_Cb)(void *data, Eo *obj, |
|
const Eo_Event_Description *desc, void *event_info); |
|
|
|
struct _Eo_Callback_Array_Item { |
|
const Eo_Event_Description *desc; |
|
Eo_Event_Cb func; |
|
}; |
|
typedef struct _Eo_Callback_Array_Item Eo_Callback_Array_Item; |
|
|
|
Eina_Bool eo_init(void); |
|
Eina_Bool eo_shutdown(void); |
|
|
|
Eina_Bool eo_isa(const Eo *obj, const Eo_Class *klass); |
|
|
|
const char *eo_class_name_get(const Eo_Class *klass); |
|
|
|
void eo_constructor(void); |
|
void eo_destructor(void); |
|
|
|
Eo *_eo_add_internal_start(const char *file, int line, |
|
const Eo_Class *klass_id, Eo *parent, Eina_Bool ref); |
|
|
|
Eina_Bool _eo_do_start(const Eo *obj, const Eo_Class *cur_klass, |
|
Eina_Bool is_super, const char *file, const char *func, int line); |
|
void _eo_do_end (const Eo **ojb); |
|
|
|
const Eo_Class *eo_class_get(const Eo *obj); |
|
|
|
void *eo_data_xref_internal(const char *file, int line, const Eo *obj, |
|
const Eo_Class *klass, const Eo *ref_obj); |
|
void eo_data_xunref_internal(const Eo *obj, void *data, const Eo *ref_obj); |
|
Eo *eo_xref_internal(const char *file, int line, Eo *obj, const Eo *ref_obj); |
|
void eo_xunref(Eo *obj, const Eo *ref_obj); |
|
Eo *eo_ref(const Eo *obj); |
|
void eo_unref(const Eo *obj); |
|
int eo_ref_get(const Eo *obj); |
|
void eo_wref_add(Eo **wref); |
|
void eo_wref_del(Eo **wref); |
|
|
|
void eo_del(const Eo *obj); |
|
|
|
void eo_manual_free_set(Eo *obj, Eina_Bool manual_free); |
|
Eina_Bool eo_manual_free(Eo *obj); |
|
Eina_Bool eo_destructed_is(const Eo *obj); |
|
|
|
Eina_Bool eo_composite_attach(Eo *comp_obj, Eo *parent); |
|
Eina_Bool eo_composite_detach(Eo *comp_obj, Eo *parent); |
|
Eina_Bool eo_composite_is(const Eo *comp_obj); |
|
|
|
void eo_parent_set(Eo *parent); |
|
Eo *eo_parent_get(void); |
|
|
|
void eo_event_freeze(void); |
|
void eo_event_thaw(void); |
|
int eo_event_freeze_count_get(void); |
|
void eo_event_global_freeze(void); |
|
void eo_event_global_thaw(void); |
|
int eo_event_global_freeze_count_get(void); |
|
|
|
Eina_Bool eo_finalized_get(void); |
|
Eo *eo_finalize(void); |
|
|
|
void eo_event_callback_forwarder_add(const Eo_Event_Description *desc, |
|
Eo *new_obj); |
|
void eo_event_callback_forwarder_del(const Eo_Event_Description *desc, |
|
Eo *new_obj); |
|
void eo_event_callback_priority_add(const Eo_Event_Description *desc, |
|
Eo_Callback_Priority priority, Eo_Event_Cb cb, const void *data); |
|
void eo_event_callback_del(const Eo_Event_Description *desc, |
|
Eo_Event_Cb func, const void *user_data); |
|
void eo_event_callback_array_priority_add(const Eo_Callback_Array_Item *array, |
|
Eo_Callback_Priority priority, const void *data); |
|
void eo_event_callback_array_del(const Eo_Callback_Array_Item *array, |
|
const void *user_data); |
|
Eina_Bool eo_event_callback_call(const Eo_Event_Description *desc, |
|
void *event_info); |
|
|
|
void eo_key_data_set(const char *key, const void *data, |
|
eo_key_data_free_func free_func); |
|
void *eo_key_data_get(const char *key); |
|
|
|
Eina_Iterator *eo_children_iterator_new(void); |
|
|
|
const Eo_Class *eo_base_class_get(void); |
|
|
|
extern const Eo_Event_Description _EO_BASE_EVENT_DEL; |
|
]] |
|
|
|
local addr_d = ffi.typeof("union { double d; const Eo *p; }") |
|
local eo_obj_addr_get = function(x) |
|
local v = addr_d() |
|
v.p = x |
|
return tonumber(v.d) |
|
end |
|
|
|
local cutil = require("cutil") |
|
local util = require("util") |
|
|
|
local M = {} |
|
|
|
local eo |
|
|
|
local classes = {} |
|
local eo_classes = {} |
|
|
|
-- event system |
|
|
|
local eo_callbacks, eo_cbidcache = {}, {} |
|
|
|
local eo_event_del, eo_event_cb |
|
|
|
local eo_event_del_fun = function(data, obj, desc, einfo) |
|
local addr = eo_obj_addr_get(obj) |
|
if eo_callbacks[addr] then |
|
eo_callbacks[addr], eo_cbidcache[addr] = nil, nil |
|
end |
|
end |
|
|
|
local eo_event_cb_fun = function(data, obj, desc, einfo) |
|
local addr = eo_obj_addr_get(obj) |
|
local cbs = eo_callbacks[addr] |
|
assert(cbs) |
|
local cidx = tonumber(ffi.cast("intptr_t", data)) |
|
local fun = cbs[cidx] |
|
assert(fun) |
|
return fun() ~= false |
|
end |
|
|
|
local connect = function(self, ename, func, priority) |
|
local ev = self.__events[ename] |
|
if not ev then |
|
return false, "attempt to connect an invalid event '" .. ename .. "'" |
|
end |
|
local cl = eo_classes["Eo_Base"] |
|
-- add the callback to the respective array |
|
local cdel = false |
|
local addr = eo_obj_addr_get(self) |
|
local cbs = eo_callbacks[addr] |
|
local cbi = eo_cbidcache[addr] |
|
if not cbs then |
|
cbs, cbi = {}, {} |
|
eo_callbacks[addr], eo_cbidcache[addr] = cbs, cbi |
|
cdel = true |
|
end |
|
local cidx = #cbs + 1 |
|
cbs[cidx], cbi[func] = func, cidx |
|
M.__do_start(self, cl) |
|
eo.eo_event_callback_priority_add(ev, priority or 0, |
|
eo_event_cb, ffi.cast("void *", cidx)) |
|
eo.eo_event_callback_priority_add(eo._EO_BASE_EVENT_DEL, 0, eo_event_del, |
|
nil) |
|
M.__do_end() |
|
return true |
|
end |
|
|
|
local disconnect = function(self, ename, func) |
|
local ev = self.__events[ename] |
|
if not ev then |
|
return false, "attempt to disconnect an invalid event '" .. ename .. "'" |
|
end |
|
local cl = eo_classes["Eo_Base"] |
|
-- like connect, but the other way around |
|
local addr = eo_obj_addr_get(self) |
|
local cbs = eo_callbacks[addr] |
|
if not cbs then |
|
return false |
|
end |
|
local cbi = eo_cbidcache[addr] |
|
assert(cbi) |
|
local cidx = cbi[func] |
|
if not cidx then |
|
return false |
|
end |
|
cbs[cidx] = nil |
|
cbi[func] = nil |
|
M.__do_start(self, cl) |
|
eo.eo_event_callback_del(ev, eo_event_cb, ffi.cast("void *", cidx)) |
|
M.__do_end() |
|
return true |
|
end |
|
|
|
local init = function() |
|
eo = util.lib_load("eo") |
|
eo.eo_init() |
|
local eocl = eo.eo_base_class_get() |
|
local addr = eo_obj_addr_get(eocl) |
|
classes["Eo_Base"] = util.Object:clone { |
|
connect = connect, |
|
disconnect = disconnect, |
|
__events = util.Object:clone {}, |
|
__properties = util.Object:clone {} |
|
} |
|
classes[addr] = classes["Eo_Base"] |
|
eo_classes["Eo_Base"] = eocl |
|
eo_classes[addr] = eocl |
|
eo_event_del = ffi.cast("Eo_Event_Cb", eo_event_del_fun) |
|
eo_event_cb = ffi.cast("Eo_Event_Cb", eo_event_cb_fun) |
|
end |
|
|
|
local shutdown = function() |
|
classes, eo_classes = {}, {} |
|
eo.eo_shutdown() |
|
eo_event_del:free() |
|
eo_event_cb:free() |
|
eo_event_del = nil |
|
eo_event_cb = nil |
|
eo_callbacks = {} |
|
eo_cbidcache = {} |
|
util.lib_unload("eo") |
|
end |
|
|
|
local getinfo = debug.getinfo |
|
|
|
local getfuncname = function(info) |
|
return info.name or "<" .. tostring(info.func) .. ">" |
|
end |
|
|
|
M.class_get = function(name) |
|
return classes[name] |
|
end |
|
|
|
M.eo_class_get = function(name) |
|
return eo_classes[name] |
|
end |
|
|
|
local inherit_meta = function(body, field, parents, mixins) |
|
local o = body[field] |
|
if o then |
|
o = parents[1][field]:clone(o) |
|
for i = 2, #parents do o:add_parent(parents[i][field]) end |
|
for i = 1, #mixins do o:add_mixin (mixins [i][field]) end |
|
body[field] = o |
|
end |
|
end |
|
|
|
M.class_register = function(name, parents, mixins, body, eocl) |
|
-- map given names to objects |
|
local pars = {} |
|
for i = 1, #parents do pars[i] = classes[parents[i]] end |
|
-- for mixins, we need to check if it hasn't already been mixed in |
|
-- in some parent (doesn't matter how deep down the tree), because |
|
-- if it has, we need to skip it (for proper inheritance lookup order) |
|
local mins = {} |
|
local midx = 1 |
|
for i = 1, #mixins do |
|
local mixin = mixins[i] |
|
local ck, hasmi = "__mixin_" .. mixin, false |
|
if mixin[ck] then |
|
for i = 1, #pars do |
|
if pars[i][ck] then |
|
hasmi = true |
|
break |
|
end |
|
end |
|
end |
|
if not hasmi then |
|
mins[midx] = mixin |
|
midx = midx + 1 |
|
end |
|
end |
|
|
|
inherit_meta(body, "__events" , pars, mins) |
|
inherit_meta(body, "__properties", pars, mins) |
|
|
|
local lcl = pars[1]:clone(body) |
|
for i = 2, #pars do lcl:add_parent(pars[i]) end |
|
for i = 1, #mins do lcl:add_mixin (mins[i]) end |
|
|
|
local addr = eo_obj_addr_get(eocl) |
|
classes [name], classes [addr] = lcl , lcl |
|
eo_classes[name], eo_classes[addr] = eocl, eocl |
|
end |
|
|
|
M.class_unregister = function(name) |
|
local addr = eo_obj_addr_get(eo_classes[name]) |
|
classes [name], classes [addr] = nil |
|
eo_classes[name], eo_classes[addr] = nil |
|
end |
|
|
|
local obj_gccb = function(obj) |
|
eo.eo_unref(obj) |
|
end |
|
|
|
M.__ctor_common = function(klass, parent, ctor, loff, ...) |
|
local info = getinfo(2 + (loff or 0), "nlSf") |
|
local source = info.source |
|
local func = getfuncname(info) |
|
local line = info.currentline |
|
local ret = eo._eo_add_internal_start(source, line, klass, parent, true) |
|
local retval |
|
if eo._eo_do_start(ret, nil, false, source, func, line) ~= 0 then |
|
eo.eo_constructor() |
|
if ctor then ctor(ret, ...) end |
|
ret = eo.eo_finalize() |
|
eo._eo_do_end(nil) |
|
end |
|
ffi.gc(ret, obj_gccb) |
|
return ret |
|
end |
|
|
|
M.__do_start = function(self, klass) |
|
if eo.eo_isa(self, klass) == 0 then |
|
error("method call on an invalid object", 3) |
|
end |
|
local info = getinfo(3, "nlSf") |
|
return eo._eo_do_start(self, nil, false, info.source, |
|
getfuncname(info), info.currentline) ~= 0 |
|
end |
|
|
|
M.__do_end = function() |
|
eo._eo_do_end(nil) -- the parameter is unused and originally there |
|
-- only for cleanup (dtor) |
|
end |
|
|
|
local get_obj_mt = function(obj) |
|
local cl = eo.eo_class_get(obj) |
|
if cl == nil then return nil end |
|
return classes[eo_obj_addr_get(cl)] |
|
end |
|
|
|
local prop_proxy_meta = { |
|
__index = function(self, key) |
|
local ngkeys = self.ngkeys |
|
if ngkeys <= 0 then |
|
error("property '" .. self.key .. "' has no getter keys", 2) |
|
end |
|
if ngkeys > 1 and type(key) == "table" then |
|
if self.ngvals > 1 then |
|
return { self(unpack(key)) } |
|
else |
|
return self(unpack(key)) |
|
end |
|
else |
|
if self.ngvals > 1 then |
|
return { self(key) } |
|
else |
|
return self(key) |
|
end |
|
end |
|
end, |
|
|
|
__newindex = function(self, key, val) |
|
local nskeys = self.nskeys |
|
if nskeys <= 0 then |
|
error("property '" .. self.key .. "' has no setter keys", 2) |
|
end |
|
if nskeys > 1 then |
|
-- ultra slow path, q66 failed optimizing this |
|
-- if you ever get to touch this, increment this |
|
-- counter to let others know you failed too. |
|
-- |
|
-- failures: 1 |
|
-- |
|
-- fortunately this one is not very commonly used. |
|
local atbl |
|
if type(key) == "table" then |
|
atbl = { unpack(key) } |
|
else |
|
atbl = { key } |
|
end |
|
if self.nsvals > 1 and type(val) == "table" then |
|
for i, v in ipairs(val) do atbl[nskeys + i] = v end |
|
else |
|
atbl[nskeys + 1] = val |
|
end |
|
self.mt[self.key .. "_set"](self.obj, unpack(atbl)) |
|
else |
|
if self.nsvals > 1 and type(val) == "table" then |
|
-- somewhat less slow but still slow path |
|
self.mt[self.key .. "_set"](self.obj, key, unpack(val)) |
|
else |
|
-- least slow, no unpacks, no temporaries, just proxy |
|
self.mt[self.key .. "_set"](self.obj, key, val) |
|
end |
|
end |
|
end, |
|
|
|
-- provides alt syntax for getters with keys |
|
__call = function(self, ...) |
|
return self.mt[self.key .. "_get"](self.obj, ...) |
|
end, |
|
|
|
-- locks out the proxy |
|
__metatable = false |
|
} |
|
|
|
-- each __properties field looks like this: |
|
-- |
|
-- { NUM_GET_KEYS, NUM_SET_KEYS, NUM_GET_VALS, NUM_SET_VALS, GETTABLE, SETTABLE } |
|
-- |
|
-- the last two are booleans (determining if the property can be get and set). |
|
ffi.metatype("Eo", { |
|
-- handles property getting with no keys and also properties with keys |
|
__index = function(self, key) |
|
local mt = get_obj_mt(self) |
|
if mt == nil then return nil end |
|
local pt = mt.__properties |
|
local pp = pt[key] |
|
if not pp then |
|
return mt[key] |
|
end |
|
local ngkeys, nskeys = pp[1], pp[2] |
|
if ngkeys ~= 0 or nskeys ~= 0 then |
|
-- proxy - slow path, but no way around it |
|
-- basically the proxy is needed because we want nice syntax and |
|
-- lua can't do it by default. so we help ourselves a bit with this |
|
return setmetatable({ ngkeys = ngkeys, nskeys = nskeys, |
|
ngvals = pp[3], nsvals = pp[4], obj = self, key = key, |
|
mt = mt }, prop_proxy_meta) |
|
end |
|
if not pp[5] then |
|
error("property '" .. key .. "' is not gettable", 2) |
|
end |
|
if pp[3] > 1 then |
|
return { mt[key .. "_get"](self) } |
|
else |
|
return mt[key .. "_get"](self) |
|
end |
|
end, |
|
|
|
-- handles property setting with no keys |
|
__newindex = function(self, key, val) |
|
local mt = get_obj_mt(self) |
|
if mt == nil then return nil end |
|
local pt = mt.__properties |
|
local pp = pt[key] |
|
if not pp then |
|
error("no such property '" .. key .. "'", 2) |
|
end |
|
if not pp[6] then |
|
error("property '" .. key .. "' is not settable", 2) |
|
end |
|
local nkeys = pp[2] |
|
if nkeys ~= 0 then |
|
error("property '" .. key .. "' requires " .. nkeys .. " keys", 2) |
|
end |
|
local nvals = pp[4] |
|
if nvals > 1 and type(val) == "table" then |
|
mt[key .. "_set"](self, unpack(val)) |
|
else |
|
mt[key .. "_set"](self, val) |
|
end |
|
end |
|
}) |
|
|
|
cutil.init_module(init, shutdown) |
|
|
|
return M
|
|
|