[Closed] The Challenge >>> Biped Figure Mode
Yes, I know that this site is dying as a professional resource. But according to the old tradition, I want to offer one more challenge.
Imagine I have a CheckButton “Biped In Figure Mode” in the MXS dialog (simple Rollout).
This button should always be ON if any Biped system is in figure mode, and OFF otherwise.
Obviously, we need to track events … but which ones? what is the safest way to keep track of all related events and callbacks?
How do we set up a minimal but sufficient kind of callbacks to track this task?
try(destroydialog FigureModeCheck) catch()
rollout FigureModeCheck "Figure Mode Check" width:191
(
checkbutton figure_mode_cb "Figure Mode" width:180 align:#center
on FigureModeCheck open do
(
b = for obj in objects where iskindof obj.controller Vertical_Horizontal_Turn do exit with obj
if isvalidnode b do figure_mode_cb.state = b.controller.figureMode
)
)
createdialog FigureModeCheck
this is a good test to check how well you know the event mechanism in MAX
Not very elegant, but it kinda works
try(destroydialog FigureModeCheck) catch()
rollout FigureModeCheck "Figure Mode Check" width:191
(
checkbutton figure_mode_cb "Figure Mode" width:180 align:#center
local bipeds = #()
fn UpdateCheckbox =
(
local state = false
bipeds = for n in bipeds where isValidNode n collect ( state = state or n.controller.figureMode; n )
if state != figure_mode_cb.checked do figure_mode_cb.checked = state
)
fn OnEvent ev nodes =
(
local complete = false
for handle in nodes while not complete where isKindOf (getAnimByHandle handle) Biped_Object do ( complete = true; UpdateCheckbox() )
)
fn OnAdded ev nodes =
(
local found = false
for n in nodes while not found do
(
local new_node = getAnimByHandle n
if isKindOf new_node Biped_Object do
(
found = true
for n in getClassInstances Vertical_Horizontal_Turn do appendIfUnique bipeds n.rootnode
UpdateCheckbox()
)
)
)
fn OnDeleted ev nodes =
(
local complete = false
for n in bipeds while not complete where not isValidNode n do ( complete = true; UpdateCheckbox() )
)
local handler = NodeEventCallback enabled:true controllerOtherEvent:OnEvent added:OnAdded deleted:OnDeleted
on FigureModeCheck open do
(
for n in getClassInstances Vertical_Horizontal_Turn do appendIfUnique bipeds n.rootnode
UpdateCheckbox()
)
on FigureModeCheck close do
(
handler = undefined; gc light:true
)
)
createdialog FigureModeCheck
Not too bad, neither too good. There must be a way to minimize the calls even more.
try(destroyDialog ::RO_FIGUREMODECHECK) catch()
rollout RO_FIGUREMODECHECK "Figure Mode Check" width:200
(
checkbutton figure_mode_cb "Figure Mode" width:180 height:32
local calls = 0
local instances = #()
local nodesCallback = undefined
fn UpdateUI =
(
mode = false
for ctrl in instances do mode = mode or ctrl.figureMode
figure_mode_cb.state = mode
format "mode:%\tcalls:%\n" mode (calls+=1)
)
fn UpdateInstances event node =
(
deleteAllChangeHandlers id:#ID_0X712292CE
instances = getClassInstances Vertical_Horizontal_Turn
when parameters instances changes id:#ID_0X712292CE handleAt:#redrawViews do UpdateUI()
UpdateUI()
)
on RO_FIGUREMODECHECK open do
(
UpdateInstances 0 0
nodesCallback = NodeEventCallback added:UpdateInstances deleted:UpdateInstances
)
on RO_FIGUREMODECHECK close do
(
deleteAllChangeHandlers id:#ID_0X712292CE
nodesCallback = undefined
gc light:on
)
)
createDialog ::RO_FIGUREMODECHECK
damn
So much less code and it is way simpler. I didn’t know the fact that you can use when construct with collections.
we’re on the same track … use NodeEventCallback to track scene changes and when context to track Figure mode change.
now let’s get down to optimization.
getClassInstances is slow method. To find a specified class instances we enumerate ALL animatables. It’s faster to find Bip roots than all Vertical_Horizontal_Turn controllers
fn getBipBodies = (for obj in geometry where iskindof (c = obj.controller) Vertical_Horizontal_Turn collect c)
(
t0 = timestamp()
h0 = heapfree
for k=1 to 1000 do
(
--getClassInstances Vertical_Horizontal_Turn
--getbipbodies()
)
format "time:% heap:%\n" (timestamp() - t0) (h0 - heapfree)
)
if we call getClassInstances just once it always wins, even in situations when there’re thousands of nodes present in a scene. I don’t have a real big project to test if it is still a true in general.
Timings
calls | nodes | getBipBodies | getClassInstances
1 | 1 | 0 | 1
3 | 1 | 0 | 0
5 | 1 | 0 | 1
10 | 1 | 0 | 2
1 | 10 | 1 | 0
3 | 10 | 0 | 1
5 | 10 | 0 | 1
10 | 10 | 0 | 3
1 | 100 | 2 | 1
3 | 100 | 1 | 2
5 | 100 | 2 | 4
10 | 100 | 2 | 8
1 | 1000 | 21 | 8
3 | 1000 | 11 | 23
5 | 1000 | 18 | 38
10 | 1000 | 33 | 75
1 | 5000 | 103 | 38
3 | 5000 | 60 | 113
5 | 5000 | 99 | 191
10 | 5000 | 197 | 379
1 | 6000 | 124 | 46
3 | 6000 | 71 | 139
5 | 6000 | 119 | 230
10 | 6000 | 236 | 458
1 | 7000 | 149 | 55
3 | 7000 | 83 | 163
5 | 7000 | 138 | 275
10 | 7000 | 278 | 544
1 | 8000 | 165 | 64
3 | 8000 | 94 | 186
5 | 8000 | 156 | 307
10 | 8000 | 313 | 615
1 | 9000 | 188 | 71
3 | 9000 | 105 | 213
5 | 9000 | 178 | 354
10 | 9000 | 357 | 703
1 | 10000 | 213 | 81
3 | 10000 | 119 | 237
5 | 10000 | 198 | 385
10 | 10000 | 388 | 778
(
fn getBipBodies = (for obj in geometry where iskindof (c = obj.controller) Vertical_Horizontal_Turn collect c)
format "calls | nodes | getBipBodies | getClassInstances\n"
for i in #( 1, 10, 100, 1000, 5000, 6000, 7000, 8000, 9000, 10000 ) do
(
with undo off
(
delete objects; gc()
for j = 1 to i do Teapot()
)
for max_iterations in #( 1, 3, 5, 10 ) do
(
result = #(max_iterations,i,0,0)
(
t0 = timestamp()
for k = 1 to max_iterations do
(
getbipbodies()
)
result[3] = timestamp() - t0
)
(
t0 = timestamp()
for k = 1 to max_iterations do
(
getClassInstances Vertical_Horizontal_Turn
)
result[4] = timestamp() - t0
)
format "%\n" ((dotNetClass "system.string").format "{0,3} | {1,5} | {2,6} | {3,6}" (dotNet.ValueToDotNetObject result (dotnetclass "system.object[]")))
)
format "\n"
)
)
check this snippet:
delete objects
for k=1 to 1000 do point()
fn getpoints = (for obj in objects where iskindof obj point collect obj)
(
t0 = timestamp()
h0 = heapfree
for k=1 to 1000 do
(
getclassinstances point
--getpoints()
)
format "time:% heap:%\n" (timestamp() - t0) (h0 - heapfree)
)
sometimes the number of nodes in a scene is not very important. more dramatic is a number of cross-dependencies (it includes controllers, attributes, modifiers, materials, … all reference targets (anims))
For a scene with 13000 bipeds, 10000 additional objects and 10000 materials I get these results:
fn getBipBodies = (for obj in geometry where iskindof (c = obj.controller) Vertical_Horizontal_Turn collect c)
time:169 heap:6980544L
getClassInstances Vertical_Horizontal_Turn processAllAnimatables:true
time:56 heap:33592L
getclassinstances point
time:3205 heap:232120L
getpoints()
time:745 heap:96120L
But, for this example (which is more “realistic”), I get these numbers:
I mean more “realisctic” because in my code (post 3), getclassinstances() is called when an object is created/deleted only, so it won’t be called too frequently, not 1000 times in less than a second for sure.
(
with undo off
(
delete objects
for k=1 to 10000 do point()
)
gc()
fn getpoints = (for obj in objects where iskindof obj point collect obj)
t0 = timestamp()
h0 = heapfree
--getclassinstances point processAllAnimatables:true
--getpoints()
format "time:% heap:%\n" (timestamp() - t0) (h0 - heapfree)
)
getclassinstances point processAllAnimatables:true
time:50 heap:1368672L
getpoints()
time:49 heap:1368536L