/[svn]/linuxsampler/trunk/src/engines/InstrumentManagerThread.cpp
ViewVC logotype

Contents of /linuxsampler/trunk/src/engines/InstrumentManagerThread.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4019 - (show annotations) (download)
Mon Jan 3 17:45:26 2022 UTC (2 years, 3 months ago) by schoenebeck
File size: 11471 byte(s)
* SFZ engine: Automatically reload .sfz files on file changes
  (e.g. by some external text editor).

* Bumped version (2.2.0.svn14).

1 /***************************************************************************
2 * *
3 * Copyright (C) 2005 - 2022 Christian Schoenebeck *
4 * *
5 * This library is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This library is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this library; if not, write to the Free Software *
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
18 * MA 02111-1307 USA *
19 ***************************************************************************/
20
21 #include "InstrumentManagerThread.h"
22
23 #include "../common/global_private.h"
24 #include "EngineChannelFactory.h"
25
26 // how often periodic jobs for external components shall be executed
27 // (every 200ms)
28 #define PERIODIC_JOBS_SEC 0
29 #define PERIODIC_JOBS_NSEC (200 * 1000 * 1000)
30
31 namespace LinuxSampler {
32
33 InstrumentManagerThread::InstrumentManagerThread() : Thread(true, false, 0, -4) {
34 eventHandler.pThread = this;
35 }
36
37 InstrumentManagerThread::~InstrumentManagerThread() {
38 Thread::StopThread();
39 }
40
41 /**
42 * @brief Order loading of a new instrument.
43 *
44 * The request will go into a queue waiting to be processed by the
45 * class internal task thread. This method will immediately return and
46 * the instrument will be loaded in the background.
47 *
48 * @param Filename - file name of the instrument
49 * @param uiInstrumentIndex - index of the instrument within the file
50 * @param pEngineChannel - engine channel on which the instrument should be loaded
51 */
52 void InstrumentManagerThread::StartNewLoad(String Filename, uint uiInstrumentIndex, EngineChannel* pEngineChannel) {
53 dmsg(1,("Scheduling '%s' (Index=%d) to be loaded in background (if not loaded yet).\n",Filename.c_str(),uiInstrumentIndex));
54
55 // the listener only needs to be registered once in the
56 // Sampler, but as we don't know if Sampler has been
57 // recreated, we simply remove and add every time
58 pEngineChannel->GetSampler()->RemoveChannelCountListener(&eventHandler);
59 pEngineChannel->GetSampler()->AddChannelCountListener(&eventHandler);
60
61 command_t cmd;
62 cmd.type = command_t::DIRECT_LOAD;
63 cmd.pEngineChannel = pEngineChannel;
64 cmd.instrumentId.Index = uiInstrumentIndex;
65 cmd.instrumentId.FileName = Filename;
66
67 {
68 LockGuard lock(mutex);
69 queue.push_back(cmd);
70 }
71
72 StartThread(); // ensure thread is running
73 conditionJobsLeft.Set(true); // wake up thread
74 }
75
76 /**
77 * @brief Order changing the life-time strategy of an instrument.
78 *
79 * The request will go into a queue waiting to be processed by the
80 * class internal task thread. This method will immediately return and
81 * in case the instrument has to be loaded due to a mode change to
82 * PERSISTENT, it will be loaded in the background.
83 *
84 * @param pManager - InstrumentManager which manages the instrument
85 * @param ID - unique ID of the instrument
86 * @param Mode - life-time strategy to set for this instrument
87 */
88 void InstrumentManagerThread::StartSettingMode(InstrumentManager* pManager, const InstrumentManager::instrument_id_t& ID, InstrumentManager::mode_t Mode) {
89 command_t cmd;
90 cmd.type = command_t::INSTR_MODE;
91 cmd.pManager = pManager;
92 cmd.instrumentId = ID;
93 cmd.mode = Mode;
94
95 {
96 LockGuard lock(mutex);
97 queue.push_back(cmd);
98 }
99
100 StartThread(); // ensure thread is running
101 conditionJobsLeft.Set(true); // wake up thread
102 }
103
104 // Entry point for the task thread.
105 int InstrumentManagerThread::Main() {
106 #if DEBUG
107 Thread::setNameOfCaller("InstrumentMngr");
108 #endif
109
110 while (true) {
111
112 TestCancel();
113
114 // prevent thread from being cancelled
115 // (e.g. to prevent deadlocks while holding mutex lock(s))
116 pushCancelable(false);
117
118 while (true) {
119 command_t cmd;
120
121 // grab a new command from the queue
122 {
123 LockGuard lock(mutex);
124 if (queue.empty()) break;
125
126 cmd = queue.front();
127 queue.pop_front();
128
129 if (cmd.type == command_t::DIRECT_LOAD) {
130 EngineChannelFactory::SetDeleteEnabled(cmd.pEngineChannel, false);
131 }
132 }
133
134 try {
135 switch (cmd.type) {
136 case command_t::DIRECT_LOAD:
137 cmd.pEngineChannel->PrepareLoadInstrument(cmd.instrumentId.FileName.c_str(), cmd.instrumentId.Index);
138 cmd.pEngineChannel->LoadInstrument();
139 EngineChannelFactory::SetDeleteEnabled(cmd.pEngineChannel, true);
140 break;
141 case command_t::INSTR_MODE:
142 cmd.pManager->SetMode(cmd.instrumentId, cmd.mode);
143 break;
144 default:
145 std::cerr << "InstrumentManagerThread: unknown command - BUG!\n" << std::flush;
146 }
147 } catch (Exception e) {
148 e.PrintMessage();
149 if (cmd.type == command_t::DIRECT_LOAD) {
150 EngineChannelFactory::SetDeleteEnabled(cmd.pEngineChannel, true);
151 }
152 } catch (...) {
153 std::cerr << "InstrumentManagerThread: some exception occured, could not finish task\n" << std::flush;
154 if (cmd.type == command_t::DIRECT_LOAD) {
155 EngineChannelFactory::SetDeleteEnabled(cmd.pEngineChannel, true);
156 }
157 }
158 }
159
160 // perform periodic, custom jobs on behalf of external components
161 {
162 LockGuard lock(periodicJobsMutex);
163 for (ext_job_t job : periodicJobs) {
164 job.fn();
165 }
166 }
167
168 // now allow thread being cancelled again
169 // (since all mutexes are now unlocked)
170 popCancelable();
171
172 // nothing left to do, sleep until new jobs arrive
173 const bool timedOut =
174 (AnyPeriodicJobs()) ?
175 // do wait, but with a maximum timeout value
176 conditionJobsLeft.WaitIf(false, PERIODIC_JOBS_SEC, PERIODIC_JOBS_NSEC) :
177 // wait indefinitely
178 conditionJobsLeft.WaitIf(false);
179 // reset flag
180 if (!timedOut) {
181 conditionJobsLeft.Set(false);
182 // unlock condition object so it can be turned again by other thread
183 conditionJobsLeft.Unlock();
184 }
185 }
186 return 0;
187 }
188
189 /**
190 * Schedule a job to be executed by instrument manager thread periodically.
191 *
192 * This method is intended to be used by external components to execute
193 * instrument files related tasks periodically. Currently this is hard
194 * coded to happen approximately every 200ms, however if the instrument
195 * manager thread is currently blocked by executing other tasks (e.g.
196 * loading some instrument) then this might also be significantly delayed
197 * accordingly.
198 *
199 * The scheduled task will continue to be executed indefinitely until
200 * @c RemovePeriodicJob() is called.
201 *
202 * Currently this method is only used by the SFZ engine for its auto
203 * reload feature (i.e. on modifications of .sfz files by external apps).
204 *
205 * @param name - arbitrary but unique ID for the task
206 * @param fn - actual callback for the task to be executed
207 */
208 void InstrumentManagerThread::AddPeriodicJob(String name, std::function<void()> fn) {
209 LockGuard lock(periodicJobsMutex);
210 RemovePeriodicJobWithoutLock(name);
211 periodicJobs.push_back({
212 .name = name,
213 .fn = fn
214 });
215 }
216
217 /**
218 * Unschedule a periodic task previously scheduled by @c AddPeriodicJob().
219 *
220 * @param name - unique ID of the previously scheduled periodic task
221 */
222 void InstrumentManagerThread::RemovePeriodicJob(String name) {
223 LockGuard lock(periodicJobsMutex);
224 RemovePeriodicJobWithoutLock(name);
225 }
226
227 void InstrumentManagerThread::RemovePeriodicJobWithoutLock(String name) {
228 for (size_t i = 0; i < periodicJobs.size(); ++i) {
229 if (periodicJobs[i].name == name) {
230 periodicJobs.erase( periodicJobs.begin() + i );
231 return;
232 }
233 }
234 }
235
236 bool InstrumentManagerThread::AnyPeriodicJobs() {
237 // would probably be OK without a lock, but strictly it would be undefined behaviour
238 LockGuard lock(periodicJobsMutex);
239 return !periodicJobs.empty();
240 }
241
242 void InstrumentManagerThread::EventHandler::ChannelToBeRemoved(SamplerChannel* pChannel) {
243 /*
244 Removing from the queue an eventual scheduled loading of an instrument
245 to a sampler channel which is going to be removed.
246 */
247 LockGuard lock(pThread->mutex);
248 std::list<command_t>::iterator it;
249 for (it = pThread->queue.begin(); it != pThread->queue.end();){
250 if ((*it).type != command_t::DIRECT_LOAD) { ++it; continue; }
251 if ((*it).pEngineChannel == pChannel->GetEngineChannel()) {
252 it = pThread->queue.erase(it);
253 // we don't break here because the same engine channel could
254 // occur more than once in the queue, so don't make optimizations
255 } else {
256 ++it;
257 }
258 }
259 }
260
261 #if defined(__APPLE__) && !defined(__x86_64__)
262 int InstrumentManagerThread::StopThread() {
263 // This is a fix for Mac OS X 32 bit, where SignalStopThread
264 // doesn't wake up a thread waiting for a condition variable.
265 SignalStopThread(); // send stop signal, but don't wait
266 conditionJobsLeft.Set(true); // wake thread
267 return Thread::StopThread(); // then wait for it to cancel
268 }
269 #endif
270
271 #ifdef WIN32
272 int InstrumentManagerThread::StopThread() {
273 int res = Thread::StopThread();
274 conditionJobsLeft.Reset();
275 return res;
276 }
277 #endif
278
279 } // namespace LinuxSampler

  ViewVC Help
Powered by ViewVC