private static LED_Set MergeSnapshotsDown(List<LED_Set> LightSnapshots) { if (LightSnapshots.Count <= 0) { return null; } LED_Set MergedLayers = new LED_Set(); for (int index = 0; index < LightSnapshots[0].LightCount; index++) { MergedLayers.LED_Values.Add (new LED (0, 0, 0, 0)); } List<int> snapshots_requesting_replacement = new List<int> (); for (int i = 0; i < LightSnapshots.Count; i++) { if (LightSnapshots [i].BlendMode == LED.BlendingStyle.Favor || LightSnapshots [i].BlendMode == LED.BlendingStyle.Mask || LightSnapshots [i].BlendMode == LED.BlendingStyle.Replace) { snapshots_requesting_replacement.Add (i); } else { // Blend the layers together LightProcessing.MergeLayerDown (LightSnapshots [i].LED_Values, MergedLayers.LED_Values, LightSnapshots[i].BlendMode); } } // These must be done after standard blending modes due to the possibility of overriding the above foreach (int snapshot_index in snapshots_requesting_replacement) { LightProcessing.MergeLayerDown (LightSnapshots [snapshot_index].LED_Values, MergedLayers.LED_Values, LightSnapshots[snapshot_index].BlendMode); } return MergedLayers; }
public LED_Set Clone() { LED_Set cloned_set = new LED_Set (0); cloned_set.BlendMode = BlendMode; foreach (LED LED_to_clone in LED_Values) { cloned_set.LED_Values.Add (LED_to_clone.Clone ()); } return cloned_set; }
private static void Actinic_Light_Run_Queue() { Actinic_Lights_Queue.ClearQueue (); // Current LED frame to send to output LED_Set Light_Snapshot = null; // All individual frames to send to output List<LED_Set> Light_Snapshots = new List<LED_Set> (); // Current VU volumes as collected from the VU input system; only used for AbstractReactiveAnimation List<double> Audio_Volumes_Snapshot = new List<double> (); // Keeps track of queue performance to maintain consistent FPS System.Diagnostics.Stopwatch Queue_PerfStopwatch = new System.Diagnostics.Stopwatch (); #if DEBUG_PERFORMANCE bool wasIdle = false; #endif while (true) { // Keep track of how long these steps take to maintain a consistent FPS Queue_PerfStopwatch.Restart (); // -- ReactiveAnimation-specific queue management -- // Note: For now, only the base layer can be a ReactiveAnimation; overlays will not get updated AbstractReactiveAnimation selected_reactive_animation = Actinic_Lights_Queue.SelectedAnimation as AbstractReactiveAnimation; if (Actinic_Lights_Queue.AnimationActive && selected_reactive_animation != null) { #if DEBUG_VU_PERFORMANCE VU_Processing_PerfStopwatch.Restart (); #endif #if DEBUG_PERFORMANCE Console.WriteLine ("{0} ms - updating audio snapshot", Queue_PerfStopwatch.ElapsedMilliseconds); #endif // Grab a snapshot of the current audio volumes if (ActiveAudioInputSystem.Running) { double[] Audio_Volumes = ActiveAudioInputSystem.GetSnapshot (); while (Audio_Volumes_Snapshot.Count < Audio_Volumes.Length) { Audio_Volumes_Snapshot.Add (0); } while (Audio_Volumes_Snapshot.Count > Audio_Volumes.Length) { Audio_Volumes_Snapshot.RemoveAt (Audio_Volumes_Snapshot.Count - 1); } for (int i = 0; i < Audio_Volumes.Length; i++) { Audio_Volumes_Snapshot [i] = Audio_Volumes [i]; } } // Update the current reactive animation with this volume snapshot selected_reactive_animation.UpdateAudioSnapshot (Audio_Volumes_Snapshot); ReactiveSystem.PrintAudioInformationToConsole (selected_reactive_animation); #if DEBUG_VU_PERFORMANCE Console.WriteLine ("# Time until acknowledged: {0}", VU_Processing_PerfStopwatch.ElapsedMilliseconds); #endif } // -- Animation-specific queue management -- foreach (KeyValuePair <string, LED_Queue> queue in GetAllQueues ()) { if (queue.Value.AnimationActive) { UpdateAnimationStackForQueue (queue.Value, Queue_PerfStopwatch.ElapsedMilliseconds, queue.Key); } } // -- Generic Light Queue output -- // Fixed: with multi-threaded timing, the light-queue could become empty between checking the count and pulling a snapshot if (ActiveOutputSystemReady ()) { LED_Set QueueLightSnapshot = null; bool update_needed = false; foreach (KeyValuePair <string, LED_Queue> queue in GetAllQueues ()) { if (queue.Value.QueueEmpty == false) { update_needed = true; break; } } foreach (KeyValuePair <string, LED_Queue> queue in GetAllQueues ()) { QueueLightSnapshot = queue.Value.PopFromQueue (); if (QueueLightSnapshot != null) { #if DEBUG_PERFORMANCE Console.WriteLine ("{0} ms - grabbing snapshot from light queue ({1})", Queue_PerfStopwatch.ElapsedMilliseconds, queue.Key); #endif Light_Snapshots.Add (QueueLightSnapshot); queue.Value.QueueIdleTime = 0; queue.Value.Lights = QueueLightSnapshot.LED_Values; #if DEBUG_PERFORMANCE Console.WriteLine ("{0} ms - updating last processed ({1})", Queue_PerfStopwatch.ElapsedMilliseconds, queue.Key); #endif queue.Value.MarkAsProcessed (); } else if (update_needed) { // Nothing new in the queue, but it must be added to the snapshot for it to be blended down again LED_Set last_processed_set = new LED_Set (queue.Value.LightsLastProcessed); last_processed_set.BlendMode = queue.Value.BlendMode; Light_Snapshots.Add (last_processed_set); } } } // Do this after the above to ensure any remaining queue entries will be pushed out bool update_after_deletion_needed = false; lock (Actinic_Lights_Overlay_Queues) { List<string> EmptyQueues = new List<string> (); IAnimationOneshot selected_oneshot_animation = null; bool deletionRequested = false; bool queueWithMaskBlendingActive = false; foreach (KeyValuePair <string, LED_Queue> queue in Actinic_Lights_Overlay_Queues) { selected_oneshot_animation = queue.Value.SelectedAnimation as IAnimationOneshot; if (queue.Value.BlendMode == LED.BlendingStyle.Mask || queue.Value.BlendMode == LED.BlendingStyle.Replace) queueWithMaskBlendingActive = true; if ((queue.Value.LightsHaveNoEffect) || (selected_oneshot_animation != null && selected_oneshot_animation.AnimationFinished)) { HaltActivity (queue.Value, true); EmptyQueues.Add (queue.Key); deletionRequested = true; } } if (deletionRequested && queueWithMaskBlendingActive) update_after_deletion_needed = true; // Test case: // color all 0 255 0 // brightness all 100 // overlay test identify && overlay test blending replace // overlay voice-notif color all 255 0 0 keep && overlay voice-notif blending favor && overlay voice-notif brightness all 255 // [wait a little while] // overlay voice-notif brightness all 0 && overlay voice-notif color all 0 0 0 keep // [test layer should reappear -and- have the correct color/brightness values] // Continuing the test case: // overlay clear_all // [only green as set above should show] foreach (string key in EmptyQueues) { #if DEBUG_OVERLAY_MANAGEMENT Console.WriteLine (" {0} ms - removing overlay '{1}' as it is empty", Queue_PerfStopwatch.ElapsedMilliseconds, key); #endif Actinic_Lights_Overlay_Queues.Remove (key); } } if (update_after_deletion_needed) { UpdateAllQueues (); } // Merge all layers together... Light_Snapshot = MergeSnapshotsDown (Light_Snapshots); // And clear the now out-of-date values for next run Light_Snapshots.Clear (); if (Light_Snapshot != null && ActiveOutputSystemReady ()) { #if DEBUG_PERFORMANCE wasIdle = false; #endif // There's a frame to play and the system is ready Actinic_Light_Queue_CurrentIdleWait = 1; // Reset the idle counter, used for implementing interval animations #if DEBUG_PERFORMANCE Console.WriteLine ("{0} ms - frame generated", Queue_PerfStopwatch.ElapsedMilliseconds); #endif int retriesSinceLastSuccess = 0; while (true) { if (retriesSinceLastSuccess >= 5) { throw new System.IO.IOException ("Could not reconnect to output system in background after 5 tries, giving up"); } try { if (UpdateLights_All (Light_Snapshot.LED_Values) == false) { Console.WriteLine ("(Error while updating lights in the animation queue!)"); } // It at least didn't throw an exception... retriesSinceLastSuccess = 0; // Exit this loop to allow further processing break; } catch (System.IO.IOException ex) { Console.WriteLine (DateTime.Now); Console.WriteLine ("\n ! Unexpected connection loss, attempting to reconnect...\n\n{0}\n", ex); ShutdownOutputSystem (); retriesSinceLastSuccess ++; Console.Write ("Waiting 5 seconds..."); System.Threading.Thread.Sleep (5000); Console.WriteLine (" Retrying."); // RunWithoutInteraction: Usually someone won't be providing input when this happens // SkipPreparingSystem: Queue and all that is already running if (PrepareLightingOutput (true, true) == false) { throw new System.IO.IOException ("Could not reconnect to output system in background, giving up"); } // Try again by nature of not calling break } } #if DEBUG_PERFORMANCE Console.WriteLine ("{0} ms - frame sent", Queue_PerfStopwatch.ElapsedMilliseconds); #endif #if DEBUG_BRIEF_PERFORMANCE if (Queue_PerfStopwatch.ElapsedMilliseconds > Light_Animation_Latency) Console.WriteLine ("# {0} ms - frame finished ({1})", Queue_PerfStopwatch.ElapsedMilliseconds, DateTime.Now.ToLongTimeString ()); #endif // Attempt to keep each frame spaced 'Light_Animation_Delay' time apart, default 50ms SleepForAnimation ((int)Queue_PerfStopwatch.ElapsedMilliseconds); #if DEBUG_PERFORMANCE Console.WriteLine ("# {0} ms - frame finished", Queue_PerfStopwatch.ElapsedMilliseconds); #endif #if DEBUG_VU_PERFORMANCE Console.WriteLine ("# {0} ms - frame finished", VU_Processing_PerfStopwatch.ElapsedMilliseconds); VU_Processing_PerfStopwatch.Stop (); #endif } else { #if DEBUG_PERFORMANCE if (wasIdle == false) Console.WriteLine ("# Idle ({0} ms loop)", Actinic_Light_Queue_CurrentIdleWait); if (Actinic_Light_Queue_CurrentIdleWait == Actinic_Light_Queue_MaxIdleWaitTime) wasIdle = true; #endif System.Threading.Thread.Sleep (Actinic_Light_Queue_CurrentIdleWait); foreach (KeyValuePair <string, LED_Queue> queue in GetAllQueues ()) { queue.Value.QueueIdleTime += Actinic_Light_Queue_CurrentIdleWait; } if (Actinic_Light_Queue_CurrentIdleWait < Actinic_Light_Queue_MaxIdleWaitTime) { // Each loop spent in idle without events, increase the delay Actinic_Light_Queue_CurrentIdleWait *= Actinic_Light_Queue_IdleWaitMultiplier; } else if (Actinic_Light_Queue_CurrentIdleWait > Actinic_Light_Queue_MaxIdleWaitTime) { // ...but don't go above the maximum idle wait time Actinic_Light_Queue_CurrentIdleWait = Actinic_Light_Queue_MaxIdleWaitTime; } } if (Actinic_Lights_Queue.QueueCount >= Actinic_Light_Queue_BufferFullWarning && Actinic_Light_Queue_BufferFullWarning_Shown == false) { Console.WriteLine ("(Warning: the LED output queue holds over {0} frames, which will cause a delay in" + " response after a command)", Actinic_Light_Queue_BufferFullWarning); Actinic_Light_Queue_BufferFullWarning_Shown = true; } else if (Actinic_Lights_Queue.QueueCount < Actinic_Light_Queue_BufferFullWarning && Actinic_Light_Queue_BufferFullWarning_Shown == true) { Console.WriteLine ("(LED output queue now holds less than {0} frames)", Actinic_Light_Queue_BufferFullWarning); Actinic_Light_Queue_BufferFullWarning_Shown = false; } Queue_PerfStopwatch.Stop (); } }
/// <summary> /// Adds a frame to the end of the output queue /// </summary> /// <param name="NextFrame">An LED_Set representing the desired frame.</param> public void PushToQueue(LED_Set NextFrame) { if (NextFrame.LightCount != LightCount) throw new ArgumentOutOfRangeException (string.Format ("NextFrame must contain same number of LEDs (has {0}, expected {1})", NextFrame.LightCount, LightCount)); lock (OutputQueue) { OutputQueue.Enqueue (NextFrame.Clone ()); } }