flyweightstore

flyweightstore

function flyweightstore() --> ( ... ) --> reference

Description

Creates a flyweight storage, which is a function that interns the list of arguments, i.e. it generates a reference for each possible list. When it is called multiple times with the same list, it will return the same reference. All the reference are automatically garbage collected when no more used.

Parameters

List of argument to which associate a reference.

Return Values

reference

Reference associated to the list of arguments.

Code

--ZFUNC-flyweightstore-v0
local function flyweightstore() --> ( ... ) --> reference

   local NIL, NAN = {}, {}

   local meta = {
      __index = function()
         error( "Can not access interned content directly.", 2 )
      end,
      __newindex = function()
         error( "Can not change or add content to a flyweight store.", 2 )
      end,
   }

   local internstore = setmetatable( {}, { __mode = "kv" } )

   -- A map from child to parent is used to protect the internstore table's
   -- contents.
   -- In this way, they will he collected only when all the cildren are
   -- collected in turn.
   local parent = setmetatable( {}, { __mode = "k" } )

   return function( ... )
      local currentstore = internstore
      for a = 1, select( "#", ... ) do

         -- Get next intern field. Replace un-storable contents.
         local tonext = select( a, ... )
         if tonext ~= tonext then tonext = NAN end
         if tonext == nil then tonext = NIL end

         -- Get or create the correspondent sub-intern
         local substore = rawget( currentstore, tonext )
         if substore == nil then

            substore = setmetatable( {}, meta )
            parent[ substore ] = currentstore
            rawset( currentstore, tonext, substore )
         end

         currentstore = substore
      end
      return currentstore
   end
end

return flyweightstore

Examples

local t = require "taptest"
local flyweightstore = require "flyweightstore"

local function diff( a, b ) return a ~= b end

t( type( flyweightstore() ), "function" )

local fly = flyweightstore()

t( type( fly( 1 )), "table" )
t( fly( 1 ), fly( 2 ), diff )

t( type( fly( 1, nil, 0/0, 3 ) ), "table" )
t( fly( 1, nil, 0/0, 3 ), fly( 1, nil, 0/0, 3 ) )

t( fly( 1, nil, 0/0, 3 ), fly( 1, nil, 0/0 ), diff )
t( fly( 1, nil, 0/0, 3 ), fly( 1, nil ), diff )
t( fly( 1, nil, 0/0, 3 ), fly( 1 ), diff )

t( fly( 1, nil, 0/0, 3 ), fly( 1, nil, 0/0, 2 ), diff )
t( fly( 1, nil, 0/0, 3 ), fly( 1, nil, 0, 3 ), diff )
t( fly( 1, nil, 0/0, 3 ), fly( 1, '', 0/0, 3 ), diff )
t( fly( 1, nil, 0/0, 3 ), fly( 4, nil, 0/0, 3 ), diff )

-- Multiple store
local alt = flyweightstore()
t( type( alt( 1, nil, 0/0, 3 )), "table" )
t( alt( 1, nil, 0/0, 3 ), alt( 1, nil, 0/0, 3 ) )
t( alt( 1, nil, 0/0, 3 ), fly( 1, nil, 0/0, 3 ), diff )

-- Garbage collection test

-- Check if the current lua version supports garbage collection metamethod
local has_gc_meta
setmetatable( {}, { __gc = function() has_gc_meta = true end } )
collectgarbage( "collect" )
local function skipon51( a, b )
   if has_gc_meta then return a == b end
   return true, "skipped"
end

local gccount = 0
local x = fly( true, false )
x = setmetatable( x, { __gc = function( t ) gccount = gccount + 1 end } )

-- No collection if some reference is still around
collectgarbage( "collect" )
t( gccount, 0, skipon51 )

-- Automatic collection
x = nil
collectgarbage( "collect" )
t( gccount, 1, skipon51 )

t()