Nasal optimisation: Difference between revisions

Jump to navigation Jump to search
Line 85: Line 85:
= Optimisation techniques =  
= Optimisation techniques =  


The F-15 I think is the reference implementation of my three main Nasal optimisation techniques;  
The F-15 I think is the reference implementation of my three main Nasal optimisation techniques;


== Emesary real time executive ==
== Nasal Profiling ==


The following code can be used to provide a basic and yet sophisticated way to run Nasal code in a scheduled (per frame or at a suitable rate) and in a managed way (i.e. something knows which modules are registered)
As yet there isn't a profiler available so instead there is an OperationTimer that is ships as part of emexec (2020.3 or later released after Aprial 2022).


Each module that wishes to be executed needs to implement itself as a receiver or have a receiver wrapper (as follows). Generally I prefer to use a receiver wrapper as it helps to isolate the code that does the work from the code that interfaces to the scheduler.
This makes it easy to output (via logprint) information about how much time a module is taking.  


<syntaxhighlight lang="nasal">
To create an instance of a timer there are two parameters, the first is the ident and the second the log level (3=info, 2=debug)
var ModuleRecipient =
<code>var ot = emexec.OperationTimer.new("VSD",2);</code>
{
The output is a cumulative number of milliseconds since the OperationTimer was reset.
    new: func(_ident)
<code>ot.reset();
    {
ot.log("Start");
        var new_class = emesary.Recipient.new(_ident);
<some code to time>
        new_class.Object = nil;
ot.log("1");
        new_class.Receive = func(notification)
<more code>
        {
ot.log("ed");</code>
            if (notification.NotificationType == "FrameNotification")
            {
                if (new_class.Object == nil)
                  new_class.Object = ModuleObject.new();


                if (!math.mod(notifications.frameNotification.FrameCount,2)){
== Emesary real time executive ==
                    new_class.Object.update(notification);
                }
                return emesary.Transmitter.ReceiptStatus_OK;
            }
            return emesary.Transmitter.ReceiptStatus_NotProcessed;
        };
        return new_class;
    },
};
 
emesary.GlobalTransmitter.Register(ModuleRecipient.new("MODULE-NAME"));
</syntaxhighlight>
 
This is the code that is used in the F-15 (Nasal/M_exec.nas) that provides the simple real time frame based executive.
 
<syntaxhighlight lang="nasal">
#---------------------------------------------------------------------------
#
# Title                : Emesary based real time executive
#
# File Type            : Implementation File
#
# Description          : Uses emesary notifications to permit nasal subsystems to
#                      : be invoked in a controlled manner.
#
# Author              : Richard Harrison (richard@zaretto.com)
#
# Creation Date        : 4 June 2018
#
# Version              : 1.0
#
#  Copyright (C) 2018 Richard Harrison          Released under GPL V2
#
#---------------------------------------------------------------------------*/
 
# to add properties to the FrameNotification simply send a FrameNotificationAddProperty
# to the global transmitter. This will be received by the frameNotifcation object and
# included in the update.
#emesary.GlobalTransmitter.NotifyAll(notifications.FrameNotificationAddProperty.new("MODULE", "wow","gear/gear[0]/wow"));
#emesary.GlobalTransmitter.NotifyAll(notifications.FrameNotificationAddProperty.new("MODULE", "engine_n2", "engines/engine[0]/n2"));
#   
 
 
#
# real time exec loop.
var frame_inc = 0;
var cur_frame_inc = 0.03;
 
var rtExec_loop = func
{
    #   
    notifications.frameNotification.fetchvars();
 
    if (!notifications.frameNotification.running){
        return;
    }
    notifications.frameNotification.dT = notifications.frameNotification.elapsed_seconds - notifications.frameNotification.curT;
 
    if (notifications.frameNotification.dT > 1.0)
      notifications.frameNotification.curT = notifications.frameNotification.elapsed_seconds;
 
    if (notifications.frameNotification.FrameCount >= 16) {
        notifications.frameNotification.FrameCount = 0;
    }
    emesary.GlobalTransmitter.NotifyAll(notifications.frameNotification);
    #   
 
    notifications.frameNotification.FrameCount = notifications.frameNotification.FrameCount + 1;


    # adjust exec rate based on frame rate.
Starting from 2020.3 released after April 2022 FGdata includes the emexec module which provides an easy to use object scheduler that uses Emesary to do most of the work.  
    if (notifications.frameNotification.frame_rate_worst < 5) {
        frame_inc = 0.25;#4 Hz
    } elsif (notifications.frameNotification.frame_rate_worst < 10) {
        frame_inc = 0.125;#8 Hz
    } elsif (notifications.frameNotification.frame_rate_worst < 15) {
        frame_inc = 0.10;#10 Hz
    } elsif (notifications.frameNotification.frame_rate_worst < 20) {
        frame_inc = 0.075;#13.3 Hz
    } elsif (notifications.frameNotification.frame_rate_worst < 25) {
        frame_inc = 0.05;#20 Hz
    } elsif (notifications.frameNotification.frame_rate_worst < 40) {
        frame_inc = 0.0333;#30 Hz
    } else {
        frame_inc = 0.02;#50 Hz
    }
    if (frame_inc != cur_frame_inc) {
        cur_frame_inc = frame_inc;
    }
    execTimer.restart(cur_frame_inc);
}


# setup the properties to monitor for this system
By default it is recommended to use the built in scheduler <code>emexec.ExecModule</code>
  input = {
          frame_rate                : "/sim/frame-rate",
          frame_rate_worst          : "/sim/frame-rate-worst",
          elapsed_seconds          : "/sim/time/elapsed-sec",
          };


foreach (var name; keys(input)) {
The scheduler will adjust the frequency from 50hz right down to 4hz.  
    emesary.GlobalTransmitter.NotifyAll(notifications.FrameNotificationAddProperty.new("EXEC", name, input[name]));
}


setlistener("sim/signals/fdm-initialized", func {
Any object that wishes to be invoked simply needs to provide an <code>update(notification)</code> method and register itself via ExecModule.register method.
    notifications.frameNotification.running = 1;
});


notifications.frameNotification.running = 0;
The register method takes the following arguments
notifications.frameNotification.dT = 0; # seconds
notifications.frameNotification.curT = 0;


var execTimer = maketimer(cur_frame_inc, rtExec_loop);
# ident - text to identify this object registration
execTimer.simulatedTime = 1;
# properties_to_monitor : a key value pair hash of properties to include in the notification. This helps to optimise property tree access (see FrameNotification below for the format)
setlistener("/sim/signals/fdm-initialized", func {
# object : an instance of an objec that has an update(notification) method
    print("M_exec: starting");
# rate : frame skip rate (1/rate is the effective rate)
    execTimer.start()
# frame_offset : frame skip offset. Must be less than rate and when used with rate it can allow interleaving of modules (usually for performance)
});


#
During development it would be usual to set the overrun detection active emexec.ExecModule.transmitter.OverrunDetection(9) for a warning (log leve info) of any frame that exceeds 9 ms
# Turn on the automatic overrun detection - this will notify on the console
# if any recipient takes more than the allocated (9ms) amount of time
emesary.GlobalTransmitter.OverrunDetection(9); # warn at 9ms
</syntaxhighlight>


== FrameNotification ==
== FrameNotification ==
308

edits

Navigation menu