1if not modules then modules = { } end modules ['core-uti'] = {
2 version = 1.001,
3 comment = "companion to core-uti.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12
13
14
15
16
17local math = math
18local next, type, tostring, tonumber, setmetatable, load = next, type, tostring, tonumber, setmetatable, load
19local format, match = string.format, string.match
20local concat, sortedkeys = table.concat, table.sortedkeys
21
22local definetable = utilities.tables.definetable
23local accesstable = utilities.tables.accesstable
24local migratetable = utilities.tables.migratetable
25local serialize = table.serialize
26local packers = utilities.packers
27local allocate = utilities.storage.allocate
28local mark = utilities.storage.mark
29
30local getrandom = utilities.randomizer.get
31local setrandomseedi = utilities.randomizer.setseedi
32local getrandomseed = utilities.randomizer.getseed
33
34local implement = interfaces.implement
35
36local texgetcount = tex.getcount
37
38local report_passes = logs.reporter("job","passes")
39
40job = job or { }
41local job = job
42
43job.version = 1.33
44job.packversion = 1.02
45
46
47
48
49
50local savelist, comment = { }, { }
51
52function job.comment(key,value)
53 if type(key) == "table" then
54 for k, v in next, key do
55 comment[k] = v
56 end
57 else
58 comment[key] = value
59 end
60end
61
62job.comment("version",job.version)
63
64local enabled = true
65local initialized = false
66
67directives.register("job.save",function(v) enabled = v end)
68
69function job.disablesave()
70 enabled = false
71end
72
73function job.initialize(loadname,savename)
74 if not initialized then
75 if not loadname or loadname == "" then
76 loadname = tex.jobname .. ".tuc"
77 end
78 if not savename or savename == "" then
79 savename = tex.jobname .. ".tua"
80 end
81 job.load(loadname)
82 luatex.registerstopactions(function()
83 if enabled then
84 job.save(savename)
85 end
86 end)
87 initialized = true
88 end
89end
90
91function job.register(collected, tobesaved, initializer, finalizer, serializer)
92 savelist[#savelist+1] = { collected, tobesaved, initializer, finalizer, serializer }
93end
94
95
96
97
98
99
100do
101
102 local tobesaved = allocate()
103 local collected = allocate()
104 local checksums = allocate()
105
106 local jobvariables = {
107 collected = collected,
108 tobesaved = tobesaved,
109 checksums = checksums,
110 }
111
112 job.variables = jobvariables
113
114 local function initializer()
115 checksums = jobvariables.checksums
116 end
117
118 job.register('job.variables.checksums', 'job.variables.checksums', initializer)
119
120 local rmethod, rvalue
121
122 statistics.register("randomizer", function()
123 if rmethod and rvalue then
124 return format("%s with value %s",rmethod,rvalue)
125 end
126 end)
127
128 local collectedmacros
129 local tobesavedmacros
130
131
132 local setmacro = token.setmacro
133
134 local function initializer(indeed)
135 if indeed then
136
137 collected = indeed
138 jobvariables.collected = indeed
139 end
140
141 rvalue = collected.randomseed
142 if not rvalue then
143 rvalue = getrandom("initialize")
144 setrandomseedi(rvalue)
145 rmethod = "initialized"
146 else
147 setrandomseedi(rvalue)
148 rmethod = "resumed"
149 end
150 collected.randomseed = rvalue
151 tobesaved.randomseed = rvalue
152
153 collectedmacros = collected.macros
154 tobesavedmacros = tobesaved.macros
155 if not collectedmacros then
156 collectedmacros = { }
157 collected.macros = collectedmacros
158 end
159 if not tobesavedmacros then
160 tobesavedmacros = { }
161 tobesaved.macros = tobesavedmacros
162 end
163
164 for cs, value in next, collectedmacros do
165 if type(value) == "string" then
166 setmacro(cs,value,"global")
167 end
168 end
169 end
170
171 job.register('job.variables.collected', tobesaved, initializer)
172
173 function jobvariables.save(cs,value)
174 tobesavedmacros[cs] = value
175 end
176
177 function jobvariables.restore(cs)
178 return collectedmacros[cs] or tobesavedmacros[cs]
179 end
180
181 function job.getrandomseed()
182 return tobesaved.randomseed or getrandomseed()
183 end
184
185
186
187 function jobvariables.getchecksum(tag)
188 return checksums[tag]
189 end
190
191 function jobvariables.makechecksum(data)
192 return data and md5.HEX(data)
193 end
194
195 function jobvariables.setchecksum(tag,checksum)
196 checksums[tag] = checksum
197 end
198
199end
200
201
202
203local packlist = {
204 "numbers",
205 "ownnumbers",
206 "metadata",
207 "sectiondata",
208 "prefixdata",
209 "numberdata",
210 "pagedata",
211 "directives",
212 "specification",
213 "processors",
214
215}
216
217local skiplist = {
218 "datasets",
219 "userdata",
220 "positions",
221
222}
223
224
225
226
227local deltapacking = false
228
229
230local function packnumberdata(tobesaved)
231 if deltapacking and tobesaved[1] then
232 local last
233 local current
234 for i=1,#tobesaved do
235 current = tobesaved[i]
236 if last then
237 if last.numbers and last.block then
238 for k, v in next, last do
239 if k ~= "numbers" and v ~= current[k] then
240 goto DIFFERENT
241 end
242 end
243 for k, v in next, current do
244 if k ~= "numbers" and v ~= last[k] then
245 goto DIFFERENT
246 end
247 end
248 tobesaved[i] = {
249 numbers = current.numbers,
250 }
251 goto CONTINUE
252 else
253 current = nil
254 end
255 end
256 ::DIFFERENT::
257 last = current
258 ::CONTINUE::
259 end
260 end
261end
262
263local function unpacknumberdata(collected)
264 if deltapacking and collected[1] then
265 local key = "numbers"
266 local last = collected[1]
267 local meta = false
268 for i=2,#collected do
269 local c = collected[i]
270 if c.block then
271 last = c
272 meta = false
273 elseif c.numbers then
274 if not meta then
275 meta = { __index = last }
276 end
277 setmetatable(c, meta)
278 end
279 end
280 end
281end
282
283
284
285
286
287
288local jobpacker = packers.new(packlist,job.packversion,skiplist)
289
290job.pack = true
291
292
293directives.register("job.pack",function(v) job.pack = v end)
294
295local savedfiles = { }
296local loadedfiles = { }
297local othercache = { }
298
299function job.save(filename)
300 statistics.starttiming(savedfiles)
301 local f = io.open(filename,'w')
302 if f then
303 f:write("local utilitydata = { }\n\n")
304 f:write(serialize(comment,"utilitydata.comment",true),"\n\n")
305 for l=1,#savelist do
306 local list = savelist[l]
307 local target = format("utilitydata.%s",list[1])
308 local data = list[2]
309 local finalizer = list[4]
310 local serializer = list[5]
311 if type(data) == "string" then
312 data = utilities.tables.accesstable(data)
313 end
314 if type(finalizer) == "function" then
315 finalizer()
316 end
317 if job.pack then
318 packers.pack(data,jobpacker,true)
319 end
320 local definer, name = definetable(target,true,true)
321 if serializer then
322 f:write(definer,"\n\n",serializer(data,name,true),"\n\n")
323 else
324 f:write(definer,"\n\n",serialize(data,name,true),"\n\n")
325 end
326 end
327 if job.pack then
328 packers.strip(jobpacker)
329 packnumberdata(jobpacker.index)
330 f:write(serialize(jobpacker,"utilitydata.job.packed",true),"\n\n")
331 end
332 f:write("return utilitydata")
333 f:close()
334 end
335 statistics.stoptiming(savedfiles)
336end
337
338local function load(filename)
339 if lfs.isfile(filename) then
340 local function dofile(filename)
341 local result = loadstring(io.loaddata(filename))
342 if result then
343 return result()
344 else
345 return nil
346 end
347 end
348 local okay, data = pcall(dofile,filename)
349 if okay and type(data) == "table" then
350 local jobversion = job.version
351 local datacomment = data.comment
352 local dataversion = datacomment and datacomment.version or "?"
353 if dataversion ~= jobversion then
354 report_passes("version mismatch: %s <> %s",dataversion,jobversion)
355 else
356 return data
357 end
358 else
359 os.remove(filename)
360 report_passes("removing stale job data file %a, restart job, message: %s",filename,tostring(data))
361 os.exit(true)
362 end
363 end
364end
365
366function job.load(filename)
367 statistics.starttiming(loadedfiles)
368 local utilitydata = load(filename)
369 if utilitydata then
370 local jobpacker = utilitydata.job.packed
371 if jobpacker then
372 unpacknumberdata(jobpacker.index)
373 for i=1,#savelist do
374 local list = savelist[i]
375 local target = list[1]
376 local result = accesstable(target,utilitydata)
377 if result then
378 local done = packers.unpack(result,jobpacker,true)
379 if done then
380 migratetable(target,mark(result))
381 else
382 report_passes("pack version mismatch")
383 end
384 end
385 end
386 end
387 end
388
389 for i=1,#savelist do
390 local list = savelist[i]
391 local target = list[1]
392 local initializer = list[3]
393 if type(initializer) == "function" then
394 initializer(utilitydata and accesstable(target,utilitydata) or nil)
395 end
396 end
397 statistics.stoptiming(loadedfiles)
398end
399
400function job.loadother(filename)
401 local jobname = environment.jobname
402 if filename == jobname then
403 return
404 end
405 filename = file.addsuffix(filename,"tuc")
406 local unpacked = othercache[filename]
407 if not unpacked then
408
409 statistics.starttiming(loadedfiles)
410 local utilitydata = load(filename)
411 if utilitydata then
412 report_passes("integrating list %a into %a",filename,jobname)
413 local jobpacker = utilitydata.job.packed
414 unpacknumberdata(jobpacker.index)
415 unpacked = { }
416 for l=1,#savelist do
417 local list = savelist[l]
418 local target = list[1]
419 local result = accesstable(target,utilitydata)
420 local done = packers.unpack(result,jobpacker,true)
421 if done then
422 migratetable(target,result,unpacked)
423 end
424 end
425 unpacked.job.packed = nil
426 othercache[filename] = unpacked
427
428 utilitydata.components, utilitydata.namestack = collectstructure(utilitydata.job.structure.collected)
429
430 structures.lists .integrate(utilitydata)
431 structures.registers .integrate(utilitydata)
432 structures.references.integrate(utilitydata)
433 end
434 statistics.stoptiming(loadedfiles)
435 end
436 return unpacked
437end
438
439statistics.register("startup time", function()
440 return statistics.elapsedseconds(statistics,"including runtime option file processing")
441end)
442
443statistics.register("jobdata time",function()
444 local elapsedsave = statistics.elapsedtime(savedfiles)
445 local elapsedload = statistics.elapsedtime(loadedfiles)
446 if enabled then
447 if next(othercache) then
448 return format("%s seconds saving, %s seconds loading, other files: %s",elapsedsave,elapsedload,concat(sortedkeys(othercache), ", "))
449 else
450 return format("%s seconds saving, %s seconds loading",elapsedsave,elapsedload)
451 end
452 else
453 if next(othercache) then
454 return format("nothing saved, %s seconds loading, other files: %s",elapsedload,concat(sortedkeys(othercache), ", "))
455 else
456 return format("nothing saved, %s seconds loading",elapsedload)
457 end
458 end
459end)
460
461statistics.register("callbacks", function()
462 local backend = backends.getcallbackstate()
463 local frontend = status.getcallbackstate()
464 local pages = structures.pages.nofpages or 0
465 local total = frontend.count + backend.count
466 local average = pages > 0 and math.round(total/pages) or 0
467 local result = format (
468 "file: %s, saved: %s, direct: %s, function: %s, value: %s, message: %s, bytecode: %s, late %s, total: %s (%s per page)",
469 frontend.file, frontend.saved, frontend.direct, frontend["function"],
470 frontend.value, frontend.message, frontend.bytecode, backend.count,
471 total, average
472 )
473 statistics.callbacks = function()
474 return result
475 end
476 return result
477end)
478
479function statistics.formatruntime(runtime)
480 if not environment.initex then
481
482 local shipped = texgetcount("nofshipouts")
483 local pages = texgetcount("realpageno")
484 if pages > shipped then
485 pages = shipped
486 end
487 runtime = tonumber(runtime)
488 if shipped > 0 or pages > 0 then
489 local persecond = (runtime > 0) and (shipped/runtime) or pages
490 if pages == 0 then
491 pages = shipped
492 end
493 return format("%0.3f seconds, %i processed pages, %i shipped pages, %.3f pages/second",runtime,pages,shipped,persecond)
494 else
495 return format("%0.3f seconds",runtime)
496 end
497 end
498end
499
500implement {
501 name = "savecurrentvalue",
502 public = true,
503 actions = job.variables.save,
504 arguments = { "csname", "argument" },
505}
506
507implement {
508 name = "setjobcomment",
509 actions = job.comment,
510 arguments = { { "*" } }
511}
512
513implement {
514 name = "initializejob",
515 actions = job.initialize
516}
517
518implement {
519 name = "disablejobsave",
520 actions = job.disablesave
521}
522 |