Updated Construction and Activation phases concepts (markdown)

hvolmer
2020-02-11 18:35:22 -07:00
parent 48b448c356
commit 8add97bd11

@@ -36,7 +36,7 @@ public override bool CustomActivate()
### 4. Post-activation
This phase is used primarily to handle any logic in a device that might be dependent on another device, and we need to ensure that we have waited for the dependent device to be activated first. For example, if we look at the `MicrophonePrivacyController` class, this is a "virtual" device whose purpose is to control the mute state of microphones from one or more contact closure inputs as well as provide feedback via different colored LEDs as to the current mute state. This device doesn't actually represent any sort of physical hardware device, but rather relies on associating itself with other devices that represent digital inputs and relays as well as whatever device is responsible for preforming the actual muting of the microphones.
This phase is used primarily to handle any logic in a device that might be dependent on another device, and we need to ensure that we have waited for the dependent device to be activated first. For example, if we look at the `MicrophonePrivacyController` class, this is a "virtual" device whose purpose is to control the mute state of microphones from one or more contact closure inputs as well as provide feedback via different colored LEDs as to the current mute state. This virtual-device doesn't actually represent any sort of physical hardware device, but rather relies on associating itself with other devices that represent digital inputs and relays as well as whatever device is responsible for preforming the actual muting of the microphones.
We can see in the example below that during the `CustomActivate()` phase, we define a post-activation action via a lambda in `AddPostActivationAction()` that will execute during the post-activation phase. The purpose here is to check the state of the microphone mute and set the state of the relays that control the LEDs accordingly. We need to do this as a post-activation action because we need to make sure that the devices PrivacyDevice, RedLedRelay and GreenLedRelay are fully activated before we can attempt to interact with them.
@@ -47,20 +47,17 @@ public override bool CustomActivate()
foreach (var i in Config.Inputs)
{
var input = DeviceManager.GetDeviceForKey(i.DeviceKey) as IDigitalInput;
if(input != null)
AddInput(input);
}
var greenLed = DeviceManager.GetDeviceForKey(Config.GreenLedRelay.DeviceKey) as GenericRelayDevice;
if (greenLed != null)
GreenLedRelay = greenLed;
else
Debug.Console(0, this, "Unable to add Green LED device");
var redLed = DeviceManager.GetDeviceForKey(Config.RedLedRelay.DeviceKey) as GenericRelayDevice;
if (redLed != null)
RedLedRelay = redLed;
else
@@ -82,7 +79,6 @@ void CheckPrivacyMode()
if (PrivacyDevice != null)
{
var privacyState = PrivacyDevice.PrivacyModeIsOnFeedback.BoolValue;
if (privacyState)
TurnOnRedLeds();
else
@@ -93,10 +89,14 @@ void CheckPrivacyMode()
### Activation exceptions
Each of the three activation phases operates in a try/catch block for each device. This way if one device has a fatal failure during activation, the failure will be logged and the system can continue to activate. This allows the developer to chase down multiple issues per load while testing, or to fix configuration omissions/errors as a group rather than one-at-a-time. A system can theoretically load fully and have many or all devices fail. We generally do not want to depend on exception handling to log device failures. Construction and activation code should have plenty of null checks, parameter validity checks, and debugging output to prevent exceptions from occurring. `String.IsEmptyOrNull(myString)` and `if(myObject == null)` are your friends. Invite them often.
Each of the three activation phases operates in a try/catch block for each device. This way if one device has a fatal failure during activation, the failure will be logged and the system can continue to activate. This allows the developer to chase down multiple issues per load while testing, or to fix configuration omissions/errors as a group rather than one-at-a-time. A program can theoretically be fully-initialized and have many or all devices fail. We generally do not want to depend on exception handling to log device failures. Construction and activation code should have plenty of null checks, parameter validity checks, and debugging output to prevent exceptions from occurring. `String.IsEmptyOrNull(myString)` and `if(myObject == null)` are your friends. Invite them often.
### Interdependence
### The goal
Robust C#-based system code should not depend on "order" or "time" to get running. We do not need to manage the order of our startup in this environment. Our Room class may come alive before our DSP and or Codec, and the Room is responsible for handling things when those devices become available. The UI layer is responsible for blocking the UI or providing status when the Room's requirements are coming alive, or if something has gone away. We use events or Feedbacks to notify dependents that other devices/classes are ready or not, but we do not prevent continued construction/activation of the system when many of these events don't happen, or don't happen in a timely fashion. This removes the need for startup management, which is often prolonged and consumes tons of developer/installer time. A fully-loaded Essentials system may go through activation in several seconds, with all devices concurrently getting themselves going, where legacy code may take 10 minutes.
Robust C#-based system code should not depend on "order" or "time" to get running. We do not need to manage the order of our startup in this environment. Our Room class may come alive before our DSP and or Codec, and the Room is responsible for handling things when those devices become available. The UI layer is responsible for blocking the UI or providing status when the Room's requirements are coming alive, or if something has gone away. We use events or `Feedbacks` to notify dependents that other devices/classes are ready or not, but we do not prevent continued construction/activation of the system when many of these events don't happen, or don't happen in a timely fashion. This removes the need for startup management, which is often prolonged and consumes _tons_ of developer/installer time. A fully-loaded Essentials system may go through activation in several seconds, with all devices concurrently getting themselves going, where legacy code may take 10 minutes.
When designing new Device-based classes, be it rooms, devices, port controllers, bridges, make them as independent as possible. They could exist alone in a program with no required partner objects, and just quietly exist without failing. We want the system to be fast and flexible, and keeping the interdependence between objects at a minimum improves this flexibility into the future.