if not modules then modules = { } end modules ['luat-cbk'] = { version = 1.001, comment = "companion to luat-lib.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local insert, remove, concat = table.insert, table.remove, table.concat local find, format = string.find, string.format local collectgarbage, type, next = collectgarbage, type, next local round = math.round local sortedhash, sortedkeys, tohash = table.sortedhash, table.sortedkeys, table.tohash -- Callbacks are the real asset of LuaTeX. They permit you to hook your own code -- into the TeX engine. Here we implement a few handy auxiliary functions. Watch -- out, there are diferences between LuateX and LuaMetaTeX. callbacks = callbacks or { } local callbacks = callbacks -- When you (temporarily) want to install a callback function, and after a while -- wants to revert to the original one, you can use the following two functions. -- This only works for non-frozen ones. local trace_callbacks = false trackers.register("system.callbacks", function(v) trace_callbacks = v end) local trace_calls = false -- only used when analyzing performance and initializations local trace_checking = false trackers.register("memory.checking", function(v) trace_checking = v end) local report_system = logs.reporter("system") local report_callbacks = logs.reporter("system","callbacks") local report_memory = logs.reporter("system","memory") local register_callback = callback.register local find_callback = callback.find local list_callbacks = callback.list local register_usercall = false local original_register = register_callback local frozen = { } local stack = { } local list = callbacks.list local permit_overloads = false local block_overloads = false -- By now most callbacks are frozen and most provide a way to plug in your own code. -- For instance all node list handlers provide before/after namespaces and the file -- handling code can be extended by adding schemes and if needed I can add more -- hooks. So there is no real need to overload a core callback function. It might be -- ok for quick and dirty testing but anyway you're on your own if you permanently -- overload callback functions. -- This might become a configuration file only option when it gets abused too much. directives.register("system.callbacks.permitoverloads", function(v) if block_overloads or permit_overloads then -- once bad news, always bad news elseif v then permit_overloads = { } report_system() report_system("The callback system has been brought in an unprotected state. As a result of directly") report_system("setting of callbacks subsystems of ConTeXt can stop working. There is no support for") report_system("bugs resulting from this state. It's better to use the official extension mechanisms.") report_system() end end) sandbox.initializer { category = "functions", action = function() block_overloads = true end } if not list then -- otherwise counters get reset list = utilities.storage.allocate(list_callbacks()) local supported = { } for k in next, list do list[k] = 0 supported[k] = true end callbacks.list = list callbacks.supported = supported end local delayed = tohash { "buildpage_filter", } if trace_calls then local functions = { } register_callback = function(name,func) if type(func) == "function" then if functions[name] then functions[name] = func return find_callback(name) else functions[name] = func local cnuf = function(...) list[name] = list[name] + 1 return functions[name](...) end return original_register(name,cnuf) end else return original_register(name,func) end end end -- temporary, not public: callbacks.functions = { } -- till here local reported = { } local function register_usercall(what,name,func) if list[name] then if trace_callbacks or not reported[name] then report_system() report_system("disabling core code by %s user function into callback '%s' (reported only once)",what,name) report_system() reported[name] = true end permit_overloads[name] = true return original_register(name,function(...) if trace_callbacks then report_callbacks("calling user function from '%s'",name) end return func(...) end) else report_callbacks("not %s function into invalid callback '%s'",name) return nil, format("unknown callback '%s'",name) end end local function frozen_callback(name) report_callbacks("not %s frozen %a","registering",name) return nil, format("callback '%s' is frozen",name) -- no formatter yet end local function state(name) local f = find_callback(name) if f == false then return "disabled" elseif f then return "enabled" else return "undefined" end end function callbacks.known(name) return list[name] end function callbacks.report() for name, _ in sortedhash(list) do local str = frozen[name] if str then report_callbacks("%s: %s -> %s",state(name),name,str) else report_callbacks("%s: %s",state(name),name) end end end function callbacks.freeze(name,freeze) if not permit_overloads then freeze = type(freeze) == "string" and freeze if find(name,"*",1,true) then local pattern = name for name, _ in next, list do if find(name,pattern) then frozen[name] = freeze or frozen[name] or "frozen" end end else frozen[name] = freeze or frozen[name] or "frozen" end end end function callbacks.register(name,func,freeze) if frozen[name] then if permit_overloads then return register_usercall("registering",name,func) else return frozen_callback(name) end elseif freeze then frozen[name] = type(freeze) == "string" and freeze or "registered" end if delayed[name] and environment.initex then return nil end return register_callback(name,func) end function callback.register(name,func) -- original if not frozen[name] then return register_callback(name,func) elseif permit_overloads then return register_usercall("registering",name,func) else return frozen_callback(name) end end function callbacks.push(name,func) if not frozen[name] or permit_overloads then local sn = stack[name] if not sn then sn = { } stack[name] = sn end insert(sn,find_callback(name)) if permit_overloads then register_usercall("pushing",name,func) else register_callback(name,func) end else report_callbacks("not %s frozen %a","pushing",name) end end function callbacks.pop(name) if not frozen[name] or permit_overloads then local sn = stack[name] if not sn or #sn == 0 then -- some error register_callback(name,nil) -- ! really needed else -- this fails: register_callback(name, remove(stack[name])) local func = remove(sn) register_callback(name,func) end end end if trace_calls then statistics.register("callback details", function() local t = { } -- todo: pass function to register and quit at nil for name, n in sortedhash(list) do if n > 0 then t[#t+1] = format("%s -> %s",name,n) end end return concat(t," ") end) end statistics.register("callbacks overloaded by user", function() if permit_overloads then return concat(sortedkeys(permit_overloads)," ") end end) -- -- somehow crashes later on -- -- callbacks.freeze("find_.*_file","finding file") -- callbacks.freeze("read_.*_file","reading file") -- callbacks.freeze("open_.*_file","opening file") -- The simple case is to remove the callback: -- -- callbacks.push('linebreak_filter') -- ... some actions ... -- callbacks.pop('linebreak_filter') -- -- Often, in such case, another callback or a macro call will pop the original. -- -- In practice one will install a new handler, like in: -- -- callbacks.push('linebreak_filter', function(...) -- return something_done(...) -- end) -- -- Even more interesting is: -- -- callbacks.push('linebreak_filter', function(...) -- callbacks.pop('linebreak_filter') -- return something_done(...) -- end) -- -- This does a one-shot. -- -- Callbacks may result in Lua doing some hard work which takes time and above all -- resourses. Sometimes it makes sense to disable or tune the garbage collector in -- order to keep the use of resources acceptable. -- -- At some point in the development we did some tests with counting nodes (in this -- case 121049). -- -- setstepmul seconds megabytes -- 200 24.0 80.5 -- 175 21.0 78.2 -- 150 22.0 74.6 -- 160 22.0 74.6 -- 165 21.0 77.6 -- 125 21.5 89.2 -- 100 21.5 88.4 -- -- The following code is kind of experimental. In the documents that describe the -- development of LuaTeX we report on speed tests. One observation is that it -- sometimes helps to restart the collector. Okay, experimental code has been -- removed, because messing around with the gc is too unpredictable. -- -- For the moment we keep this here and not in util-gbc.lua or so. utilities = utilities or { } utilities.garbagecollector = utilities.garbagecollector or { } local garbagecollector = utilities.garbagecollector garbagecollector.enabled = false -- could become a directive garbagecollector.criterium = 4*1024*1024 -- garbagecollector.enabled = true -- Lua allocates up to 12 times the amount of memory needed for -- handling a string, and for large binary chunks (like chinese otf -- files) we get a prominent memory consumption. Even when a variable -- is nilled, there is some delay in freeing the associated memory (the -- hashed string) because if we do the same thing directly afterwards, -- we see only a slight increase in memory. For that reason it makes -- sense to do a collector pass after a huge file. -- -- test file: -- -- function test() -- local b = collectgarbage("count") -- local s = io.loaddata("some font table, e.g. a big tmc file") -- local a = collectgarbage("count") -- print(">>> STATUS",b,a,a-b,#s,1000*(a-b)/#s) -- end -- -- test() test() test() test() collectgarbage("collect") test() test() test() test() -- -- As a result of this, LuaTeX now uses an optimized version of f:read("*a"), -- one that does not use the 4K allocations but allocates in one step. function garbagecollector.check(size,criterium) if garbagecollector.enabled then criterium = criterium or garbagecollector.criterium if not size or (criterium and criterium > 0 and size > criterium) then if trace_checking then local b = collectgarbage("count") collectgarbage("collect") local a = collectgarbage("count") report_memory("forced sweep, collected: %s MB, used: %s MB",round((b-a)/1000),round(a/1000)) else collectgarbage("collect") end end end end -- this will move to a module commands = commands or { } function commands.showcallbacks() local NC, NR, verbatim = context.NC, context.NR, context.type context.starttabulate { "|l|l|p|" } for name, _ in sortedhash(list) do NC() verbatim(name) NC() verbatim(state(name)) NC() context(frozen[name] or "") NC() NR() end context.stoptabulate() end