1
0
Fork 0
fgdata/Nasal/modules/canvas_efis/efis.nas

284 lines
9.9 KiB
Text

#-------------------------------------------------------------------------------
# efis.nas
# author: jsb
# created: 12/2017
#-------------------------------------------------------------------------------
# class EFIS
# manage cockpit displays (=outputs) and sources (image generators for PFD, MFD, EICAS...)
# allow redirection of sources to alternate displays (allow for simulated display fault)
var EFIS = {
#-- static members
_instances: [],
unload: func() {
foreach (var instance; EFIS._instances) {
instance.del();
}
EFIS._instances = [];
},
NO_SRC: -1,
defaultcanvas_settings: {
"name": "EFIS_display",
"size": [1024,1024],
"view": [1024,1024],
"mipmapping": 1
},
window_size: [450,450],
colors: {
transparent: [1,0,0,0],
white: [1,1,1],
red: [1,0,0],
green : [0,1,0],
blue : [0,0,1],
yellow: [1,1,0],
cyan: [0,1,1],
magenta: [1,0,1],
amber: [1,0.682,0],
},
del: func() {
},
# create EFIS object
# display_names: vector of display names, one DisplayUnit per entry will be
# created
# object_names: vector of same size and order as display_names, containing
# 3D object names for canvas placement of the DisplayUnits
new: func(display_names, object_names, canvas_settings=nil) {
if (!isvec(display_names)) {
logprint(DEV_ALERT, "EFIS.new: 'display_names' not a vector!");
return;
}
var obj = {
parents: [me],
id: 0,
display_units: [],
sources: [], # vector of EFISCanvas instances
display_names: display_names,
controls: {},
source_records: [], # stores infos about each source
active_sources: [],
powerN: nil,
};
if (object_names != nil and isvec(object_names)
and size(display_names) == size(object_names))
{
foreach (var i; display_names) {
append(obj.active_sources, EFIS.NO_SRC);
}
var settings = obj.defaultcanvas_settings;
if (canvas_settings != nil and ishash(canvas_settings)) {
foreach (var key; keys(canvas_settings)) {
settings[key] = canvas_settings[key];
}
}
setsize(obj.display_units, size(display_names));
forindex (var id; display_names)
{
obj.display_units[id] = DisplayUnit.new(obj.display_names[id],
obj.defaultcanvas_settings, object_names[id]);
}
}
append(EFIS._instances, obj);
return obj;
}, #new
#-- private methods ----------------------
# _setDisplaySource - switch display unit du_id to source source_id
# count how often a source is displayed, sources not displayed stop updating themselves
_setDisplaySource: func(du_id, source_id)
{
var prev_source = me.active_sources[du_id];
#print("setDisplaySource unit "~du_id~" src "~source_id~" prev "~prev_source);
if (prev_source >= 0) {
if (me.source_records[prev_source] == nil)
logprint(LOG_ALERT, "_setDisplaySource error: prev: "~prev_source~" #"~size(me.source_records));
var n = me.source_records[prev_source].visibleN;
n.setValue(n.getValue() - 1);
}
var path = "";
if (source_id >= 0) {
path = me.sources[source_id].getPath();
}
me.display_units[du_id].setSource(path);
me.active_sources[du_id] = source_id;
var n = me.source_records[source_id].visibleN;
n.setValue(n.getValue() + 1);
},
# mapping can be either:
# - vector of source ids, size must equal size(display_units)
# values nil = do nothing, 0..N select source, -1 no source
# - hash {<unit_name>: source_id}
_activateRouting: func(mapping)
{
if (typeof(mapping) == "vector") {
forindex (var unit_id; me.display_units)
{
if (mapping[unit_id] != nil)
me._setDisplaySource(unit_id, mapping[unit_id]);
}
}
elsif (typeof(mapping) == "hash") {
foreach (var unit_name; keys(mapping))
{
forindex (var unit_id; me.display_names) {
if (me.display_names[unit_id] == unit_name) {
me._setDisplaySource(unit_id, mapping[unit_name]);
}
}
}
}
},
# Start/stop updates on all sources
_powerOnOff: func(power) {
if (power) {
logprint(3, "EFIS power on");
foreach (var src; me.sources)
src.startUpdates();
}
else {
logprint(3, "EFIS power off.");
foreach (var src; me.sources)
src.stopUpdates();
}
},
#-- public methods -----------------------
# set power prop and add listener to start/stop all registered update functions
# e.g. power up will start updates, loss of power will stop updates
setPowerProp: func(path) {
me.powerN = props.getNode(path,1);
setlistener(me.powerN, func(n) {
var power = n.getValue();
me._powerOnOff(power);
}, 1, 0);
},
setWindowSize: func(window_size) {
if (window_size != nil and typeof(window_size) == "vector") {
me.window_size = window_size;
}
else {
logprint(5, "EFIS.setWindowSize(): Error, argument is not a vector.");
}
},
boot: func() {
me._powerOnOff(me.powerN.getValue());
},
setDUPowerProps: func(power_props, minimum_power=0) {
if (power_props != nil and typeof(power_props) == "vector") {
forindex (var i; me.display_names) {
me.display_units[i].setPowerSource(power_props[i], minimum_power);
}
}
else logprint(5, "EFIS.setDUPowerProps(): Error, argument is not a vector.");
},
# add a EFISCanvas instance as display source
# EFIS controls updating by tracking how often source is used
# returns source ID that can be used in mappings
addSource: func(efis_canvas) {
append(me.sources, efis_canvas);
var srcID = size(me.sources) - 1;
var visibleN = EFIS_root_node.getNode("update/visible"~srcID,1);
visibleN.setIntValue(0);
efis_canvas.setUpdateN(visibleN);
append(me.source_records, {visibleN: visibleN});
return srcID;
},
# ctrl: property path to integer prop
# mappings: vector of display mappings
# callback: optional function that will be called with current ctrl value
addDisplaySwapControl: func(ctrl, mappings, callback=nil)
{
if (me.controls[ctrl] != nil) return;
ctrlN = props.getNode(ctrl,1);
if (typeof(mappings) != "vector") {
logprint(5, "EFIS addDisplayControl: mappings must be a vector.");
return;
}
var listener = func(p) {
var ctlValue = p.getValue();
if (ctlValue >= 0 and ctlValue < size(me.controls[ctrl].mappings))
me._activateRouting(me.controls[ctrl].mappings[ctlValue]);
else debug.warn("Invalid value for display selector "~ctrl~": "~ctlValue);
if (callback != nil) {
call(callback, [ctlValue], nil, nil, var err = []);
debug.printerror(err);
}
}
#print("addDisplayControl "~ctrl);
me.controls[ctrl] = {L: setlistener(ctrlN, listener, 0, 0), mappings: mappings};
},
# selected: property (node or path) containing source number (integer)
# target: contains the DU number to which the source will be mapped
# sources: optional vector, selected -> source ID (as returned by addSource)
# defaults to all registered sources
addSourceSelector: func(selected, target, sources=nil){
if (typeof(selected) == "scalar") {
selected = props.getNode(selected,1);
}
if (typeof(target) == "scalar") {
target = props.getNode(target,1);
}
if (selected.getValue() == nil)
selected.setIntValue(0);
if (sources == nil) {
for (var i = 0; i < size(me.sources); i += 1)
append(sources, i);
}
setlistener(selected, func(n){
var src = n.getValue();
var destination = target.getValue();
if (src >= 0 and src < size(sources))
me._setDisplaySource(destination, sources[src]);
}, 0, 0);
},
setDefaultMapping: func(mapping) {
if (mapping != nil and (typeof(mapping) == "vector" or typeof(mapping) == "hash")) {
me.default_mapping = mapping;
me._activateRouting(me.default_mapping);
}
},
getDU: func(i) {return me.display_units[i]},
#getSources: func() { return me.source_records; },
getDisplayName: func(id) {
id = num(id);
if (id != nil and id >=0 and id < size(me.display_names))
return me.display_names[id];
else return "Invalid display ID.";
},
getDisplayID: func(name) {
for (var id = 0; id < size(me.display_names); id += 1) {
if (me.display_names[id] == name) return id;
}
return -1;
},
#open a canvas window for display unit <id>
displayWindow: func(id)
{
id = num(id);
if (id < 0 or id >= size(me.display_units))
{
debug.warn("EFIS.displayWindow: invalid id");
return;
}
return me.display_units[id].asWindow(me.window_size);
},
};