From d6beb9cc703d2f4f916e02f8ebfbaccbd2f0d5ef Mon Sep 17 00:00:00 2001 From: Olivier Jacques Date: Sun, 27 Oct 2024 15:00:06 +0100 Subject: [PATCH 1/7] new: attract mode --- DOF2DMD/Program.cs | 67 +++++++++++++++++++++++++++++++++++++++++- DOF2DMD/dof2dmd.csproj | 1 + 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/DOF2DMD/Program.cs b/DOF2DMD/Program.cs index 3f8908e..d34d854 100644 --- a/DOF2DMD/Program.cs +++ b/DOF2DMD/Program.cs @@ -49,6 +49,7 @@ using static System.Net.Mime.MediaTypeNames; using FlexDMD.Properties; using System.Collections; +using System.Linq; namespace DOF2DMD @@ -64,6 +65,9 @@ class DOF2DMD public static string gGameMarquee = "DOF2DMD"; private static Timer _scoreTimer; private static Timer _animationTimer; + private static Timer _attractTimer; + private static bool _AttractModeAlternate = true; + private static string _currentAttractGif = null; private static readonly object _scoreQueueLock = new object(); private static readonly object _animationQueueLock = new object(); private static readonly object sceneLock = new object(); @@ -143,9 +147,69 @@ static void Main() Trace.WriteLine($"DOF2DMD is now listening for requests on {AppSettings.UrlPrefix}..."); + // Initialize and start the attract timer + _attractTimer = new Timer(AttractTimer, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + Task listenTask = HandleIncomingConnections(listener); listenTask.GetAwaiter().GetResult(); - }/// + } + + /// + /// + /// + /// + private static void AttractTimer(object state) + { + LogIt("⏱️ AttractTimer"); + AttractAction(); + } + + private static void AttractAction() + { + DateTime now = DateTime.Now; + string currentTime = now.ToString("HH:mm:ss"); + + // Toggle the display mode every 10 seconds + if (now.Second % 10 == 0) + { + _AttractModeAlternate = !_AttractModeAlternate; + + // If switching to GIF mode, select a new random GIF + if (!_AttractModeAlternate) + { + SelectRandomGif(); + if (_currentAttractGif != null) + { + DisplayPicture(_currentAttractGif, -1, "none"); + } + } + } + + if (_AttractModeAlternate) + { + DisplayText(currentTime, "XL", "FFFFFF", "WhiteRabbit", "00FF00", "1", true, "None", 1); + } + + } + + + private static void SelectRandomGif() + { + string[] gifFiles = Directory.GetFiles(AppSettings.artworkPath, "*.gif"); + if (gifFiles.Length > 0) + { + Random random = new Random(); + int randomIndex = random.Next(gifFiles.Length); + _currentAttractGif = Path.GetFileNameWithoutExtension(gifFiles[randomIndex]); + Console.WriteLine($"Random GIF: {_currentAttractGif}"); + } + else + { + _currentAttractGif = null; + } + } + + /// /// Callback method once animation is finished. /// Displays the player's score or the game marquee picture based on the state of gScoreQueued. /// @@ -361,6 +425,7 @@ public static bool DisplayPicture(string path, float duration, string animation) return false; } + Trace.WriteLine($"File not found: {localPath}"); return false; } diff --git a/DOF2DMD/dof2dmd.csproj b/DOF2DMD/dof2dmd.csproj index 460d313..d42ba1d 100644 --- a/DOF2DMD/dof2dmd.csproj +++ b/DOF2DMD/dof2dmd.csproj @@ -19,6 +19,7 @@ + From 3946b85d0fb3806b510918be10ac24c9c231a09d Mon Sep 17 00:00:00 2001 From: Olivier Jacques Date: Tue, 12 Nov 2024 17:41:12 +0100 Subject: [PATCH 2/7] Change clock format --- DOF2DMD/Program.cs | 151 +++++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 75 deletions(-) diff --git a/DOF2DMD/Program.cs b/DOF2DMD/Program.cs index 3441aff..ec865c5 100644 --- a/DOF2DMD/Program.cs +++ b/DOF2DMD/Program.cs @@ -65,9 +65,9 @@ class DOF2DMD private static Timer _scoreTimer; private static Timer _animationTimer; private static Timer _attractTimer; + private static Timer _loopTimer; private static bool _AttractModeAlternate = true; private static string _currentAttractGif = null; - private static Timer _loopTimer; private static readonly object _scoreQueueLock = new object(); private static readonly object _animationQueueLock = new object(); private static readonly object sceneLock = new object(); @@ -158,7 +158,7 @@ static void Main() private static void ResetAttractTimer() { - + LogIt("⏱️ Received a request - resetting AttractTimer"); lock (attractTimerLock) { if (_attractTimer == null) @@ -193,8 +193,7 @@ private static void AttractTimer(object state) private static void AttractAction() { DateTime now = DateTime.Now; - string currentTime = now.ToString("HH:mm:ss"); - + string currentTime = now.Second % 2 == 0 ? now.ToString("HH:mm") : now.ToString("HH mm"); // Toggle the display mode every 10 seconds if (now.Second % 10 == 0) { @@ -243,7 +242,7 @@ private static void SelectRandomGif() /// private static void AnimationTimer(object state) { - _animationTimer.Dispose(); + _animationTimer?.Dispose(); if (AppSettings.ScoreDmd != 0) { LogIt("⏱️ AnimationTimer: now display score"); @@ -315,6 +314,7 @@ public static void LogIt(string message) if (AppSettings.Debug) { Trace.WriteLine(message); + Console.WriteLine(message); } } public static Boolean DisplayScore(int cPlayers, int player, int score, bool sCleanbg, int credits) @@ -486,91 +486,92 @@ private static BackgroundScene CreateBackgroundScene(FlexDMD.FlexDMD gDmdDevice, /// Displays text on the DMD device. /// %0A or | for line break /// - public static bool DisplayText(string text, string size, string color, string font, string bordercolor, string bordersize, bool cleanbg, string animation, float duration, bool loop) -{ - try - { - // Convert size to numeric value based on device dimensions - size = GetFontSize(size, gDmdDevice.Width, gDmdDevice.Height); + public static bool DisplayText(string text, string size, string color, string font, string bordercolor, string bordersize, bool cleanbg, string animation, float duration, bool loop) + { + try + { + // Convert size to numeric value based on device dimensions + size = GetFontSize(size, gDmdDevice.Width, gDmdDevice.Height); - // Check if the font exists - string localFontPath = $"resources/{font}_{size}"; - List extensions = new List { ".fnt", ".png" }; + // Check if the font exists + string localFontPath = $"resources/{font}_{size}"; + List extensions = new List { ".fnt", ".png" }; - if (FileExistsWithExtensions(localFontPath, extensions, out string foundExtension)) - { - localFontPath = localFontPath + ".fnt"; - } - else - { - localFontPath = $"resources/Consolas_{size}.fnt"; - LogIt($"Font not found, using default: {localFontPath}"); - } + if (FileExistsWithExtensions(localFontPath, extensions, out string foundExtension)) + { + localFontPath = localFontPath + ".fnt"; + } + else + { + localFontPath = $"resources/Consolas_{size}.fnt"; + LogIt($"Font not found, using default: {localFontPath}"); + } - // Determine if border is needed - int border = bordersize != "0" ? 1 : 0; + // Determine if border is needed + int border = bordersize != "0" ? 1 : 0; - System.Action displayAction = () => - { - // Create font and label actor - FlexDMD.Font myFont = gDmdDevice.NewFont(localFontPath, HexToColor(color), HexToColor(bordercolor), border); - var labelActor = (Actor)gDmdDevice.NewLabel("MyLabel", myFont, text); + System.Action displayAction = () => + { + // Create font and label actor + FlexDMD.Font myFont = gDmdDevice.NewFont(localFontPath, HexToColor(color), HexToColor(bordercolor), border); + var labelActor = (Actor)gDmdDevice.NewLabel("MyLabel", myFont, text); - gDmdDevice.Graphics.Clear(Color.Black); - _scoreBoard.Visible = false; + gDmdDevice.Graphics.Clear(Color.Black); + _scoreBoard.Visible = false; - var currentActor = new Actor(); - if (cleanbg) - { - _queue.RemoveAllScenes(); - _loopTimer?.Dispose(); - } + var currentActor = new Actor(); + if (cleanbg) + { + _queue.RemoveAllScenes(); + _loopTimer?.Dispose(); + } - if (duration > -1) - { - _animationTimer?.Dispose(); - _animationTimer = new Timer(AnimationTimer, null, (int)duration * 1000 + 1000, Timeout.Infinite); - } + if (duration > -1) + { + _animationTimer?.Dispose(); + _animationTimer = new Timer(AnimationTimer, null, (int)duration * 1000 + 1000, Timeout.Infinite); + } - // Create background scene based on animation type - BackgroundScene bg = CreateTextBackgroundScene(animation.ToLower(), currentActor, text, myFont, duration); + // Create background scene based on animation type + BackgroundScene bg = CreateTextBackgroundScene(animation.ToLower(), currentActor, text, myFont, duration); - _queue.Visible = true; + _queue.Visible = true; - // Add scene to the queue or directly to the stage - if (cleanbg) - { - _queue.Enqueue(bg); - _loopTimer?.Dispose(); - } - else - { - gDmdDevice.Stage.AddActor(bg); - } - }; + // Add scene to the queue or directly to the stage + if (cleanbg) + { + _queue.Enqueue(bg); + _loopTimer?.Dispose(); + } + else + { + gDmdDevice.Stage.AddActor(bg); + } + }; - // Ejecutar la acción inicial - displayAction(); + // Execute initial action + gDmdDevice.Post(displayAction); - // Si loop es verdadero, configurar el temporizador - if (loop) - { - float waitDuration = duration * 0.85f; // 15% menos que la duración - _loopTimer = new Timer(_ => + // If loop is true, configure the timer + if (loop) + { + float waitDuration = duration * 0.85f; // 15% less than duration + _loopTimer = new Timer(_ => + { + gDmdDevice.Post(displayAction); + }, null, (int)(waitDuration * 1000), (int)(waitDuration * 1000)); + } + + LogIt($"Rendering text: {text}"); + return true; + } + catch (Exception ex) { - gDmdDevice.Post(displayAction); - }, null, (int)(waitDuration * 1000), (int)(waitDuration * 1000)); + LogIt($"Error in DisplayText: {ex.Message}"); + return false; + } } - LogIt($"Rendering text: {text}"); - return true; - } - catch (Exception ex) - { - LogIt($"Error: {ex.Message}"); - return false; - } -} /// From 578a37f0568aa6120ddeb87ed4a8807e5036315d Mon Sep 17 00:00:00 2001 From: Olivier Jacques Date: Tue, 12 Nov 2024 19:18:11 +0100 Subject: [PATCH 3/7] Add Attract mode --- DOF2DMD/Program.cs | 48 ++++++++++++++++++++++++++++++-------------- DOF2DMD/settings.ini | 7 ++++++- README.md | 34 +++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/DOF2DMD/Program.cs b/DOF2DMD/Program.cs index 854839a..186a7b9 100644 --- a/DOF2DMD/Program.cs +++ b/DOF2DMD/Program.cs @@ -73,8 +73,8 @@ class DOF2DMD private static readonly object sceneLock = new object(); private static Sequence _queue; private static readonly object attractTimerLock = new object(); - private const int InactivityDelay = 30000; // 30 seconds in milliseconds private static string[] gGifFiles; + private static readonly Random _random = new Random(); public static ScoreBoard _scoreBoard; @@ -149,8 +149,8 @@ static void Main() Trace.WriteLine($"DOF2DMD is now listening for requests on {AppSettings.UrlPrefix}..."); // Initialize and start the attract timer - gGifFiles = Directory.GetFiles(AppSettings.artworkPath, "*.gif", SearchOption.AllDirectories); - _attractTimer = new Timer(AttractTimer, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + gGifFiles = Directory.GetFiles(AppSettings.artworkAttractMode, "*.gif", SearchOption.AllDirectories); + _attractTimer = new Timer(AttractTimer, null, TimeSpan.FromSeconds(AppSettings.InactivityDelayS), TimeSpan.FromSeconds(1)); Task listenTask = HandleIncomingConnections(listener); listenTask.GetAwaiter().GetResult(); @@ -163,11 +163,11 @@ private static void ResetAttractTimer() { if (_attractTimer == null) { - _attractTimer = new Timer(StartAttractMode, null, InactivityDelay, Timeout.Infinite); + _attractTimer = new Timer(AttractTimer, null, TimeSpan.FromSeconds(AppSettings.InactivityDelayS), TimeSpan.FromSeconds(1)); } else { - _attractTimer.Change(InactivityDelay, Timeout.Infinite); + _attractTimer.Change(AppSettings.InactivityDelayS * 1000, 1000); } } } @@ -186,7 +186,6 @@ private static void StartAttractMode(object state) /// private static void AttractTimer(object state) { - LogIt("⏱️ AttractTimer"); AttractAction(); } @@ -212,23 +211,28 @@ private static void AttractAction() if (_AttractModeAlternate) { - DisplayText(currentTime, "XL", "FFFFFF", "WhiteRabbit", "00FF00", "1", true, "None", 1, false); + // Display the current time in white text (FFFFFF) with green border (00FF00) in XL size, and default font + DisplayText(currentTime, "XL", "FFFFFF", "", "00FF00", "1", true, "none", 1, false); } } + /// + /// Select a random Gif for attract mode + /// private static void SelectRandomGif() { if (gGifFiles.Length > 0) { - Random random = new Random(); - int randomIndex = random.Next(gGifFiles.Length); + int randomIndex; + lock (_random) // Thread-safe access to Random + { + randomIndex = _random.Next(gGifFiles.Length); + } string fullPath = gGifFiles[randomIndex]; - string parentFolder = Path.GetFileName(Path.GetDirectoryName(fullPath)); - string fileName = Path.GetFileNameWithoutExtension(fullPath); - _currentAttractGif = Path.Combine(parentFolder, fileName); - Console.WriteLine($"Random GIF: {_currentAttractGif}"); + string relativePath = Path.GetRelativePath(AppSettings.artworkPath, fullPath); + _currentAttractGif = Path.ChangeExtension(relativePath, null); } else { @@ -303,6 +307,9 @@ static AppSettings() public static ushort dmdWidth => ushort.Parse(_configuration["dmd_width"] ?? "128"); public static ushort dmdHeight => ushort.Parse(_configuration["dmd_height"] ?? "32"); public static string StartPicture => _configuration["start_picture"] ?? "DOF2DMD"; + public static int InactivityDelayS => Int32.Parse(_configuration["inactivity_delay_s"] ?? "60"); + public static string DefaultFont => _configuration["text_font"] ?? "Consolas"; + public static string artworkAttractMode => _configuration["artwork_attract_mode"] ?? artworkPath; } /// @@ -486,6 +493,17 @@ private static BackgroundScene CreateBackgroundScene(FlexDMD.FlexDMD gDmdDevice, /// Displays text on the DMD device. /// %0A or | for line break /// + /// The text to display on the DMD + /// Font size (will be converted based on device dimensions) + /// Text color in hex format + /// Font name to use (must exist in resources folder) + /// Border color in hex format + /// Border size (0 for no border, 1 for border) + /// If true, clears all existing scenes before displaying + /// Animation type for text display. Animation can be one of "none", "scrollright", "scrollleft", "scrolldown", "scrollup" + /// Duration in seconds (-1 for permanent display) + /// If true, loops the text display with 85% of duration as interval + /// True if text was displayed successfully, false if an error occurred public static bool DisplayText(string text, string size, string color, string font, string bordercolor, string bordersize, bool cleanbg, string animation, float duration, bool loop) { try @@ -503,8 +521,8 @@ public static bool DisplayText(string text, string size, string color, string fo } else { - localFontPath = $"resources/Consolas_{size}.fnt"; - LogIt($"Font not found, using default: {localFontPath}"); + localFontPath = $"resources/{AppSettings.DefaultFont}_{size}.fnt"; + //LogIt($"Font not found, using default: {localFontPath}"); } // Determine if border is needed diff --git a/DOF2DMD/settings.ini b/DOF2DMD/settings.ini index 7e114a5..ee59ff9 100644 --- a/DOF2DMD/settings.ini +++ b/DOF2DMD/settings.ini @@ -16,8 +16,13 @@ url_prefix=http://127.0.0.1:8080 ;Activate the autoshow of the Scoreboard or Marquee after using a call ;score_dmd=1 ;marquee_dmd=1 +;Delay in seconds before attract mode starts. Defaults to 60 (1 minute). +;inactivity_delay_s=60 +;Attract mode artwork folder. If not set, defaults to artwork_path +;artwork_attract_mode=artwork/attract +;Default text font +;text_font=Consolas ; Not implemented --- ;scene_default=marquee ;number_of_dmd=1 ;animation_dmd=1 - diff --git a/README.md b/README.md index dec77fa..3d0fc5f 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,40 @@ uses [Freezy DMD extensions](https://github.com/freezy/dmd-extensions) DOFLinx, which in turn will trigger API calls to DOF2DMD. - Enjoy! +## Attract Mode + +DOF2DMD includes an attract mode feature that displays a clock and random animations when the system is inactive. + +### How it works +- After a period of inactivity (default: 60 seconds), DOF2DMD will start cycling through random GIF animations from your artwork folder, and a clock +- Each animation is displayed for 10 seconds before switching to the next one +- Any API call will reset the inactivity timer, so that the attract mode will automatically stop when new content needs to be displayed + +### Configuration +In `settings.ini`, you can customize the attract mode behavior: + +```ini +; Delay in seconds before attract mode starts. Defaults to 60 (1 minute) +inactivity_delay_s=60 +``` + +### Artwork for Attract Mode + +- Place your GIF animations in the artwork folder or any subfolder +- All GIF files in the artwork directory tree will be included in the random selection +- Recommended: Create an "attract" subfolder in your artwork directory for dedicated attract mode animations, and set artwork_attract_mode in your ini file + + ```ini + ;Attract mode artwork folder. If not set, defaults to artwork_path + artwork_attract_mode=artwork/attract + ``` + +### Tips + +- Use high-quality, looping GIF animations for the best attract mode experience +- Consider creating themed collections in subfolders (e.g., artwork/attract/classics/, artwork/attract/modern/) +- The attract mode is great for showcasing your cabinet when idle + ## Artwork The images and animations must be in the `artwork` folder (by default in the DOF2DMD path under the `artwork` folder). From 9e7a24782019557be0e695d15fe9254529829384 Mon Sep 17 00:00:00 2001 From: Olivier Jacques Date: Mon, 16 Dec 2024 22:58:27 +0100 Subject: [PATCH 4/7] Let videos play in full for attract mode --- DOF2DMD/Program.cs | 70 ++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/DOF2DMD/Program.cs b/DOF2DMD/Program.cs index 186a7b9..1d59677 100644 --- a/DOF2DMD/Program.cs +++ b/DOF2DMD/Program.cs @@ -65,6 +65,7 @@ class DOF2DMD private static Timer _scoreTimer; private static Timer _animationTimer; private static Timer _attractTimer; + private static Timer _attractChangeTimer; private static Timer _loopTimer; private static bool _AttractModeAlternate = true; private static string _currentAttractGif = null; @@ -159,6 +160,8 @@ static void Main() private static void ResetAttractTimer() { LogIt("⏱️ Received a request - resetting AttractTimer"); + _attractChangeTimer?.Dispose(); + _attractChangeTimer = null; lock (attractTimerLock) { if (_attractTimer == null) @@ -172,47 +175,52 @@ private static void ResetAttractTimer() } } - private static void StartAttractMode(object state) - { - // Your existing attract mode logic goes here - SelectRandomGif(); - // Add any other attract mode initialization code - } - - /// /// /// /// private static void AttractTimer(object state) { + // By default, arm timer so attract display is changed in 10 seconds + // The _attractChangeTimer will be changed to expire after a video is fully displayed + if (_attractChangeTimer == null) + { + LogIt($"Setting next AttractChange in 10 seconds"); + _attractChangeTimer = new Timer(AttractChange, null, 10 * 1000, Timeout.Infinite); + } AttractAction(); } - private static void AttractAction() + private static void AttractChange(object state) { - DateTime now = DateTime.Now; - string currentTime = now.Second % 2 == 0 ? now.ToString("HH:mm") : now.ToString("HH mm"); - // Toggle the display mode every 10 seconds - if (now.Second % 10 == 0) + LogIt("AttractChange expired - changing AttractMode"); + _AttractModeAlternate = !_AttractModeAlternate; + // By default, arm timer so attract display is changed in 10 seconds + // The _attractChangeTimer will be changed to expire after a video is fully displayed + _attractChangeTimer?.Dispose(); + LogIt($"Setting next AttractChange in 10 seconds"); + _attractChangeTimer = new Timer(AttractChange, null, 10 * 1000, Timeout.Infinite); + // If switching to GIF mode, select a new random GIF + if (!_AttractModeAlternate) { - _AttractModeAlternate = !_AttractModeAlternate; - - // If switching to GIF mode, select a new random GIF - if (!_AttractModeAlternate) + SelectRandomGif(); + if (_currentAttractGif != null) { - SelectRandomGif(); - if (_currentAttractGif != null) - { - DisplayPicture(_currentAttractGif, -1, "none"); - } + DisplayPicture(_currentAttractGif, 0, "none"); } } + } + + private static void AttractAction() + { + DateTime now = DateTime.Now; + string currentTime = now.Second % 2 == 0 ? now.ToString("HH:mm") : now.ToString("HH mm"); if (_AttractModeAlternate) { // Display the current time in white text (FFFFFF) with green border (00FF00) in XL size, and default font - DisplayText(currentTime, "XL", "FFFFFF", "", "00FF00", "1", true, "none", 1, false); + //DisplayText(currentTime, "XL", "FFFFFF", "", "00FF00", "1", true, "none", 1, false);* + DisplayText(currentTime, "XL", "FFFFFF", "", "FFFFFF", "0", true, "none", 1, false); } } @@ -249,9 +257,9 @@ private static void AnimationTimer(object state) _animationTimer?.Dispose(); if (AppSettings.ScoreDmd != 0) { - LogIt("⏱️ AnimationTimer: now display score"); if (gScore[gActivePlayer] > 0) { + LogIt("AnimationTimer: now display score"); DisplayScoreboard(gNbPlayers, gActivePlayer, gScore[1], gScore[2], gScore[3], gScore[4], "", "", true); } } @@ -414,7 +422,6 @@ public static bool DisplayPicture(string path, float duration, string animation) { gDmdDevice.Clear = true; - // Liberar recursos existentes if (_queue.ChildCount >= 1) { @@ -439,6 +446,9 @@ public static bool DisplayPicture(string path, float duration, string animation) // Arm timer to restore to score, once animation is done playing _animationTimer?.Dispose(); _animationTimer = new Timer(AnimationTimer, null, (int)duration * 1000 + 1000, Timeout.Infinite); + _attractChangeTimer?.Dispose(); + LogIt($"Setting next AttractChange in {duration} seconds (video duration)"); + _attractChangeTimer = new Timer(AttractChange, null, (int)duration * 1000, Timeout.Infinite); } } @@ -544,11 +554,11 @@ public static bool DisplayText(string text, string size, string color, string fo _loopTimer?.Dispose(); } - if (duration > -1) - { - _animationTimer?.Dispose(); - _animationTimer = new Timer(AnimationTimer, null, (int)duration * 1000 + 1000, Timeout.Infinite); - } + // if (duration > -1) + // { + // _animationTimer?.Dispose(); + // _animationTimer = new Timer(AnimationTimer, null, (int)duration * 1000 + 1000, Timeout.Infinite); + // } // Create background scene based on animation type BackgroundScene bg = CreateTextBackgroundScene(animation.ToLower(), currentActor, text, myFont, duration); From 179072faff5644b401ed3398c743a984e3d596fb Mon Sep 17 00:00:00 2001 From: Olivier Jacques Date: Sun, 19 Jan 2025 02:50:27 +0100 Subject: [PATCH 5/7] Massively improve startup time --- DOF2DMD/Program.cs | 132 ++++++++++++++++++++++++++--------------- DOF2DMD/dof2dmd.csproj | 56 +++-------------- 2 files changed, 91 insertions(+), 97 deletions(-) diff --git a/DOF2DMD/Program.cs b/DOF2DMD/Program.cs index 9b2772b..419416b 100644 --- a/DOF2DMD/Program.cs +++ b/DOF2DMD/Program.cs @@ -72,13 +72,43 @@ class DOF2DMD public static ScoreBoard _scoreBoard; - static void Main() + static async Task Main() { // Set up logging to a file Trace.Listeners.Add(new TextWriterTraceListener("dof2dmd.log") { TraceOutputOptions = TraceOptions.Timestamp }); + Trace.Listeners.Add(new ConsoleTraceListener()); Trace.AutoFlush = true; - // Initializing DMD + LogIt("Starting DOF2DMD..."); + // Start the http listener first + LogIt("Starting HTTP listener"); + HttpListener listener = new HttpListener(); + listener.Prefixes.Add($"{AppSettings.UrlPrefix}/"); + listener.Start(); + LogIt($"DOF2DMD is now listening for requests on {AppSettings.UrlPrefix}..."); + + // Initialize DMD in parallel + LogIt("Starting DMD initialization"); + var dmdInitTask = Task.Run(() => InitializeDMD()); + + // Start handling HTTP connections + LogIt("Starting HTTP connection handler"); + var listenTask = HandleIncomingConnections(listener); + + // Wait for DMD initialization to complete + LogIt("Waiting for DMD initialization to complete"); + await dmdInitTask; + + // Wait for the HTTP listener + LogIt("DOF2DMD now fully initialized!"); + await listenTask; + } + + private static void InitializeDMD() + { + var grayColor = Color.FromArgb(168, 168, 168); + + // Initialize DMD device with configuration gDmdDevice = new FlexDMD.FlexDMD { Width = AppSettings.dmdWidth, @@ -90,65 +120,54 @@ static void Main() Run = true }; - _queue = new Sequence(gDmdDevice); - _queue.FillParent = true; + // Initialize sequence + _queue = new Sequence(gDmdDevice) { FillParent = true }; - //DMDScene = (Group)gDmdDevice.NewGroup("Scene"); - - FlexDMD.Font _scoreFontText; - FlexDMD.Font _scoreFontNormal; - FlexDMD.Font _scoreFontHighlight; - - // UltraDMD uses f4by5 / f5by7 / f6by12 - if(gDmdDevice.Height == 64 && gDmdDevice.Width == 256) - { - _scoreFontText = gDmdDevice.NewFont("FlexDMD.Resources.udmd-f6by12.fnt", Color.FromArgb(168, 168, 168), Color.Black,1); - _scoreFontNormal = gDmdDevice.NewFont("FlexDMD.Resources.udmd-f7by13.fnt", Color.FromArgb(168, 168, 168), Color.Black,1); - _scoreFontHighlight = gDmdDevice.NewFont("FlexDMD.Resources.udmd-f12by24.fnt", Color.Orange, Color.Red, 1); - } - else - { - - _scoreFontText = gDmdDevice.NewFont("FlexDMD.Resources.udmd-f6by12.fnt", Color.FromArgb(168, 168, 168), Color.Black,1); - _scoreFontNormal = gDmdDevice.NewFont("FlexDMD.Resources.udmd-f7by13.fnt", Color.FromArgb(168, 168, 168), Color.Black,1); - _scoreFontHighlight = gDmdDevice.NewFont("FlexDMD.Resources.udmd-f12by24.fnt", Color.Orange, Color.Red, 1); - } + // Initialize fonts + var fonts = InitializeFonts(gDmdDevice, grayColor); + // Initialize scoreboard _scoreBoard = new ScoreBoard( gDmdDevice, - _scoreFontNormal, - _scoreFontHighlight, - _scoreFontText - ) + fonts.NormalFont, + fonts.HighlightFont, + fonts.TextFont + ) { Visible = false }; - - + // Add actors to stage gDmdDevice.Stage.AddActor(_queue); gDmdDevice.Stage.AddActor(_scoreBoard); - // Display start picture as game marquee + // Set and display game marquee gGameMarquee = AppSettings.StartPicture; - - Thread.Sleep(500); DisplayPicture(gGameMarquee, -1, "none"); + } + private static (FlexDMD.Font TextFont, FlexDMD.Font NormalFont, FlexDMD.Font HighlightFont) InitializeFonts( + FlexDMD.FlexDMD device, Color grayColor) + { + // Font configurations + var fontConfig = new[] + { + new { Path = "FlexDMD.Resources.udmd-f6by12.fnt", ForeColor = grayColor }, + new { Path = "FlexDMD.Resources.udmd-f7by13.fnt", ForeColor = grayColor }, + new { Path = "FlexDMD.Resources.udmd-f12by24.fnt", ForeColor = Color.Orange } + }; + + return ( + TextFont: device.NewFont(fontConfig[0].Path, fontConfig[0].ForeColor, Color.Black, 1), + NormalFont: device.NewFont(fontConfig[1].Path, fontConfig[1].ForeColor, Color.Black, 1), + HighlightFont: device.NewFont(fontConfig[2].Path, fontConfig[2].ForeColor, Color.Red, 1) + ); + } - // Start the http listener - HttpListener listener = new HttpListener(); - listener.Prefixes.Add($"{AppSettings.UrlPrefix}/"); - listener.Start(); - Trace.WriteLine($"DOF2DMD is now listening for requests on {AppSettings.UrlPrefix}..."); - Task listenTask = HandleIncomingConnections(listener); - listenTask.GetAwaiter().GetResult(); - } - /// - /// Callback method once animation is finished. - /// Displays the player's score - /// + /// Callback method once animation is finished. + /// Displays the player's score + /// private static void AnimationTimer(object state) { _animationTimer.Dispose(); @@ -222,8 +241,7 @@ public static void LogIt(string message) // If debug is enabled if (AppSettings.Debug) { - Trace.WriteLine(message); - } + Trace.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {message}"); } } public static Boolean DisplayScore(int cPlayers, int player, int score, bool sCleanbg, int credits) { @@ -275,7 +293,7 @@ public static bool DisplayScoreboard(int cPlayers, int highlightedPlayer, Int64 } catch (Exception ex) { - Trace.WriteLine($" Error occurred while genering the Score Board. {ex.Message}"); + LogIt($" Error occurred while genering the Score Board. {ex.Message}"); return false; } } @@ -288,6 +306,22 @@ public static bool DisplayPicture(string path, float duration, string animation) { if (string.IsNullOrEmpty(path)) return false; + + // Retry if gDmdDevice is null + int retries = 10; + while (gDmdDevice == null && retries > 0) + { + Thread.Sleep(1000); + LogIt($"Retrying DMD device initialization {retries} retries left"); + retries--; + } + + if (gDmdDevice == null) + { + LogIt("DMD device initialization failed 10 retries"); + return false; + } + // Check if path is a full path or a relative path (use AppSettings.artworkPath if necessary) string localPath; if (Path.IsPathRooted(path)) // If the path is a full path (starts with a drive letter, e.g., G:/) @@ -364,7 +398,7 @@ public static bool DisplayPicture(string path, float duration, string animation) } catch (Exception ex) { - Trace.WriteLine($"Error occurred while fetching the image. {ex.Message}"); + LogIt($"Error occurred while fetching the image. {ex.Message}"); return false; } diff --git a/DOF2DMD/dof2dmd.csproj b/DOF2DMD/dof2dmd.csproj index 460d313..5d66381 100644 --- a/DOF2DMD/dof2dmd.csproj +++ b/DOF2DMD/dof2dmd.csproj @@ -3,18 +3,14 @@ Exe net8.0-windows - true + false true None + + true + Speed - - - PreserveNewest - true - - - @@ -29,51 +25,15 @@ - - PreserveNewest - true - - - - - PreserveNewest - true - - - - - PreserveNewest - true - - - - - PreserveNewest - true - - - - - PreserveNewest - true - - - - - PreserveNewest - true - - - - + PreserveNewest true - - - + + PreserveNewest true + From a9d2b13bc50db7f65a0a36c47a5ae51b251f874b Mon Sep 17 00:00:00 2001 From: Olivier Jacques Date: Sun, 19 Jan 2025 02:56:59 +0100 Subject: [PATCH 6/7] Update build to take into account all files --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ccefd6..def51ab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,14 +49,14 @@ jobs: run: | copy FlexDMD.dll dof2dmd copy DmdDevice64.dll dof2dmd - dotnet publish -r win-x64 --self-contained=false /p:PublishSingleFile=true dof2dmd/dof2dmd.csproj /p:Version=${{ github.ref_name }} + dotnet publish /p:Version=${{ github.ref_name }} - if: "!startsWith(github.ref, 'refs/tags/')" name: Build run: | copy FlexDMD.dll dof2dmd copy DmdDevice64.dll dof2dmd - dotnet publish -r win-x64 --self-contained=false /p:PublishSingleFile=true dof2dmd/dof2dmd.csproj + dotnet publish # Upload artifacts - name: Upload artifacts From 4a0da5b0c9998c4839af35ba2b4c92e8907fb0aa Mon Sep 17 00:00:00 2001 From: Olivier Jacques Date: Sun, 19 Jan 2025 02:59:43 +0100 Subject: [PATCH 7/7] Update build to take into account all files --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index def51ab..fa203ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,13 +63,13 @@ jobs: uses: actions/upload-artifact@v4 with: name: DOF2DMD - path: .\DOF2DMD\bin\Release\net8.0-windows\win-x64\publish + path: .\DOF2DMD\bin\Release\net8.0-windows\publish retention-days: 7 - name: Generate zip bundle run: | # tree /f - 7z a -tzip DOF2DMD.zip .\DOF2DMD\bin\Release\net8.0-windows\win-x64\publish\* + 7z a -tzip DOF2DMD.zip .\DOF2DMD\bin\Release\net8.0-windows\publish\* - if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true name: Publish latest pre-release