/* Utility for logging user statistics and events to a database. Author: Jason Hayes (jason.hayes@rockstarsandiego.com) Example: -- Timing example. exportUsage = RsUsage() exportUsage.start "Map Export" "Export textures" -- Export textures process exportUsage.start "Map Export" "Syncing textures" -- Syncing textures process exportUsage.stop() -- Stop syncing textures timer. exportUsage.stop() -- Stop export textures timer. -- After export is finished. if exportUsage.anyStillRunning() then ( exportUsage.stopAll() ) -- Event example. on button clicked do ( RsLogEvent "The Tool Name" "The button was clicked." ) */ struct RsUsageTimer ( /* Wrapper for working with a RSG.MaxUtils.UserStatisticsTimer object. */ private usageTimer = undefined, public enabled = true, id = undefined, guid = undefined, filename = undefined, -- Methods fn start scope = ( /* Start the timer, using the supplied scope. */ local result = false if usageTimer == undefined then ( if id == undefined or filename == undefined then ( RsAssert ( id != undefined or filename != undefined ) ) else ( usageTimer = dotNetObject "RSG.MaxUtils.UserStatisticsTimer" id filename ) ) if usageTimer != undefined then ( if enabled do ( usageTimer.Start scope result = true ) ) else ( RsAssert ( usageTimer != undefined ) message:"Cannot start usage timer because the dotNet object has not been created!" ) result ), fn stop = ( /* Stop the timer. */ local result = false if usageTimer != undefined then ( if enabled do ( usageTimer.Stop() result = true ) ) else ( RsAssert ( usageTimer != undefined ) message:"Cannot stop usage timer because the dotNet object has not been created!" ) result ) ) struct RsUsage ( /* Manager class for dealing with usage statistics. This class employs a push/pop method, where each time .start is called, a new RsUsageTimer is placed on the stack. Every time .stop is called, the last RsUsageTimer on the stack will be stopped and popped off the stack. */ private -- Stack of usage timers. usageTimerStack = #(), -- Current position on the stack. currentStackPos = 0, -- Link timers via a GUID. guid = undefined, public enabled = true, -- Methods fn start id scope filename:"" = ( /* Creates a new RsUsageTimer, places it on the stack and starts the timer. */ if enabled then ( -- If no timers have been started, then generate a new GUID. if usageTimerStack.count == 0 then ( local dotNetGuid = dotNetClass "System.Guid" newGuid = dotNetGuid.NewGuid() guid = newGuid.ToString() ) local newTimer = RsUsageTimer() newTimer.id = id newTimer.guid = guid newTimer.filename = filename append usageTimerStack newTimer newTimer.start scope currentStackPos += 1 ) -- Return stack position in case it's useful. currentStackPos ), fn stop = ( /* Stops and removes the last timer placed on the stack. */ local result = false if enabled then ( -- Still have usage timers on the stack. if usageTimerStack.count > 0 and currentStackPos <= usageTimerStack.count then ( local existingTimer = usageTimerStack[ currentStackPos ] if existingTimer != undefined then ( existingTimer.stop() deleteItem usageTimerStack currentStackPos currentStackPos -= 1 if currentStackPos == 0 then ( guid = undefined ) result = true ) -- No usage timers left. ) else if usageTimerStack.count == 0 and currentStackPos == 0 then ( format "No usage timers to stop!\n" -- Something bad happened to the stack. ) else ( format "Cannot stop usage timer. Internal usage timer stack is wrong, or the stack position index (%) is incorrect.\n" currentStackPos ) ) result ), fn stopAll = ( /* Stops all timers and cleans up the stack. */ local numTimersInStack = usageTimerStack.count for stackIdx = 1 to numTimersInStack do ( stop() ) ), fn anyStillRunning = ( /* Returns whether any timers are still running. */ local result = false if usageTimerStack.count > 0 then ( result = true ) result ) ) fn RsLogEvent identifier message = ( /* Logs a single event to the Usage Statistics database. */ ( dotNetObject "RSG.MaxUtils.UserStatistics" ).RegisterUsage identifier message )