Nasal optimisation: Difference between revisions

m
(Created page with "{{Nasal Navigation}} = Introduction to writing Nasal for optimum performance = Writing optimal performing code in a real time system (in any language) is usually related to...")
 
(5 intermediate revisions by 2 users not shown)
Line 13: Line 13:
# Avoid garbage collection
# Avoid garbage collection


Inside each Nasal frame the programmer needs to ensure that the minimum necessary work is performed; ideally a predictable workload to ensure that Nasal processing per frame is constant - because otherwise there can be frame pauses - which is where an occasional frame will take much longer than usual.
Inside each [[Nasal]] frame the programmer needs to ensure that the minimum necessary work is performed; ideally a predictable workload to ensure that Nasal processing per frame is constant - because otherwise there can be frame pauses - which is where an occasional frame will take much longer than usual.


Splitting any given task across frames is the normal way of ensuring that performance remains good; unfortunately a lot of Nasal doesn't do this and instead will process everything each frame. This is what the PartitionProcessor is designed to help with
Splitting any given task across frames is the normal way of ensuring that performance remains good; unfortunately a lot of Nasal doesn't do this and instead will process everything each frame. This is what the PartitionProcessor is designed to help with
Line 86: Line 86:


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 ==
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)
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.
<syntaxhighlight lang="nasal">
var ModuleRecipient =
{
    new: func(_ident)
    {
        var new_class = emesary.Recipient.new(_ident);
        new_class.Object = nil;
        new_class.Receive = func(notification)
        {
            if (notification.NotificationType == "FrameNotification")
            {
                if (new_class.Object == nil)
                  new_class.Object = ModuleObject.new();
                if (!math.mod(notifications.frameNotification.FrameCount,2)){
                    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.
    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
  input = {
          frame_rate                : "/sim/frame-rate",
          frame_rate_worst          : "/sim/frame-rate-worst",
          elapsed_seconds          : "/sim/time/elapsed-sec",
          };
foreach (var name; keys(input)) {
    emesary.GlobalTransmitter.NotifyAll(notifications.FrameNotificationAddProperty.new("EXEC", name, input[name]));
}
setlistener("sim/signals/fdm-initialized", func {
    notifications.frameNotification.running = 1;
});
notifications.frameNotification.running = 0;
notifications.frameNotification.dT = 0; # seconds
notifications.frameNotification.curT = 0;
var execTimer = maketimer(cur_frame_inc, rtExec_loop);
execTimer.simulatedTime = 1;
setlistener("/sim/signals/fdm-initialized", func {
    print("M_exec: starting");
    execTimer.start()
});
#
# 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 ==
Line 93: Line 237:
Use the FrameNotificationAddProperty to request that certain properties are contained within the FrameNotification.
Use the FrameNotificationAddProperty to request that certain properties are contained within the FrameNotification.


<syntaxhighlight lang="nasal">
notifications.FrameNotificationAddProperty.new("F15-HUD", **[HASH-Name]**, **[property-string]**)
notifications.FrameNotificationAddProperty.new("F15-HUD", **[HASH-Name]**, **[property-string]**)
</syntaxhighlight>


e.g.
e.g.
Line 179: Line 325:


------
------
== props.UpdateManager ==
== props.UpdateManager ==


Line 257: Line 404:
                                 ,
                                 ,
                                 func(pp, obj, element){
                                 func(pp, obj, element){
                                     proces individual element;  
                                     process individual element;  
                                     params
                                     params
                                     pp is the partition processor that called this
                                     pp is the partition processor that called this