-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wait_event: Sdl.wait_event implements power saving since SDL 2.0.22 #15
base: master
Are you sure you want to change the base?
Conversation
This could potentially be further optimized, so that we wait for the '1s' only once, then emit the mouse at rest event, and then don't wake up until we either have a scheduled timeout or we get a mouse moved event. |
I've implemented the optimization too, now 'boguex 44' sleeps properly, only waking up once after 1s to process the tooltip event and then sleeping indefinitely until next event. |
The opensuse CI failure is from |
thank you very much! I am currently quite busy and won't be able to review this probably before next week. |
Yeah we could fall back to polling on older SDL versions. |
I think Id' be happy to merge this PR but could you please remove the part concerning dpi scale? It would be clearer to keep both problems separated |
Thanks for the review, I removed the DPI change (sorry it leaked from the other branch) and added the SDL version check as discussed (looking closer the CPU friendly wait_event_timeout got introduced in SDL 2.0.16, so I used that as a minimum version to enable wait_event_timeout, otherwise fall back to polling and the old wait extra 0.01s between polls to be nicer to the CPU. But actually your Ubuntu 22.04 should have a new enough SDL then (2.0.20 > 2.0.16) for this to work, but the version check is useful nonetheless (I do the version check once on startup). |
I like the use of Timeout, but we have a little problem:
the idle column is great, but moving mouse becomes expensive. Maybe that's because Timeouts use Mutex, and we are constantly creating many of them. |
maybe we get too many mouse events, there are various discussions and improvements on the SDL bug tracker, especially if you have a mouse that sends events at 1000Hz: kometbomb/klystrack#299 |
'(lint-fmt)(failed: ocamlformat enabled but no .ocamlformat file!)' ocamlformat is only enabled because it is enabled by default by Dune. This project doesn't seem to use ocamlformat yet. Signed-off-by: Edwin Török <[email protected]>
tsdl.0.9.6 is last one that would work with it, but this package needs tsdl.0.9.7+. See also ocaml/opam-repository#23235 to adjust the availability condition of 'tsdl'. bogue.opam is autogenerated, so use bogue.opam.template to declare additional field ('dune-project' doesn't know about 'available'). Signed-off-by: Edwin Török <[email protected]>
See libsdl-org/SDL@0dd7024 The wiki has also been updated and doesn't mention the lack of power saving anymore. Use `Sdl.wait_event_timeout` instead of `Sdl.poll_event`, and compute the timeout based on the next `Timeout` action, clamped to the `[1, 1000] ms` interval so that mouse at rest events can still be processed (needed for tooltips). Always process `check_mouse_atrest` after waiting for an event, whether we received one or not: * mouse movements will generate mouse events and wake up `Sdl.wait_event_timeout`. * if we receive no events then eventually we'll generate the mouse_at_rest event. Could've used SDL timers, but they are not bound by Tsdl. Replace Thread.delay 0.01 with the Time.make_fps mechanism to protect against very short timeouts or a failing `Sdl.wait_event_timeout` (which internally falls back to polling if waiting is not supported by the platform). To rate limit events being produced too quickly (e.g. by a 1000Hz mouse), add the call to 'fps' before calling wait_even_timeout. As long as events are not produced too quickly (100Hz limit to match old sleep duration) there will be no additional delays for event processing: Bogue will wake up exactly when an event is received and will process it immediately, only inserting the sleep when it runs out of events. Confirmed via 'strace' that 'boguex 12' is idle now, waking up only once a second, and tooltips in 'boguex 44' still work. (unfortunately 'boguex 44' spawns a 2nd thread which enables a ~50ms timer in the OCaml runtime to switch between them) Signed-off-by: Edwin Török <[email protected]>
This avoids 'wait_event' having to wake up every second to check mouse position: * when the mouse moves it'll send and SDL event, and 'check_mouse_at_rest' will get called, if position is different the timeout is canceled and a new 1s idle timer is started * when the mouse is idle for 1 second then the timeout triggers, and pushes a mouse at rest event once. If no more events arrive the application will sleep until another event arrives, and won't wake up once a second anymore (there is no need: the mouse at rest event got delivered) Signed-off-by: Edwin Török <[email protected]>
Avoids spawning separate thread for mouse enter/leave, this allows 'boguex 44' to avoid waking up frequently due to the OCaml timer which would be active when >1 thread is active in the program. Signed-off-by: Edwin Török <[email protected]>
A timer action may add more timers, some of which may be in the past. Ensure we do not return negative values here. Signed-off-by: Edwin Török <[email protected]>
libsdl-org/SDL@0dd7024 is part of SDL 2.0.16. Older versions are not very nice to the CPU, so fall back polling and sleeping 0.01 inbetween. Signed-off-by: Edwin Török <[email protected]>
Sometimes SDL can receive a lot of events (e.g. 1000Hz mouse), and we want to limit the frame-rate when not animating. Previously the limit would've been ~10ms between Sdl.poll_event + 5ms from Thread.delay ~66 FPS. When the events have no effect on the display (nothing is rendered, and in fact Sdl.render_present will be skipped too) limit the processing rate to 60 Hz to match typical monitor refresh rate. When we do receive some useful events that change the display then use just the old 5ms Thread sleep as a throttling mechanism, but do it after we've rendered the frame to reduce input-to-display latency. Signed-off-by: Edwin Török <[email protected]>
Signed-off-by: Edwin Török <[email protected]>
Time.adaptive_fps also has optional vsync support, off by default for backwards compatibility. If we cannot keep up with requested FPS then try adaptive VSync if supported by the platform, otherwise turn vsync off. This will require a new version of Tsdl that releases the runtime lock [not yet released]. Signed-off-by: Edwin Török <[email protected]>
I've reworked the approach here, there were basically 2 things missing:
I'm hoping that these changes would eventually restore the original CPU usage on your platform, but let me do some further testing on my machines first (I've just pushed a commit so you can have a look meanwhile if you want). Thanks for bearing with me on this PR. |
@@ -31,7 +31,7 @@ depends: [ | |||
"tsdl-image" {>= "0.3.0"} | |||
"tsdl-ttf" {>= "0.3"} | |||
"ocaml" {>= "4.08.0"} | |||
"tsdl" {>= "0.9.7" & < "0.9.9"} | |||
"tsdl" {> "0.9.9"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose you mean >= 0.9.9
until now I have refrained from using a more recent version of tsdl because they implicitly require a recent version of SDL2. And last time I checked, this meant than many OS distributions were not able to build, because their default SDL was not that recent. |
I can understand the tension between wanting to support old systems and taking advantage of bugfixes/new features in later versions of SDL, both are desirable. Conditional compilation is usually problematic with editor support, I'll try to find something that fits within regular OCaml syntax ('ppx_optcomp' seems to use just regular OCaml extension points, so even an editor without special support for it would work and it requires both sides of the conditional to be otherwise valid OCaml, which is good. It only has builtin support for conditionals based on ocaml version, but custom variables can be defined in imported files, so it might be possible to do something here with dune). Or dune's 'select...from' might be an alternative, but that is for whole files. |
Here is a draft example of how conditional compilation could work (note that this compares versions as strings, which is not ideal, but with a few more dune rules it should be possible to split that into tsdl_major/minor/patch):
|
I merged your PR into the "power_saving" branch. It works quite nicely! However I did have an issue:
and here is a section that seems relevant:
|
@@ -878,10 +878,11 @@ let event_loop anim new_anim board = | |||
let e = !Trigger.my_event in | |||
continue e 0 | |||
|
|||
let nop_event_fps = Time.make_fps () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function should be renewed at each run
, otherwise the start
hidden variable becomes meaningless
|
||
let no_timeout () = -1 | ||
|
||
let poll_noevent_fps = B_time.make_fps () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function should be renewed at each run, otherwise the start
hidden variable becomes meaningless
@@ -1291,6 +1302,8 @@ let init ?window ?(name="BOGUE Window") ?fill ?x ?y ~w ~h () = | |||
end; | |||
|
|||
printd debug_graphics "Canvas created"; | |||
if not (Theme.get_bool "NO_VSYNC") then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one should add the default value of NO_VSYNC to Theme.default_vars
@@ -985,6 +991,17 @@ let create ?shortcuts ?(connections = []) ?on_user_event windows = | |||
mouse_alive = false; | |||
on_user_event } | |||
|
|||
let get_monitor_refresh_rate board = | |||
Option.bind Layout.(window_opt board.windows_house) @@ fun win -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
window_opt board.windows_house
will always return None
because it is not associated to any physical window.
The problem is that SDL (and bogue) allows to work with several windows. A workaround would be to select the first element of the board.windows
Yes, I can reproduce that large number (that looks like an overflow of a negative number), but only with those 4 tests in sequence, removing e.g. '36' doesn't reproduce it anymore, and neither does '36 37' on its own. A workaround is to guard against negative time, and this avoids the excessively large 'round_trip' and the unnecessarily large 'wait':
Although there is probably a more fundamental bug here (race condition if we use multiple threads, or somehow resetting SDL's internal clock?). Time should never go backwards (in the past I've seen this due to unsynchronized TSC, but that shouldn't happen with modern CPUs anymore, and if it was that then it wouldn't be this reliably reproducible in the exact same spot every time). Somehow resetting SDL's internal clock sounds more plausible.... |
Calling so many examples causes Sdl.quit and Sdl.init to be called multiple times, also '36' is different from the others in that it initialises just the audio subsys but then quits everything (instead of just audio). I pushed some commits to this branch which seems to fix the underlying problem too: 'start' needs to be reinitialized whenever SDL is reinitialized https://github.com/edwintorok/bogue/commits/fixwrap. I'm not entirely happy with relying on 'at_cleanup' to do this, it should instead be done in the (re)initialization code of bogue. I'll look into doing it more nicely later, meanwhile can you try whether the commits from my branch fix the delay on your system too? |
the culprit is probably simply |
see |
@@ -50,3 +50,4 @@ build: [ | |||
] | |||
] | |||
dev-repo: "git+https://github.com/sanette/bogue.git" | |||
available: [ (os-distribution != "opensuse-leap" | os-version >= 16) ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is good for avoiding CI errors, but what if an OpenSuse user decides to manually install a recent version of SDL2?
I moved this to the 'vsync' branch |
See libsdl-org/SDL@0dd7024
The wiki has also been updated and doesn't mention the lack of power saving anymore.
Use
Sdl.wait_event_timeout
instead ofSdl.poll_event
, and compute the timeout based on the nextTimeout
action, clamped to the[1, 1000] ms
interval so that mouse at rest events can still be processed (needed for tooltips).Always process
check_mouse_atrest
after waiting for an event, whether we received one or not:Sdl.wait_event_timeout
.Keep the
Thread.delay 0.01
on recursion to protect against very short timeouts or a failingSdl.wait_event_timeout
(which internally falls back to polling if waiting is not supported by the platform).
Confirmed via 'strace' that 'boguex 12' is idle now, waking up only once a second, and tooltips in 'boguex 44' still work.
(unfortunately 'boguex 44' spawns a 2nd thread which enables a ~50ms timer in the OCaml runtime to switch between them)