1/*
2 * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#include "eventdispatcher.h"
24
25#include <framework/core/clock.h>
26#include <framework/core/graphicalapplication.h>
27#include <framework/graphics/graph.h>
28#include <framework/util/stats.h>
29#include "timer.h"
30
31EventDispatcher g_dispatcher;
32EventDispatcher g_graphicsDispatcher;
33std::thread::id g_mainThreadId = std::this_thread::get_id();
34std::thread::id g_graphicsThreadId = std::this_thread::get_id();
35std::thread::id g_dispatcherThreadId = std::this_thread::get_id();
36
37void EventDispatcher::shutdown()
38{
39 while(!m_eventList.empty())
40 poll();
41
42 while(!m_scheduledEventList.empty()) {
43 ScheduledEventPtr scheduledEvent = m_scheduledEventList.top();
44 scheduledEvent->cancel();
45 m_scheduledEventList.pop();
46 }
47 m_disabled = true;
48}
49
50void EventDispatcher::poll()
51{
52 AutoStat s(this == &g_dispatcher ? STATS_MAIN : STATS_RENDER, "PollDispatcher");
53 std::unique_lock<std::recursive_mutex> lock(m_mutex);
54
55 int events = 0;
56 int loops = 0;
57 for(int count = 0, max = m_scheduledEventList.size(); count < max && !m_scheduledEventList.empty(); ++count) {
58 ScheduledEventPtr scheduledEvent = m_scheduledEventList.top();
59 if(scheduledEvent->remainingTicks() > 0)
60 break;
61 m_scheduledEventList.pop();
62 {
63 AutoStat s2(STATS_DISPATCHER, scheduledEvent->getFunction());
64 m_botSafe = scheduledEvent->isBotSafe();
65 lock.unlock();
66 scheduledEvent->execute();
67 events += 1;
68 lock.lock();
69 }
70
71 if(scheduledEvent->nextCycle())
72 m_scheduledEventList.push(scheduledEvent);
73 }
74
75 // execute events list until all events are out, this is needed because some events can schedule new events that would
76 // change the UIWidgets layout, in this case we must execute these new events before we continue rendering,
77 m_pollEventsSize = m_eventList.size();
78 loops = 0;
79 while(m_pollEventsSize > 0) {
80 if(loops > 100) {
81 static Timer reportTimer;
82 if(reportTimer.running() && reportTimer.ticksElapsed() > 500) {
83 std::stringstream ss;
84 ss << "ATTENTION the event list is not getting empty, this could be caused by some bad code.\nLog:\n";
85 for (auto& event : m_eventList) {
86 ss << event->getFunction() << "\n";
87 if (ss.str().size() > 512) break;
88 }
89 g_logger.error(ss.str());
90 reportTimer.restart();
91 }
92 break;
93 }
94
95 for(int i=0;i<m_pollEventsSize;++i) {
96 EventPtr event = m_eventList.front();
97 m_eventList.pop_front();
98 {
99 AutoStat s2(STATS_DISPATCHER, event->getFunction());
100 m_botSafe = event->isBotSafe();
101 lock.unlock();
102 event->execute();
103 events += 1;
104 lock.lock();
105 }
106 }
107 m_pollEventsSize = m_eventList.size();
108
109 loops++;
110 }
111
112 g_graphs[this == &g_dispatcher ? GRAPH_DISPATCHER_EVENTS : GRAPH_GRAPHICS_EVENTS].addValue(events, true);
113
114 m_botSafe = false;
115}
116
117ScheduledEventPtr EventDispatcher::scheduleEventEx(const std::string& function, const std::function<void()>& callback, int delay)
118{
119 if(m_disabled)
120 return ScheduledEventPtr(new ScheduledEvent("", nullptr, delay, 1));
121
122 std::lock_guard<std::recursive_mutex> lock(m_mutex);
123
124 VALIDATE(delay >= 0);
125 ScheduledEventPtr scheduledEvent(new ScheduledEvent(function, callback, delay, 1, g_app.isOnInputEvent()));
126 m_scheduledEventList.push(scheduledEvent);
127 return scheduledEvent;
128}
129
130ScheduledEventPtr EventDispatcher::cycleEventEx(const std::string& function, const std::function<void()>& callback, int delay)
131{
132 if(m_disabled)
133 return ScheduledEventPtr(new ScheduledEvent("", nullptr, delay, 0));
134
135 std::lock_guard<std::recursive_mutex> lock(m_mutex);
136
137 VALIDATE(delay > 0);
138 ScheduledEventPtr scheduledEvent(new ScheduledEvent(function, callback, delay, 0, g_app.isOnInputEvent()));
139 m_scheduledEventList.push(scheduledEvent);
140 return scheduledEvent;
141}
142
143EventPtr EventDispatcher::addEventEx(const std::string& function, const std::function<void()>& callback, bool pushFront)
144{
145 if(m_disabled)
146 return EventPtr(new Event("", nullptr));
147
148 EventPtr event(new Event(function, callback, g_app.isOnInputEvent()));
149
150 std::lock_guard<std::recursive_mutex> lock(m_mutex);
151
152 // front pushing is a way to execute an event before others
153 if(pushFront) {
154 m_eventList.push_front(event);
155 // the poll event list only grows when pushing into front
156 m_pollEventsSize++;
157 } else
158 m_eventList.push_back(event);
159 return event;
160}
161
162