1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17#include "C4Include.h"
18#include "C4InteractiveThread.h"
19#include "C4Application.h"
20#include "C4Log.h"
21
22#include <C4Game.h>
23
24#include <cassert>
25
26// *** C4InteractiveThread
27
28C4InteractiveThread::C4InteractiveThread()
29{
30 // Add head-item
31 pFirstEvent = pLastEvent = new Event();
32 pFirstEvent->Type = Ev_None;
33 pFirstEvent->Next = nullptr;
34 // reset event handlers
35 std::fill(first: pCallbacks, last: std::end(arr&: pCallbacks), value: nullptr);
36}
37
38C4InteractiveThread::~C4InteractiveThread()
39{
40 CStdLock PushLock(&EventPushCSec), PopLock(&EventPopCSec);
41 // Remove all items. This may leak data, if pData was allocated on the heap.
42 while (PopEvent(pEventType: nullptr, data: nullptr));
43 // Delete head-item
44 delete pFirstEvent;
45 pFirstEvent = pLastEvent = nullptr;
46}
47
48bool C4InteractiveThread::AddProc(StdSchedulerProc *pProc)
49{
50 bool fFirst = !Scheduler.getProcCnt();
51 // Add the proc
52 Scheduler.Add(pProc);
53 // Not started yet?
54 if (fFirst)
55 if (!Scheduler.Start())
56 return false;
57 return true;
58}
59
60void C4InteractiveThread::RemoveProc(StdSchedulerProc *pProc)
61{
62 Scheduler.Remove(pProc);
63 // Last proc to be removed?
64 if (Scheduler.getProcCnt() == 0)
65 {
66 Scheduler.Stop();
67 }
68}
69
70bool C4InteractiveThread::PushEvent(C4InteractiveEventType eEvent, std::any data)
71{
72 CStdLock PushLock(&EventPushCSec);
73 if (!pLastEvent) return false;
74 // create event
75 Event *pEvent = new Event;
76 pEvent->Type = eEvent;
77 pEvent->Data = std::move(data);
78#ifndef NDEBUG
79 pEvent->Time = timeGetTime();
80#endif
81 pEvent->Next = nullptr;
82 // add item (at end)
83 pLastEvent->Next = pEvent;
84 pLastEvent = pEvent;
85 PushLock.Clear();
86#ifdef _WIN32
87 // post message to main thread
88 try
89 {
90 Application.NetworkEvent.Set();
91 }
92 catch (const std::runtime_error &)
93 {
94 LogFatalNTr("Network: could not post message to main thread!");
95 }
96
97 return true;
98#else
99 return Application.SignalNetworkEvent();
100#endif
101}
102
103#ifndef NDEBUG
104double AvgNetEvDelay = 0;
105#endif
106
107bool C4InteractiveThread::PopEvent(C4InteractiveEventType *pEventType, std::any *data) // (by main thread)
108{
109 CStdLock PopLock(&EventPopCSec);
110 if (!pFirstEvent) return false;
111 // get event
112 Event *pEvent = pFirstEvent->Next;
113 if (!pEvent) return false;
114 // return
115 if (pEventType)
116 *pEventType = pEvent->Type;
117 if (data)
118 *data = std::move(pEvent->Data);
119#ifndef NDEBUG
120 if (Game.IsRunning)
121 AvgNetEvDelay += ((timeGetTime() - pEvent->Time) - AvgNetEvDelay) / 100;
122#endif
123 // remove
124 delete pFirstEvent;
125 pFirstEvent = pEvent;
126 pFirstEvent->Type = Ev_None;
127 return true;
128}
129
130void C4InteractiveThread::ProcessEvents() // by main thread
131{
132 C4InteractiveEventType eEventType; std::any eventData;
133 while (PopEvent(pEventType: &eEventType, data: &eventData))
134 switch (eEventType)
135 {
136 // Execute in main thread
137 case Ev_ExecuteInMainThread:
138 std::any_cast<const std::function<void()> &>(any&: eventData)();
139 break;
140
141 // Other events: check for a registered handler
142 default:
143 if (eEventType >= Ev_None && eEventType <= Ev_Last)
144 if (pCallbacks[eEventType])
145 pCallbacks[eEventType]->OnThreadEvent(eEvent: eEventType, eventData);
146 // Note that memory might leak if the event wasn't processed....
147 }
148}
149