node-nut.lua /size: 25 Kb    last modification: 2021-10-28 13:50
1
    if not modules then modules = { } end modules ['node-nut'] = {
2    version   = 1.001,
3    comment   = "companion to node-ini.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-- Here starts some more experimental code that Luigi and I use in a next stage of
10-- exploring and testing potential speedups in the engines. This code is not meant
11-- for users and can change (or be removed) any moment. During the experiments I'll
12-- do my best to keep the code as fast as possible by using two codebases. See
13-- about-fast.pdf for some more info about impacts. Although key based access has
14-- more charm, function based is somewhat faster and has more potential for future
15-- speedups.
16
17-- This next iteration is flagged direct because we avoid user data which has a price
18-- in allocation and metatable tagging. Although in this stage we pass numbers around
19-- future versions might use light user data, so never depend on what direct function
20-- return. Using the direct approach had some speed advantages but you loose the key
21-- based access. The speed gain is only measurable in cases with lots of access. For
22-- instance when typesettign arabic with advanced fonts, we're talking of many millions
23-- of function calls and there we can get a 30\% or more speedup. On average complex
24-- \CONTEXT\ runs the gain can be 10\% to 15\% percent. Because mixing the two models
25-- (here we call then nodes and nuts) is not possible you need to cast either way which
26-- has a penalty. Also, error messages in nuts mode are less clear and \LUATEX\ will
27-- often simply abort when you make mistakes of mix the models. So, development (at least
28-- in \CONTEXT) can be done in node mode and not in nuts mode. Only robust code will
29-- be turned nuts afterwards and quite likely not all code. The official \LUATEX\ api
30-- to nodes is userdata!
31--
32-- Listening to 'lunatic soul' at the same time helped wrapping my mind around the mixed
33-- usage of both models. Just for the record: the potential of the direct approach only
34-- became clear after experimenting for weeks and partly adapting code. It is one of those
35-- (sub)projects where you afterwards wonder if it was worth the trouble, but users that
36-- rely on lots of complex functionality and font support will probably notice the speedup.
37--
38--                                luatex                    luajittex
39-- -------------    -----    --------------------     ---------------------------------
40-- name             pages     old   new       pct      old         new              pct
41-- -------------    -----    --------------------     ---------------------------------
42-- fonts-mkiv         166     9.3   7.7/7.4  17.2      7.4 (37.5)  5.9/5.7 (55.6)  20.3
43-- about               60     3.3   2.7/2.6  20.4      2.5 (39.5)  2.1     (57.0)  23.4
44-- arabic-001          61    25.3  15.8      18.2     15.3 (46.7)  6.8     (54.7)  16.0
45-- torture-001        300    21.4  11.4      24.2     13.9 (35.0)  6.3     (44.7)  22.2
46--
47-- so:
48--
49-- - we run around 20% faster on documents of average complexity and gain more when
50--   dealing with scripts like arabic and such
51-- - luajittex benefits a bit more so a luajittex job can (in principle) now be much
52--   faster
53-- - if we reason backwards, and take luajittex as norm we get 1:2:3 on some jobs for
54--   luajittex direct:luatex direct:luatex normal i.e. we can be 3 times faster
55-- - keep in mind that these are tex/lua runs so the real gain at the lua end is much
56--   larger
57--
58-- Because we can fake direct mode a little bit by using the fast getfield and setfield
59-- at the cost of wrapped getid and alike, we still are running quite ok. As we could gain
60-- some 5% with fast mode, we can sacrifice some on wrappers when we use a few fast core
61-- functions. This means that simulated direct mode runs font-mkiv in 9.1 seconds (we could
62-- get down to 8.7 seconds in fast mode) and that we can migrate slowely to direct mode.
63--
64-- The following measurements are from 2013-07-05 after adapting some 47 files to nuts. Keep
65-- in mind that the old binary can fake a fast getfield and setfield but that the other
66-- getters are wrapped functions. The more we have, the slower it gets.
67--
68--                                           fonts   about   arabic
69-- old mingw, indexed plus some functions :   8.9     3.2     20.3
70-- old mingw, fake functions              :   9.9     3.5     27.4
71-- new mingw, node functions              :   9.0     3.1     20.8
72-- new mingw, indexed plus some functions :   8.6     3.1     19.6
73-- new mingw, direct functions            :   7.5     2.6     14.4
74--
75-- \starttext \dorecurse{1000}{test\page} \stoptext :
76--
77-- luatex    560 pps
78-- luajittex 600 pps
79--
80-- \setupbodyfont[pagella]
81--
82-- \edef\zapf{\cldcontext{context(io.loaddata(resolvers.findfile("zapf.tex")))}}
83--
84-- \starttext \dorecurse{1000}{\zapf\par} \stoptext
85--
86-- luatex    3.9 sec / 54 pps
87-- luajittex 2.3 sec / 93 pps
88
89local type, rawget = type, rawget
90
91local nodes      = nodes
92local direct     = node.direct
93
94local fastcopy   = table.fastcopy
95
96local nodecodes  = nodes.nodecodes
97local hlist_code = nodecodes.hlist
98local vlist_code = nodecodes.vlist
99local glyph_code = nodecodes.glyph
100
101local nuts       = nodes.nuts or { }
102nodes.nuts       = nuts
103
104nodes.isnode     = direct.isnode   or function() return true  end
105nodes.isdirect   = direct.isdirect or function() return false end
106nodes.isnut      = nodes.isdirect
107
108-- casters
109
110local tonode     = direct.tonode   or function(n) return n end
111local tonut      = direct.todirect or function(n) return n end
112
113nuts.tonode      = tonode
114nuts.tonut       = tonut
115
116nodes.tonode     = tonode
117nodes.tonut      = tonut
118
119-- helpers
120
121local nuts                   = nodes.nuts
122
123nuts.checkdiscretionaries    = direct.check_discretionaries
124nuts.copy                    = direct.copy
125nuts.copynode                = direct.copy
126nuts.copyonly                = direct.copy_only or direct.copy
127nuts.copylist                = direct.copy_list
128nuts.count                   = direct.count
129nuts.currentattribute        = direct.current_attr
130nuts.currentattr             = direct.current_attr
131nuts.delete                  = direct.delete
132nuts.dimensions              = direct.dimensions
133nuts.endofmath               = direct.end_of_math
134nuts.findattribute           = direct.find_attribute
135nuts.firstglyph              = direct.first_glyph
136nuts.flattendiscretionaries  = direct.flatten_discretionaries
137nuts.flush                   = direct.flush_node
138nuts.flushlist               = direct.flush_list
139nuts.flushnode               = direct.flush_node
140nuts.free                    = direct.free
141nuts.getsynctexfields        = direct.get_synctex_fields
142nuts.hasattribute            = direct.has_attribute
143nuts.hasfield                = direct.has_field
144nuts.hasglyph                = direct.has_glyph or direct.first_glyph
145nuts.hpack                   = direct.hpack
146nuts.insertafter             = direct.insert_after
147nuts.insertbefore            = direct.insert_before
148nuts.isdirect                = direct.is_direct
149nuts.isnode                  = direct.is_node
150nuts.isnut                   = direct.is_direct
151nuts.kerning                 = direct.kerning
152nuts.hyphenating             = direct.hyphenating
153nuts.lastnode                = direct.last_node
154nuts.length                  = direct.length
155nuts.ligaturing              = direct.ligaturing
156nuts.new                     = direct.new
157nuts.protectglyph            = direct.protect_glyph
158nuts.protectglyphs           = direct.protect_glyphs
159nuts.protrusionskippable     = direct.protrusion_skippable
160nuts.rangedimensions         = direct.rangedimensions
161nuts.setattribute            = direct.set_attribute
162nuts.setsynctexfields        = direct.set_synctex_fields
163nuts.slide                   = direct.slide
164nuts.tail                    = direct.tail
165nuts.tostring                = direct.tostring
166nuts.traverse                = direct.traverse
167nuts.traversechar            = direct.traverse_char
168nuts.traverseglyph           = direct.traverse_glyph
169nuts.traverseid              = direct.traverse_id
170nuts.traverselist            = direct.traverse_list
171nuts.unprotectglyph          = direct.unprotect_glyph
172nuts.unprotectglyphs         = direct.unprotect_glyphs
173nuts.unsetattribute          = direct.unset_attribute
174nuts.unsetattribute          = direct.unset_attribute
175nuts.usedlist                = direct.usedlist
176nuts.usesfont                = direct.uses_font
177nuts.vpack                   = direct.vpack
178nuts.write                   = direct.write
179nuts.mlisttohlist            = direct.mlist_to_hlist
180nuts.hasdimensions           = direct.has_dimensions
181nuts.startofpar              = direct.start_of_par
182nuts.migrate                 = direct.migrate
183
184if not nuts.mlisttohlist then
185
186    local n_mlisttohlist = node.mlist_to_hlist
187
188    function nuts.mlisttohlist(head,...)
189        if head then
190            local head = n_mlisttohlist(tonode(head),...)
191            if head then
192                return tonut(head)
193            end
194        end
195    end
196
197end
198
199if not nuts.hasdimensions then
200
201    local getwhd = direct.getwhd
202
203    function nuts.hasdimensions(n)
204        local wd, ht, dp = getwhd(n)
205        return wd ~= 0 or (ht + dp) ~= 0
206    end
207
208end
209
210local getfield        = direct.getfield
211local setfield        = direct.setfield
212
213nuts.getfield         = getfield
214nuts.setfield         = setfield
215
216nuts.getnext          = direct.getnext
217nuts.setnext          = direct.setnext
218
219nuts.getid            = direct.getid
220
221nuts.getprev          = direct.getprev
222nuts.setprev          = direct.setprev
223
224local getattribute    = direct.get_attribute
225local setattribute    = direct.set_attribute
226local unsetattribute  = direct.unset_attribute
227
228nuts.getattr          = getattribute
229nuts.setattr          = setattribute
230nuts.takeattr         = unsetattribute -- ?
231
232nuts.getattribute     = getattribute
233nuts.setattribute     = setattribute
234nuts.unsetattribute   = unsetattribute -- ?
235
236nuts.iszeroglue       = direct.is_zero_glue
237nuts.effectiveglue    = direct.effective_glue
238
239nuts.getglue          = direct.getglue
240nuts.setglue          = direct.setglue
241nuts.getboxglue       = direct.getglue
242nuts.setboxglue       = direct.setglue
243
244nuts.getdisc          = direct.getdisc
245nuts.setdisc          = direct.setdisc
246nuts.getdiscretionary = direct.getdisc
247nuts.setdiscretionary = direct.setdisc
248
249nuts.getpre           = direct.getpre
250nuts.setpre           = direct.setpre
251nuts.getpost          = direct.getpost
252nuts.setpost          = direct.setpost
253nuts.getreplace       = direct.getreplace
254nuts.setreplace       = direct.setreplace
255
256local getdata         = direct.getdata
257local setdata         = direct.setdata
258
259nuts.getdata          = getdata
260nuts.setdata          = setdata
261nuts.getvalue         = getdata
262nuts.setvalue         = setdata
263
264nuts.getexpansion     = direct.getexpansion
265nuts.setexpansion     = direct.setexpansion
266
267nuts.getwhd           = direct.getwhd
268nuts.setwhd           = direct.setwhd
269nuts.getwidth         = direct.getwidth
270nuts.setwidth         = direct.setwidth
271nuts.getheight        = direct.getheight
272nuts.setheight        = direct.setheight
273nuts.getdepth         = direct.getdepth
274nuts.setdepth         = direct.setdepth
275nuts.getshift         = direct.getshift
276nuts.setshift         = direct.setshift
277nuts.gettotal         = direct.gettotal
278
279-- lmtx compatibility
280
281nuts.getorientation   = direct.getorientation or function() end
282nuts.setorientation   = direct.setorientation or function() end
283
284nuts.getglyphdata     = direct.getglyphdata or               getattribute
285nuts.setglyphdata     = direct.setglyphdata or function(n,d) setattribute(n,0,d) end
286
287nuts.getruledata      = direct.getglyphdata and getdata or function(n)   return getfield(n,"transform")   end
288nuts.setruledata      = direct.setglyphdata and setdata or function(n,d) return setfield(n,"transform",d) end
289
290-- maybe some day: [g|s]etglyphoptions and then use attribute for mkiv / generic but not now
291
292nuts.getoptions       = direct.getoptions or function() return 0 end
293nuts.setoptions       = direct.setoptions or function() end
294
295-- so far
296
297nuts.getnucleus       = direct.getnucleus
298nuts.setnucleus       = direct.setnucleus
299nuts.getsup           = direct.getsup
300nuts.setsup           = direct.setsup
301nuts.getsub           = direct.getsub
302nuts.setsub           = direct.setsub
303nuts.getsuppre        = direct.getsuppre
304nuts.setsuppre        = direct.setsuppre
305nuts.getsubpre        = direct.getsubpre
306nuts.setsubpre        = direct.setsubpre
307
308nuts.getchar          = direct.getchar
309nuts.setchar          = direct.setchar
310nuts.getfont          = direct.getfont
311nuts.setfont          = direct.setfont
312nuts.getfam           = direct.getfam
313nuts.setfam           = direct.setfam
314
315nuts.getboth          = direct.getboth
316nuts.setboth          = direct.setboth
317nuts.setlink          = direct.setlink
318nuts.exchange         = direct.exchange
319nuts.reverse          = direct.reverse
320nuts.setsplit         = direct.setsplit
321
322nuts.getlist          = direct.getlist -- only hlist and vlist !
323nuts.setlist          = direct.setlist
324nuts.getleader        = direct.getleader
325nuts.setleader        = direct.setleader
326nuts.getcomponents    = direct.getcomponents
327nuts.setcomponents    = direct.setcomponents
328
329nuts.getsubtype       = direct.getsubtype
330nuts.setsubtype       = direct.setsubtype
331
332nuts.getlang          = direct.getlang
333nuts.setlang          = direct.setlang
334nuts.getlanguage      = direct.getlang
335nuts.setlanguage      = direct.setlang
336
337nuts.getattrlist      = direct.getattributelist
338nuts.setattrlist      = direct.setattributelist
339nuts.getattributelist = direct.getattributelist
340nuts.setattributelist = direct.setattributelist
341
342nuts.getoffsets       = direct.getoffsets
343nuts.setoffsets       = direct.setoffsets
344
345nuts.getkern          = direct.getkern
346nuts.setkern          = direct.setkern
347
348nuts.getdir           = direct.getdir
349nuts.setdir           = direct.setdir
350
351nuts.getdirection     = direct.getdirection
352nuts.setdirection     = direct.setdirection
353
354nuts.getpenalty       = direct.getpenalty
355nuts.setpenalty       = direct.setpenalty
356
357nuts.getbox           = direct.getbox
358nuts.setbox           = direct.setbox
359
360nuts.ischar           = direct.is_char
361nuts.isglyph          = direct.is_glyph
362
363local d_remove_node   = direct.remove
364local d_flushnode     = direct.flush_node
365local d_getnext       = direct.getnext
366local d_getprev       = direct.getprev
367local d_getid         = direct.getid
368local d_getlist       = direct.getlist
369local d_find_tail     = direct.tail
370local d_insertafter   = direct.insert_after
371local d_insertbefore  = direct.insert_before
372local d_slide         = direct.slide
373local d_traverse      = direct.traverse
374local d_setlink       = direct.setlink
375local d_setboth       = direct.setboth
376local d_getboth       = direct.getboth
377
378local remove = function(head,current,free_too)
379    if current then
380        local h, c = d_remove_node(head,current)
381        if free_too then
382            d_flushnode(current)
383            return h, c
384        else
385            d_setboth(current)
386            return h, c, current
387        end
388    end
389    return head, current
390end
391
392-- for now
393
394if not nuts.startofpar then
395
396    local parcodes      = nodes.parcodes
397    local hmodepar_code = parcodes.vmode_par
398    local vmodepar_code = parcodes.hmode_par
399
400    local getsubtype    = nuts.getsubtype
401
402    function nuts.startofpar(n)
403        local s = getsubtype(n)
404        return s == hmodepar_code or s == vmodepar_code
405    end
406
407end
408
409-- for now
410
411if not nuts.exchange then
412
413    local d_getprev = direct.getprev
414    local d_getnext = direct.getnext
415    local d_setlink = direct.setlink
416
417    function nuts.exchange(head,first,second)
418        if first then
419            if not second then
420                second = d_getnext(first)
421            end
422            if second then
423                d_setlink(d_getprev(first),second,first,d_getnext(second))
424                if first == head then
425                    return second
426                end
427            end
428        end
429        return head
430    end
431
432end
433
434-- for now
435
436if not nuts.getpre then
437
438    local d_getdisc  = direct.getdisc
439    local d_setfield = direct.setfield
440
441    function nuts.getpre    (n) local h, _, _, t, _, _ = d_getdisc(n,true) return h, t end
442    function nuts.getpost   (n) local _, h, _, _, t, _ = d_getdisc(n,true) return h, t end
443    function nuts.getreplace(n) local _, _, h, _, _, t = d_getdisc(n,true) return h, t end
444
445    function nuts.setpre    (n,h) d_setfield(n,"pre",    h) end
446    function nuts.setpost   (n,h) d_setfield(n,"post",   h) end
447    function nuts.setreplace(n,h) d_setfield(n,"replace",h) end
448
449end
450
451if not nuts.gettotal then
452
453    local d_getheight = direct.getheight
454    local d_getdepth  = direct.getdepth
455
456    function nuts.gettotal(n)
457        return (d_getheight(n) or 0) + (d_getdepth(n) or 0)
458    end
459
460end
461
462-- alias
463
464nuts.getsurround = nuts.getkern
465nuts.setsurround = nuts.setkern
466
467nuts.remove = remove
468
469function nuts.delete(head,current)
470    return remove(head,current,true)
471end
472
473function nuts.replace(head,current,new) -- no head returned if false
474    if not new then
475        head, current, new = false, head, current
476    end
477    local prev, next = d_getboth(current)
478    if prev or next then
479        d_setlink(prev,new,next)
480    end
481    if head then
482        if head == current then
483            head = new
484        end
485        d_flushnode(current)
486        return head, new
487    else
488        d_flushnode(current)
489        return new
490    end
491end
492
493local function countall(stack,flat)
494    local n = 0
495    while stack do
496        local id = d_getid(stack)
497        if not flat and id == hlist_code or id == vlist_code then
498            local list = d_getlist(stack)
499            if list then
500                n = n + 1 + countall(list) -- self counts too
501            else
502                n = n + 1
503            end
504        else
505            n = n + 1
506        end
507        stack = d_getnext(stack)
508    end
509    return n
510end
511
512nuts.countall = countall
513
514function nodes.countall(stack,flat)
515    return countall(tonut(stack),flat)
516end
517
518function nuts.append(head,current,...)
519    for i=1,select("#",...) do
520        head, current = d_insertafter(head,current,(select(i,...)))
521    end
522    return head, current
523end
524
525function nuts.prepend(head,current,...)
526    for i=1,select("#",...) do
527        head, current = d_insertbefore(head,current,(select(i,...)))
528    end
529    return head, current
530end
531
532function nuts.linked(...) -- slides !
533    local head, last
534    for i=1,select("#",...) do
535        local next = select(i,...)
536        if next then
537            if head then
538                d_setlink(last,next)
539            else
540                head = next
541            end
542            last = d_find_tail(next) -- we could skip the last one
543        end
544    end
545    return head
546end
547
548function nuts.concat(list) -- consider tail instead of slide
549    local head, tail
550    for i=1,#list do
551        local li = list[i]
552        if li then
553            if head then
554                d_setlink(tail,li)
555            else
556                head = li
557            end
558            tail = d_slide(li)
559        end
560    end
561    return head, tail
562end
563
564function nuts.reference(n)
565    return n or "<none>"
566end
567
568-- quick and dirty tracing of nuts
569
570-- for k, v in next, nuts do
571--     if string.find(k,"box") then
572--         nuts[k] = function(...) print(k,...) return v(...) end
573--     end
574-- end
575
576function nodes.vianuts (f) return function(n,...) return tonode(f(tonut (n),...)) end end
577function nodes.vianodes(f) return function(n,...) return tonut (f(tonode(n),...)) end end
578
579nuts.vianuts  = nodes.vianuts
580nuts.vianodes = nodes.vianodes
581
582function nodes.insertlistafter(h,c,n)
583    local t = n_tail(n)
584    if c then
585        local cn = n_getnext(c)
586        if cn then
587            -- no setboth here yet
588            n_setfield(t,"next",cn)
589            n_setfield(cn,"prev",t)
590        else
591            n_setfield(t,"next",nil)
592        end
593        n_setfield(c,"next",n)
594        n_setfield(n,"prev",c)
595        return h, n
596    end
597    return n, t
598end
599
600function nuts.insertlistafter(h,c,n)
601    local t = d_tail(n)
602    if c then
603        local cn = d_getnext(c)
604        if cn then
605            d_setlink(t,cn)
606        else
607            d_setnext(t)
608        end
609        d_setlink(c,n)
610        return h, n
611    end
612    return n, t
613end
614
615-- test code only
616
617-- collectranges and mix
618
619local report = logs.reporter("sliding")
620
621local function message(detail,head,current,previous)
622    report("error: %s, current: %s:%s, previous: %s:%s, list: %s, text: %s",
623        detail,
624        nodecodes[d_getid(current)],
625        current,
626        nodecodes[d_getid(previous)],
627        previous,
628        nodes.idstostring(head),
629        nodes.listtoutf(head)
630    )
631    utilities.debugger.showtraceback(report)
632end
633
634local function warn()
635    report()
636    report("warning: the slide tracer is enabled")
637    report()
638    warn = false
639end
640
641local function tracedslide(head)
642    if head then
643        if warn then
644            warn()
645        end
646        local next = d_getnext(head)
647        if next then
648            local prev = head
649            for n in d_traverse(next) do
650                local p = d_getprev(n)
651                if not p then
652                    message("unset",head,n,prev)
653                 -- break
654                elseif p ~= prev then
655                    message("wrong",head,n,prev)
656                 -- break
657                end
658                prev = n
659            end
660        end
661        return d_slide(head)
662    end
663end
664
665local function nestedtracedslide(head,level) -- no sliding !
666    if head then
667        if warn then
668            warn()
669        end
670        local id = d_getid(head)
671        local next = d_getnext(head)
672        if next then
673            report("%whead:%s",level or 0,nodecodes[id])
674            local prev = head
675            for n in d_traverse(next) do
676                local p = d_getprev(n)
677                if not p then
678                    message("unset",head,n,prev)
679                 -- break
680                elseif p ~= prev then
681                    message("wrong",head,n,prev)
682                 -- break
683                end
684                prev = n
685                local id = d_getid(n)
686                if id == hlist_code or id == vlist_code then
687                    nestedtracedslide(d_getlist(n),(level or 0) + 1)
688                end
689            end
690        elseif id == hlist_code or id == vlist_code then
691            report("%wlist:%s",level or 0,nodecodes[id])
692            nestedtracedslide(d_getlist(head),(level or 0) + 1)
693        end
694     -- return d_slide(head)
695    end
696end
697
698local function untracedslide(head)
699    if head then
700        if warn then
701            warn()
702        end
703        local next = d_getnext(head)
704        if next then
705            local prev = head
706            for n in d_traverse(next) do
707                local p = d_getprev(n)
708                if not p then
709                    return "unset", d_getid(n)
710                elseif p ~= prev then
711                    return "wrong", d_getid(n)
712                end
713                prev = n
714            end
715        end
716        return d_slide(head)
717    end
718end
719
720nuts.tracedslide       = tracedslide
721nuts.untracedslide     = untracedslide
722nuts.nestedtracedslide = nestedtracedslide
723
724-- this might move
725
726local propertydata = direct.get_properties_table(true)
727
728local getattr = nuts.getattr
729local setattr = nuts.setattr
730
731nodes.properties = {
732    data = propertydata,
733}
734
735if direct.set_properties_mode then
736    direct.set_properties_mode(true,true)  -- create metatable, slower but needed for font-otj.lua (unless we use an intermediate table)
737    function direct.set_properties_mode() end
738end
739
740-- experimental code with respect to copying attributes has been removed
741-- as it doesn't pay of (most attributes are only accessed once anyway)
742
743nuts.getprop = function(n,k)
744    local p = propertydata[n]
745    if p then
746        if k then
747            return p[k]
748        else
749            return p
750        end
751    end
752end
753
754nuts.rawprop = function(n,k)
755    local p = rawget(propertydata,n)
756    if p then
757        if k then
758            return p[k]
759        else
760            return p
761        end
762    end
763end
764
765nuts.setprop = function(n,k,v)
766    local p = propertydata[n]
767    if p then
768        p[k] = v
769    else
770        propertydata[n] = { [k] = v }
771    end
772end
773
774nuts.theprop = function(n)
775    local p = propertydata[n]
776    if not p then
777        p = { }
778        propertydata[n] = p
779    end
780    return p
781end
782
783local getstate = direct.getstate
784local setstate = direct.setstate
785
786if not setstate or not getstate then
787
788    setstate = function(n,v)
789        local p = propertydata[n]
790        if p then
791            p.state = v
792        else
793            propertydata[n] = { state = v }
794        end
795    end
796
797    getstate = function(n,v)
798        local p = propertydata[n]
799        if p then
800            if v then
801                return p.state == v
802            else
803                return p.state
804            end
805        else
806            return nil
807        end
808    end
809end
810
811nuts.setstate = setstate
812nuts.getstate = getstate
813
814local getscript = direct.getscript or function(n,v) end -- elsewhere
815local setscript = direct.setscript or function(n,v) end -- elsewhere
816
817nuts.setscript = setscript
818nuts.getscript = getscript
819
820function nuts.isdone(n,k)
821    local p = propertydata[n]
822    if not p then
823        propertydata[n] = { [k] = true }
824        return false
825    end
826    local v = p[k]
827    if v == nil then
828        propertydata[n] = { [k] = true }
829        return false
830    end
831    return v
832end
833
834function nuts.copy_properties(source,target,what)
835    local newprops = propertydata[source]
836    if not newprops then
837        -- nothing to copy
838        return
839    end
840    if what then
841        -- copy one category
842        newprops = rawget(source,what)
843        if newprops then
844            newprops = fastcopy(newprops)
845            local p = rawget(propertydata,target)
846            if p then
847                p[what] = newprops
848            else
849                propertydata[target] = {
850                    [what] = newprops,
851                }
852            end
853        end
854    else
855        -- copy all properties
856        newprops = fastcopy(newprops)
857        propertydata[target] = newprops
858    end
859    return newprops -- for checking
860end
861