winmm: Avoid allocations in timeSetEvent.
[wine.git] / dlls / winmm / time.c
blob2d5f3db3892ab14189c2907ef08ef2b56051ec41
1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
3 /*
4 * MMSYSTEM time functions
6 * Copyright 1993 Martin Ayotte
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 #include <stdarg.h>
24 #include <errno.h>
25 #include <time.h>
27 #include "windef.h"
28 #include "winbase.h"
29 #include "mmsystem.h"
31 #include "winemm.h"
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(mmtime);
37 typedef struct tagWINE_TIMERENTRY {
38 UINT wDelay;
39 UINT wResol;
40 LPTIMECALLBACK lpFunc; /* can be lots of things */
41 DWORD_PTR dwUser;
42 UINT16 wFlags;
43 UINT16 wTimerID;
44 DWORD dwTriggerTime;
45 } WINE_TIMERENTRY, *LPWINE_TIMERENTRY;
47 static WINE_TIMERENTRY timers[16];
48 static UINT timers_created;
50 static CRITICAL_SECTION TIME_cbcrst;
51 static CRITICAL_SECTION_DEBUG critsect_debug =
53 0, 0, &TIME_cbcrst,
54 { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
55 0, 0, { (DWORD_PTR)(__FILE__ ": TIME_cbcrst") }
57 static CRITICAL_SECTION TIME_cbcrst = { &critsect_debug, -1, 0, 0, 0, 0 };
59 static HANDLE TIME_hMMTimer;
60 static CONDITION_VARIABLE TIME_cv;
63 * Some observations on the behavior of winmm on Windows.
65 * First, the call to timeBeginPeriod(xx) can never be used to
66 * lower the timer resolution (i.e. increase the update
67 * interval), only to increase the timer resolution (i.e. lower
68 * the update interval).
70 * Second, a brief survey of a variety of Win 2k and Win X
71 * machines showed that a 'standard' (aka default) timer
72 * resolution was 1 ms (Win9x is documented as being 1). However, one
73 * machine had a standard timer resolution of 10 ms.
75 * Further, timeBeginPeriod(xx) also affects the resolution of
76 * wait calls such as NtDelayExecution() and
77 * NtWaitForMultipleObjects() which by default round up their
78 * timeout to the nearest multiple of 15.625ms across all Windows
79 * versions. In Wine all of those currently work with sub-1ms
80 * accuracy.
82 * Effective time resolution is a global value that is the max
83 * of the resolutions (i.e. min of update intervals) requested by
84 * all the processes. A lot of programs seem to do
85 * timeBeginPeriod(1) forcing it onto everyone else.
87 * Defaulting to 1ms accuracy in winmm should be safe.
89 * Additionally, a survey of Event behaviors shows that
90 * if we request a Periodic event every 50 ms, then Windows
91 * makes sure to trigger that event 20 times in the next
92 * second. If delays prevent that from happening on exact
93 * schedule, Windows will trigger the events as close
94 * to the original schedule as is possible, and will eventually
95 * bring the event triggers back onto a schedule that is
96 * consistent with what would have happened if there were
97 * no delays.
99 * Jeremy White, October 2004
100 * Arkadiusz Hiler, August 2020
102 #define MMSYSTIME_MININTERVAL (1)
103 #define MMSYSTIME_MAXINTERVAL (65535)
105 /**************************************************************************
106 * TIME_MMSysTimeCallback
108 static int TIME_MMSysTimeCallback(void)
110 WINE_TIMERENTRY *timer, copy;
111 int i, delta_time;
113 /* since timeSetEvent() and timeKillEvent() can be called
114 * from 16 bit code, there are cases where win16 lock is
115 * locked upon entering timeSetEvent(), and then the mm timer
116 * critical section is locked. This function cannot call the
117 * timer callback with the crit sect locked (because callback
118 * may need to acquire Win16 lock, thus providing a deadlock
119 * situation).
120 * To cope with that, we just copy the WINE_TIMERENTRY struct
121 * that need to trigger the callback, and call it without the
122 * mm timer crit sect locked.
125 for (;;)
127 for (i = 0; i < ARRAY_SIZE(timers); i++)
128 if (timers[i].wTimerID) break;
129 if (i == ARRAY_SIZE(timers)) return -1;
130 timer = timers + i;
131 for (i++; i < ARRAY_SIZE(timers); i++)
133 if (!timers[i].wTimerID) continue;
134 if (timers[i].dwTriggerTime < timer->dwTriggerTime)
135 timer = timers + i;
138 delta_time = timer->dwTriggerTime - timeGetTime();
139 if (delta_time > 0) break;
141 if (timer->wFlags & TIME_PERIODIC)
143 timer->dwTriggerTime += timer->wDelay;
145 else
147 copy = *timer;
148 timer->wTimerID = 0;
149 timer = &copy;
152 switch(timer->wFlags & (TIME_CALLBACK_EVENT_SET|TIME_CALLBACK_EVENT_PULSE))
154 case TIME_CALLBACK_EVENT_SET:
155 SetEvent(timer->lpFunc);
156 break;
157 case TIME_CALLBACK_EVENT_PULSE:
158 PulseEvent(timer->lpFunc);
159 break;
160 case TIME_CALLBACK_FUNCTION:
162 DWORD_PTR user = timer->dwUser;
163 UINT16 id = timer->wTimerID;
164 UINT16 flags = timer->wFlags;
165 LPTIMECALLBACK func = timer->lpFunc;
167 if (flags & TIME_KILL_SYNCHRONOUS) EnterCriticalSection(&TIME_cbcrst);
168 LeaveCriticalSection(&WINMM_cs);
170 func(id, 0, user, 0, 0);
172 EnterCriticalSection(&WINMM_cs);
173 if (flags & TIME_KILL_SYNCHRONOUS) LeaveCriticalSection(&TIME_cbcrst);
175 break;
178 return delta_time;
181 /**************************************************************************
182 * TIME_MMSysTimeThread
184 static DWORD CALLBACK TIME_MMSysTimeThread(LPVOID arg)
186 int sleep_time;
187 BOOL ret;
189 TRACE("Starting main winmm thread\n");
191 EnterCriticalSection(&WINMM_cs);
192 while (1)
194 sleep_time = TIME_MMSysTimeCallback();
196 if (sleep_time < 0)
197 break;
198 if (sleep_time == 0)
199 continue;
201 ret = SleepConditionVariableCS(&TIME_cv, &WINMM_cs, sleep_time);
202 if (!ret && GetLastError() != ERROR_TIMEOUT)
204 ERR("Unexpected error in poll: %s(%d)\n", strerror(errno), errno);
205 break;
208 CloseHandle(TIME_hMMTimer);
209 TIME_hMMTimer = NULL;
210 LeaveCriticalSection(&WINMM_cs);
211 TRACE("Exiting main winmm thread\n");
212 FreeLibraryAndExitThread(arg, 0);
213 return 0;
216 /**************************************************************************
217 * TIME_MMTimeStart
219 static void TIME_MMTimeStart(void)
221 HMODULE mod;
222 if (TIME_hMMTimer) return;
224 GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)TIME_MMSysTimeThread, &mod);
225 TIME_hMMTimer = CreateThread(NULL, 0, TIME_MMSysTimeThread, mod, 0, NULL);
226 SetThreadPriority(TIME_hMMTimer, THREAD_PRIORITY_TIME_CRITICAL);
229 /**************************************************************************
230 * TIME_MMTimeStop
232 void TIME_MMTimeStop(void)
234 if (TIME_hMMTimer) {
235 EnterCriticalSection(&WINMM_cs);
236 if (TIME_hMMTimer) {
237 ERR("Timer still active?!\n");
238 CloseHandle(TIME_hMMTimer);
240 DeleteCriticalSection(&TIME_cbcrst);
244 /**************************************************************************
245 * timeGetSystemTime [WINMM.@]
247 MMRESULT WINAPI timeGetSystemTime(LPMMTIME lpTime, UINT wSize)
249 if (wSize >= sizeof(*lpTime)) {
250 lpTime->wType = TIME_MS;
251 lpTime->u.ms = timeGetTime();
254 return 0;
257 /**************************************************************************
258 * timeGetTime [WINMM.@]
260 DWORD WINAPI timeGetTime(void)
262 LARGE_INTEGER now, freq;
264 QueryPerformanceCounter(&now);
265 QueryPerformanceFrequency(&freq);
267 return (now.QuadPart * 1000) / freq.QuadPart;
270 /**************************************************************************
271 * timeSetEvent [WINMM.@]
273 MMRESULT WINAPI timeSetEvent(UINT wDelay, UINT wResol, LPTIMECALLBACK lpFunc,
274 DWORD_PTR dwUser, UINT wFlags)
276 WORD new_id = 0;
277 int i;
279 TRACE("(%u, %u, %p, %08lX, %04X);\n", wDelay, wResol, lpFunc, dwUser, wFlags);
281 if (wDelay < MMSYSTIME_MININTERVAL || wDelay > MMSYSTIME_MAXINTERVAL)
282 return 0;
284 EnterCriticalSection(&WINMM_cs);
286 for (i = 0; i < ARRAY_SIZE(timers); i++)
287 if (!timers[i].wTimerID) break;
288 if (i == ARRAY_SIZE(timers))
290 LeaveCriticalSection(&WINMM_cs);
291 return 0;
294 new_id = ARRAY_SIZE(timers)*(++timers_created) + i;
295 if (!new_id) new_id = ARRAY_SIZE(timers)*(++timers_created) + i;
297 timers[i].wDelay = wDelay;
298 timers[i].dwTriggerTime = timeGetTime() + wDelay;
300 /* FIXME - wResol is not respected, although it is not clear
301 that we could change our precision meaningfully */
302 timers[i].wResol = wResol;
303 timers[i].lpFunc = lpFunc;
304 timers[i].dwUser = dwUser;
305 timers[i].wFlags = wFlags;
306 timers[i].wTimerID = new_id;
308 TIME_MMTimeStart();
310 LeaveCriticalSection(&WINMM_cs);
312 /* Wake the service thread in case there is work to be done */
313 WakeConditionVariable(&TIME_cv);
315 TRACE("=> %u\n", new_id);
317 return new_id;
320 /**************************************************************************
321 * timeKillEvent [WINMM.@]
323 MMRESULT WINAPI timeKillEvent(UINT wID)
325 WINE_TIMERENTRY *timer;
326 WORD flags;
328 TRACE("(%u)\n", wID);
329 EnterCriticalSection(&WINMM_cs);
331 timer = &timers[wID % ARRAY_SIZE(timers)];
332 if (timer->wTimerID != wID)
334 LeaveCriticalSection(&WINMM_cs);
335 WARN("wID=%u is not a valid timer ID\n", wID);
336 return TIMERR_NOCANDO;
339 timer->wTimerID = 0;
340 flags = timer->wFlags;
341 LeaveCriticalSection(&WINMM_cs);
343 if (flags & TIME_KILL_SYNCHRONOUS)
345 EnterCriticalSection(&TIME_cbcrst);
346 LeaveCriticalSection(&TIME_cbcrst);
348 WakeConditionVariable(&TIME_cv);
349 return TIMERR_NOERROR;
352 /**************************************************************************
353 * timeGetDevCaps [WINMM.@]
355 MMRESULT WINAPI timeGetDevCaps(LPTIMECAPS lpCaps, UINT wSize)
357 TRACE("(%p, %u)\n", lpCaps, wSize);
359 if (lpCaps == 0) {
360 WARN("invalid lpCaps\n");
361 return TIMERR_NOCANDO;
364 if (wSize < sizeof(TIMECAPS)) {
365 WARN("invalid wSize\n");
366 return TIMERR_NOCANDO;
369 lpCaps->wPeriodMin = MMSYSTIME_MININTERVAL;
370 lpCaps->wPeriodMax = MMSYSTIME_MAXINTERVAL;
371 return TIMERR_NOERROR;
374 /**************************************************************************
375 * timeBeginPeriod [WINMM.@]
377 MMRESULT WINAPI timeBeginPeriod(UINT wPeriod)
379 if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL)
380 return TIMERR_NOCANDO;
382 if (wPeriod > MMSYSTIME_MININTERVAL)
384 WARN("Stub; we set our timer resolution at minimum\n");
387 return 0;
390 /**************************************************************************
391 * timeEndPeriod [WINMM.@]
393 MMRESULT WINAPI timeEndPeriod(UINT wPeriod)
395 if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL)
396 return TIMERR_NOCANDO;
398 if (wPeriod > MMSYSTIME_MININTERVAL)
400 WARN("Stub; we set our timer resolution at minimum\n");
402 return 0;