|
|
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 == |