diff --git a/test.lua b/test.lua index fe10b63..7b303b8 100644 --- a/test.lua +++ b/test.lua @@ -6,3 +6,71 @@ local systemB = tiny.system() world:addSystem(systemA) world:addSystem(systemB) world:setSystemIndex(systemA, 1) + + +--- test requireXXX/rejectXXX functions + +local filt1 = tiny.requireAll("prop1", "prop2") + +assert(filt1(nil, { }) == false) +assert(filt1(nil, { prop1 = 1 }) == false) +assert(filt1(nil, { prop1 = 1, prop3 = 2}) == false) +assert(filt1(nil, { prop1 = 1, prop2 = 2 }) == true) +assert(filt1(nil, { prop2 = 1 }) == false) +assert(filt1(nil, { prop2 = 1, prop1 = 1, prop3 = 2 }) == true) + + +local filt2 = tiny.requireAny("prop1", "prop2") + +assert(filt2(nil, { }) == false) +assert(filt2(nil, { prop1 = 1 }) == true) +assert(filt2(nil, { prop1 = 1, prop3 = 2}) == true) +assert(filt2(nil, { prop1 = 1, prop2 = 2 }) == true) +assert(filt2(nil, { prop2 = 1 }) == true) +assert(filt2(nil, { prop2 = 1, prop1 = 1, prop3 = 2 }) == true) +assert(filt2(nil, { prop4 = 1, prop5 = 1, prop6 = 2 }) == false) + + +local filt3 = tiny.rejectAll("prop1", "prop2") + +assert(filt3(nil, { }) == true) +assert(filt3(nil, { prop1 = 1 }) == true) +assert(filt3(nil, { prop1 = 1, prop3 = 2}) == true) +assert(filt3(nil, { prop1 = 1, prop2 = 2 }) == false) +assert(filt3(nil, { prop2 = 1 }) == true) +assert(filt3(nil, { prop2 = 1, prop1 = 1, prop3 = 2 }) == false) + + +local filt4 = tiny.rejectAny("prop1", "prop2") + +assert(filt4(nil, { }) == true) +assert(filt4(nil, { prop1 = 1 }) == false) +assert(filt4(nil, { prop1 = 1, prop3 = 2}) == false) +assert(filt4(nil, { prop1 = 1, prop2 = 2 }) == false) +assert(filt4(nil, { prop2 = 1 }) == false) +assert(filt4(nil, { prop2 = 1, prop1 = 1, prop3 = 2 }) == false) +assert(filt4(nil, { prop4 = 1, prop5 = 1, prop6 = 2 }) == true) + + + +local filt5 = tiny.requireAll("prop3", filt2) + +assert(filt5(nil, {}) == false) +assert(filt5(nil, {prop1 = 1}) == false) +assert(filt5(nil, {prop1 = 1, prop2 = 1, prop3 = 1}) == true) +assert(filt5(nil, {prop1 = 1, prop2 = 1, prop3 = 1, prop4 = 1}) == true) +assert(filt5(nil, {prop1 = 1, prop2 = 1, prop4 = 1}) == false) + +local filt6 = tiny.requireAny("prop3", filt2) + +assert(filt6(nil, {}) == false) +assert(filt6(nil, {prop1 = 1}) == true) +assert(filt6(nil, {prop1 = 1, prop2 = 1, prop3 = 1}) == true) +assert(filt6(nil, {prop1 = 1, prop2 = 1, prop3 = 1, prop4 = 1}) == true) +assert(filt6(nil, {prop1 = 1, prop2 = 1, prop4 = 1}) == true) +assert(filt6(nil, {prop5 = 1, prop4 = 1}) == false) + + + + + diff --git a/tiny.lua b/tiny.lua index 4dca2cd..f8e85aa 100644 --- a/tiny.lua +++ b/tiny.lua @@ -91,41 +91,50 @@ local filterJoin -- A helper function to filters from string local filterBuildString + +local function filterJoinRaw(invert, joining_op, ...) + local _args = {...} + + return function(system, e) + local acc + local args = _args + if joining_op == 'or' then + acc = false + for i = 1, #args do + local v = args[i] + if type(v) == "string" then + acc = acc or (e[v] ~= nil) + elseif type(v) == "function" then + acc = acc or v(system, e) + else + error 'Filter token must be a string or a filter function.' + end + end + else + acc = true + for i = 1, #args do + local v = args[i] + if type(v) == "string" then + acc = acc and (e[v] ~= nil) + elseif type(v) == "function" then + acc = acc and v(system, e) + else + error 'Filter token must be a string or a filter function.' + end + end + end + + -- computes a simple xor + if invert then + return not acc + else + return acc + end + end +end + do - local loadstring = loadstring or load - local function getchr(c) - return "\\" .. c:byte() - end - local function make_safe(text) - return ("%q"):format(text):gsub('\n', 'n'):gsub("[\128-\255]", getchr) - end - - local function filterJoinRaw(prefix, seperator, ...) - local accum = {} - local build = {} - for i = 1, select('#', ...) do - local item = select(i, ...) - if type(item) == 'string' then - accum[#accum + 1] = ("(e[%s] ~= nil)"):format(make_safe(item)) - elseif type(item) == 'function' then - build[#build + 1] = ('local subfilter_%d_ = select(%d, ...)') - :format(i, i) - accum[#accum + 1] = ('(subfilter_%d_(system, e))'):format(i) - else - error 'Filter token must be a string or a filter function.' - end - end - local source = ('%s\nreturn function(system, e) return %s(%s) end') - :format( - table.concat(build, '\n'), - prefix, - table.concat(accum, seperator)) - local loader, err = loadstring(source) - if err then error(err) end - return loader(...) - end - function filterJoin(...) local state, value = pcall(filterJoinRaw, ...) if state then return value else return nil, value end @@ -169,25 +178,25 @@ end --- Makes a Filter that selects Entities with all specified Components and -- Filters. function tiny.requireAll(...) - return filterJoin('', ' and ', ...) + return filterJoin(false, 'and', ...) end --- Makes a Filter that selects Entities with at least one of the specified -- Components and Filters. function tiny.requireAny(...) - return filterJoin('', ' or ', ...) + return filterJoin(false, 'or', ...) end --- Makes a Filter that rejects Entities with all specified Components and -- Filters, and selects all other Entities. function tiny.rejectAll(...) - return filterJoin('not', ' and ', ...) + return filterJoin(true, 'and', ...) end --- Makes a Filter that rejects Entities with at least one of the specified -- Components and Filters, and selects all other Entities. function tiny.rejectAny(...) - return filterJoin('not', ' or ', ...) + return filterJoin(true, 'or', ...) end --- Makes a Filter from a string. Syntax of `pattern` is as follows.