Skip to content
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

Support for motion controllers like the DualShock 4 #8352

Open

Conversation

@rlnilsen
Copy link

commented Sep 7, 2019

The CemuHook controller input protocol

This was done by implementing the protocol for controller input used by CemuHook in a new controller interface class (CemuHookUDPServer). The protocol is used for transmitting controller input (including motion input) to Dolphin from specific server applications written to support specific controller types. For example, the Sony Dualshock 4 is supported through the DS4Windows application. This is the only configuration I have tested. See this page on the CemuHook site for more information and instructions on testing motion input before trying to use it with Dolphin.

Server configuration

First make sure you have tested your controller's motion input like in the link above. Then run and quit Dolphin once to create the settings file UDPServer.ini in the Dolphin config folder. In the settings file change Enabled = False to Enabled = True. Default values for the other settings (IPAddress and Port) may need changing depending on the server application used. With DS4Windows you just need to enable the UDP Server in DS4Windows' settings.

Emulated Wii Remote configuration

In the GUI for configuring an Emulated Wii Remote, a new tab called Motion Controls (Real) was added. The old Motion Controls tab was renamed to Motion Controls (Emulated) to differentiate them. In the Device dropdown list select a UDPServer device for motion input support. Point settings are described below. Accelerometer and Gyro settings should be left alone.

Pointer emulation

The Wiimote camera sensor (pointer) is emulated from motion input. There are however some inaccuracies in motion sensors making the pointer slowly drift horizontally. To help this, there are two options in the Point box on the Motion Controls (Real) tab. One is Recenter, which lets you recenter the pointer on the screen with a button press. The other is Total Yaw, which horizontally limits the pointer movement like when a computer mouse pointer hits the screen edge. Try out both options to see which works best in which games. I have playtested very little, but for Skyward Sword it seems best to disable Total Yaw by setting it to 360 and mapping Recenter to Pad S just like the ingame recenter function. Then, when you need to recenter, point the controller in the direction of the screen center and tap Pad S twice without moving the controller.

Funny story

Initially I had troubles getting any motion input to work. Among several things I tried Wii Sports Tennis to see if I could get any reaction from the motion input. I first tried a few forehands and backhads to get the guy to serve. Then I remembered how the pros do it and took a proper swing from behind my head. The brand new DS4 controller I used first hit a ceiling lamp, knocking the controller out of my hand. The controller then, to my horror, flew towards my four months old 1100$ TV, just missing to its left and also just above a computer monitor stored temporarily on the floor. The controller hit the wall behind the computer monitor with a loud crack, prompting people on the above floor come asking what the hell was going on. Incredibly, the ceiling lamp, both displays, the controller and the wall survived with just a few scratches.

Add support for motion controllers like the Sony DualShock 4. This is…
… done by 1) implementing the protocol for motion input used by CemuHook in a new controller interface (CemuHookUDPServer) and 2) adding functionality (in WiimoteEmu) for pushing that motion input to the emulated Wiimote and MotionPlus. The GUI has been modified to allow the user to select between the existing emulated motion input and this new functionality.
@JMC47

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

I've heard this suggested before, neat to see someone putting in the time to implement it!

I wonder if this can be hooked up to phone gyroscopes as well for Android. I do love swinging my phone around.

@delroth

This comment has been minimized.

Copy link
Member

commented Sep 7, 2019

Why does this need to rely on external software?

@Techjar

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

Because communicating with a DualShock 4 is quite involved, and most DS4 users are likely already using DS4Windows anyways, so it makes less work for us.

Additionally, if we made our own implementation DS4Windows users would have to stop that software before running Dolphin, which would be added inconvenience for little benefit.

@MayImilae

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

I'm pretty sure most DS4 users are using Steam these days, because it's really really good. And probably a fair number are using the SCPToolkit as well, as that is very popular for just being really easy to use.

@Techjar

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

Oh right, I forgot about Steam's thing. It doesn't support the gyro though does it? As for SCPToolkit, I couldn't care less, as it's essentially obsoleted by DS4Windows.

@MayImilae

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

That's not true at all? DS4Windows and SCPToolkit target different demographics. SCPToolkit is for those that just want a dead simple way to use their DS4 in PC games. It only emulates an Xbox 360 controller so it doesn't support the touchpad or gyro at all, but it is super simple to set up and use with the push of a button, even with bluetooth. DS4Windows is for those that want to use everything the DS4 has to offer, but requires tuning and adjustment on a per application basis to get the most out of it.

As for Steam, it supports the touchpad, gyros, and everything that the DS4 offers, all in a fantastic UI. So it kind of has the best of both worlds, being super easy to use and crazy powerful. The only issues is that it only works through steam.

@Techjar

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

DS4Windows allows those things, but you don't have to use them. It's quite simple to just hook up your DS4 and use it as an Xbox 360 controller, without any sort of "tuning and adjustment". As for Steam, I didn't know it actually supported all DS4 functionality. That's pretty cool. Doesn't really help us though...

@rlnilsen

This comment has been minimized.

Copy link
Author

commented Sep 7, 2019

I wonder if this can be hooked up to phone gyroscopes as well for Android. I do love swinging my phone around.

Yes, see "Android MotionSource server" on this page.

@Helios747

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

If there is no direct way to support gyros through an API, that ain't great.

This method requires external software we don't maintain, and thus can break at any time for any reason. If theres a way to support this without needing external software to act as a middleman for the motion sensor data, that would be ideal.

I don't actually think Windows only is a problem though. We have OS specific features.

@Techjar

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

The reference to CemuHook is a misnomer from the fact that it's the first implementation of the DS4Windows UDP protocol, and the fact that the protocol was designed by the developer of CemuHook (and later upstreamed). CemuHook itself is of absolutely no relevance whatsoever here.

The only required external software is DS4Windows.

@MayImilae

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

Personally, I think a far better solution for random motion controller support is to just do something we've discussed previously and just expose an option for raw axis assignment for the accelerometers and gyros. That way we wouldn't need to rely on any external software or libs, and DS4Windows, Steam, Android, or anything else could easily feed in pure axis data to Dolphin from their motion sensors. It would be completely OS agnostic and support anything that has motion sensors, like Switch or Wii U controllers.

@Techjar

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

Except there is no standard for "accelerometers and gyros" so how is one expected to bind those?

@Helios747

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

Is there any way at all to actually access those axis though with currently available and license compatible APIs?

Maybe OpenXR?

@rlnilsen

This comment has been minimized.

Copy link
Author

commented Sep 7, 2019

Why does this need to rely on external software?

As @Techjar said communicating with a DualShock 4 is quite involved. More importantly there is an existing eco system for motion input from various sources that can be taken advantage of.

@Techjar

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

OpenXR is the only standard I know of that has a concept of "motion controls", but perhaps it is a reasonable option if people start implementing device support for it beyond VR controllers.

@MayImilae

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

OpenXR is more or less what we've been waiting for. But technically, anything that can map the motion axis to multiple standard 2D axis would work with that immediately. DS4windows and Steam can do that easily.

@Techjar

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

Both DS4Windows and Steam require implementing their proprietary API though, which is what we're trying to avoid. Though I suppose Steam is more maintainable than DS4Windows, given it's not some random GitHub project.

@Helios747

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

The one thing I definitely like about this though is it sidesteps the problem of having to directly support specific controllers. A generic input API could also do that but our only option there is OpenXR, and I'm not actually sure if OpenXR supports anything other than VR controllers.

@MayImilae

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

The DS4Windows API is complete overkill for this task. All it needs to do is output the six axis of the motion sensors as six Dinput Axis. ...to be honest it's been a while since I've tried it but I'm pretty sure DS4Windows is capable of that.

Also the SteamInput API does not need to be implemented to use the Steam controller system. But dolphin does have to be "in" steam, but that's trivial to do (add non-steam game).

Regardless, OpenXR will make all of this much easier, hence why none of this was implemented previously.

@rlnilsen

This comment has been minimized.

Copy link
Author

commented Sep 7, 2019

This method

1. Requires external software we don't maintain, and thus can break at any time for any reason

Correct. But there are many implementations so there should be some inertia.

2. External software that... does, video decoding, input hooking and... game patching? In one binary? what?

This is wrong. I quote @Techjar:

The reference to CemuHook is a misnomer from the fact that it's the first implementation of the DS4Windows UDP protocol. CemuHook is of absolutely no relevance whatsoever here.

3. Requires the user to set up their device to send motion data in a specific way to the software. Looks very finicky.

Yes.

@mbc07

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

I guess there's some misunderstandings going on here. When first introduced, CemuHook actually implemented a "protocol" to receive raw controller data (not only motion data, but buttons, axes and -- on DualShock 4 case -- touch input as well) through UDP packets on the network, similar to the old UDPWiimote implementation we had on Dolphin in the past. The last time I checked, that UDP protocol gained some popularity as it's now used not only on Cemu but also on Yuzu and Citra emulators as well.

And then comes the 3rd party programs. CemuHook author submitted patches to both SCPToolkit and DS4Windows, to make them send raw controller data using that UDP Protocol, but several other apps from other authors and for different controllers/devices surfaced, implementing that same protocol. You can use any of them as "servers" to send controller data (including motion) to compatible emulators (Cemu, Yuzu and Citra AFAIK).

Also, Steam isn't really an option for this. Yes, they natively support DualShock 4 controllers, including their motion sensors and touch input, but you can only use that "extra" data to emulate key/button presses on the keyboard/mouse or on a virtual XInput controller. There's no way to directly access raw motion/touch data from a DualShock 4 nor expose them as separate axes through Steam.

With Steam out of the equation, the next "best" option is probably DS4Windows, but any other program implementing the protocol is suitable (except, perhaps, for SCP Toolkit, which is dead and abandoned and doesn't even work properly with the revised DualShock 4 model -- the one with the light bar visible through the touch pad). On DS4Windows you don't actually need to use any feature of that program other than the UDP Server option, and that feature is somewhat "portable", as you don't need to install anything else to use just the UDP Server. Unzip the program somewhere, run it, mark "UDP Server" option and plug/pair a DualShock 4 controller. That's all.

OpenXR also doesn't completely solve this. Yes, the API can expose motion data in a standardized way, but you would still need a middle-man to "translate" DualShock 3/4, Switch Pro Controller, <insert any other non-VR control with motion sensors here> into OpenXR API Calls before anything useful could be done.

TL;DR CemuHook UDP protocol might not be the ideal solution but I would argue is the best option currently available...

@Techjar

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

Ah, I wasn't aware other applications implemented the server side of the protocol. I'm still not okay with calling it CemuHook UDP protocol though, given the name is more associated with a plugin for a different emulator. It's also still completely unmaintainable, as it relies on some sort of external software which could simply stop working for any reason and potentially never be fixed, leaving this feature dead in the water. In fact that has kind of already happened with SCP Toolkit.

@mbc07

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2019

I'm still not okay with calling it CemuHook UDP protocol though, given the name is more associated with a plugin for a different emulator.

It's often called like that because AFAIK there isn't an "official" name and it first came to existence through CemuHook.

It's also still completely unmaintainable, as it relies on some sort of external software which could simply stop working for any reason and potentially never be fixed, leaving this feature dead in the water.

I think the client-side part of the protocol is already, um, "final", as it hasn't changed at all since its first appearance in 2017. Regarding the server-side part of the protocol, most programs implementing it are open source, so there's always a reference for new programs to build upon.

Like I said, even with a standardized API like OpenXR, a middle-man (DS4Windows, SCPToolkit, whatever) would always be needed for any non-VR controller, as I highly doubt Sony or Nintendo would ever bother releasing an OpenXR compatible driver for their controllers with motion sensors. Even the more supported ones (DualShock 4, Switch Pro Controller) only works on PC somewhat well due commitment of 3rd parties (e.g. Steam). If the client-side part of that protocol can be implemented in a self-contained way that doesn't increase Dolphin's maintenance burden I see no reason for not having it, especially considering it seems nothing better and readily available exists at the moment, but that's my two cents on this subject.

Also, there's more than one emulator currently using that protocol and more than just one software implementing the server-side part of the protocol, it's unlikely it would simply cease to exist from one day to another. If in the future this protocol becomes obsolete or our users run out of maintained (by maintained I mean regularly updated) softwares implementing the server-side part of the protocol we can simply drop it, just like we did with our own UDPWiimote protocol in the past...

@jordan-woyak

This comment has been minimized.

Copy link
Member

commented Sep 10, 2019

@dolphin-emu-bot rebuild.

@Miksel12

This comment has been minimized.

Copy link
Contributor

commented Sep 10, 2019

I did some testing on Wii Sports (Resorts) and Super Mario Galaxy 1/2 and it worked great. Pretty funny to tennis with a DS4. The only problem I noticed was not being able to move the cursor horizontally when the controller is held vertically (90 degrees roll to the left or right), the cursor also becomes very sensitive on the vertical axis when held this way. I encountered this in the Fluzzard levels in Super Mario Galaxy 2 which require some manoeuvres in which the controller has to be almost held vertically, making some turns quite difficult. Testing was done with a maximum yaw of 180.

@rlnilsen

This comment has been minimized.

Copy link
Author

commented Sep 11, 2019

@Miksel12

I did some testing on Wii Sports (Resorts) and Super Mario Galaxy 1/2 and it worked great. Pretty funny to tennis with a DS4. The only problem I noticed was not being able to move the cursor horizontally when the controller is held vertically (90 degrees roll to the left or right), the cursor also becomes very sensitive on the vertical axis when held this way.

This is a limitation in the accelerometer tilt angle calculations for emulating the pointer via the Wiimote IR camera. I am unfortunately not mathmatically inclined and unable to fix this. However, is this a real problem? Does any game require pointer control when the controller is at 90 degrees roll?

I encountered this in the Fluzzard levels in Super Mario Galaxy 2 which require some manoeuvres in which the controller has to be almost held vertically, making some turns quite difficult. Testing was done with a maximum yaw of 180.

I don't think this is the same issue, as i believe controlling the Fluzzard is not done with the pointer, but the accelerometer (correct me if I'm wrong). Accelerometer data is generally passed unmodified to the emulated Wii, so if there is a problem with the Fluzzard control it may be because of bad accelerometer data from the UDP Server you use.

(What UDP Server are you using? DS4Windows?)

Maybe the Fluzzard controls are supposed to be like this? Are you absolutely sure the controls were worse than if playing on either

  1. a real Wiimote on Dolphin?
  2. a real Wii?
@Miksel12

This comment has been minimized.

Copy link
Contributor

commented Sep 11, 2019

I don't think this is the same issue, as i believe controlling the Fluzzard is not done with the pointer, but the accelerometer (correct me if I'm wrong). Accelerometer data is generally passed unmodified to the emulated Wii, so if there is a problem with the Fluzzard control it may be because of bad accelerometer data from the UDP Server you use.

(What UDP Server are you using? DS4Windows?)

Maybe the Fluzzard controls are supposed to be like this? Are you absolutely sure the controls were worse than if playing on either

  1. a real Wiimote on Dolphin?
  2. a real Wii?

I'm using DS4Windows. I haven't palyed on my Wii for quite sometime as the video output is almost completely broken so maybe I just forgot how it controlled but I'm pretty sure that it controlled differently. I'll try using a real wiimote on my laptop and test it again.

@jordan-woyak

This comment has been minimized.

Copy link
Member

commented Sep 11, 2019

I've noticed the misbehaving cursor when tilted as well. But other than that it works surprisingly well. I'd say it's plenty good enough for now and someone can improve the math later if they feel like it.

@rlnilsen

This comment has been minimized.

Copy link
Author

commented Sep 11, 2019

@Miksel12 I just played through the two first Fluzzard levels with the DS4 to see how it was. Other than needing a few attempts at the second of the five gates in Fleet Glide Galaxy it didn't feel too difficult. I noticed that the Fluzzard was a bit slow to react, but that won't be because of bad accelerometer data. Unfortunately I don't have a Wiimote for comparison, so I can't judge how close the experience is to the real thing.

A few things may perhaps make the DS4 harder to use than how you remember it was with the Wiimote:

  1. The Wiimote and DS4 have very different shape and ergonomics. The DS4 has a wide two handed grip while the Wiimote is narrow with a single hand grip. This makes roll movements (like when controlling the Fluzzard) a bit harder to execute on the DS4.
  2. The differing internal location and tilt of the motion sensor chip in relation to the grip location(s) may affect the controls negatively.
  3. The games are of course specifically made for the Wiimote, not the DS4.
@JMC47

This comment has been minimized.

Copy link
Contributor

commented Sep 11, 2019

I'm guessing number 2 may be the big one on that.

@mbc07

This comment has been minimized.

Copy link
Contributor

commented Sep 12, 2019

So, although a little awkward to hold, I tested some Just Dance games and it seemed easier to get "Perfects" with the DualShock 4 when compared to a Wiimote. I guess it has something to do with more accurate motion sensors on DualShock 4 compared to the Wiimote, not exactly relevant but thought it might be useful to know...

@rlnilsen

This comment has been minimized.

Copy link
Author

commented Sep 12, 2019

The maximum value for the pointer emulation Total Yaw setting should of course be 360 degrees, not 180. Fixed in the code and the PR description.

rlnilsen added 3 commits Sep 15, 2019
Combine "emulated" and "real" motion sources.
Move "emulated" motion source mapping to separate tab.
Remove motion source setting.
Don't use the position of the "real" IMUCursor when combining motion sources as that would add to the "emulated" cursor position.
Rename config file CemuHookUDPServer.ini to UDPServer.ini to match th…
…e name in the input device selection dropdown.
@rlnilsen

This comment has been minimized.

Copy link
Author

commented Sep 15, 2019

I was able to combine the data from the "emulated" and "real" motion sources. You are no longer restricted to one or the other. For example, if you dislike shaking the controller to perform a move, you can now map this to a button and still use motion controls for other things.

The PR description is updated.

NB! The UDP Server configuration file has changed from CemuHookUDPServer.ini to UDPServer.ini. You'll need to reapply your settings.

rlnilsen added 2 commits Sep 15, 2019
@@ -348,7 +349,8 @@ void MappingWindow::SetMappingType(MappingWindow::Type type)
widget = new WiimoteEmuGeneral(this, extension);
setWindowTitle(tr("Wii Remote %1").arg(GetPort() + 1));
AddWidget(tr("General and Options"), widget);
AddWidget(tr("Motion Controls"), new WiimoteEmuMotionControl(this));
AddWidget(tr("Motion Controls (Emulated)"), new WiimoteEmuMotionControl(this));
AddWidget(tr("Motion Controls (Real)"), new WiimoteEmuMotionControlIMU(this));

This comment has been minimized.

Copy link
@iwubcode

iwubcode Sep 15, 2019

Contributor

Not sure how this shows up in the UI (haven't tested yet) but should it only be added if the controller actually supports that functionality? That would split our interface up but as it is, I think it will be confusing to many users who don't have those peripherals.

Maybe this could be moved out to a separate selection in the controller selection? So we have a drop down that currently has:

  • None
  • Emulated Wiimote
  • Real Wiimote

We could instead have:

  • None
  • Emulated Wiimote
  • Emulated Wiimote from Motion/Gyro Device
  • Real Wiimote

(can't think of a good wording)

That leads to my second comment...I can't think of a better name but "real" seems like it'd be confusing too. This functionality is still emulated just so happens to be a closer emulation because the emulated device supports actual motion.

This comment has been minimized.

Copy link
@jordan-woyak

jordan-woyak Sep 15, 2019

Member

I'm not sure I like the "Emulated" and "Real" either, but it wouldn't make sense to have the option in that drop down menu. And there is not really a reliable way to know if a controller provides raw accel/gyro data.

This comment has been minimized.

Copy link
@jordan-woyak

jordan-woyak Sep 15, 2019

Member

Perhaps leave the "Motion Controls" tab named as-is and place the new "raw" mappings under an "Advanced" tab.

This comment has been minimized.

Copy link
@iwubcode

iwubcode Sep 15, 2019

Contributor

I'm not sure I like the "Emulated" and "Real" either, but it wouldn't make sense to have the option in that drop down menu

Yeah, the UI is already pretty substantial, I just worry about having another piece that would confuse users but I don't deal with them on a daily basis like @JMC47 or others do, so maybe they'd be ok with it.

Putting in the drop down just seemed to make the distinction a little clearer but it's not ideal either.

And there is not really a reliable way to know if a controller provides raw accel/gyro data.

If they choose "Real" in this PR, how does Dolphin know it's receiving data? Could we use similar data to determine detection?

This comment has been minimized.

Copy link
@rlnilsen

rlnilsen Sep 15, 2019

Author

If we add a new item to the dropdown as you suggest, wouldn't that preclude the combination of "emulated" and "real" motion sources, as was just implemented?

The only other name I have been able to think of is Inertial Measurement Unit (IMU), which really is the correct name for this kind of motion source. But it seemed a bit heavy for the user interface, therefore "real". Some like to use "gyro", but that is really just one part of the IMU. But absolute correctness is perhaps not so important?

This comment has been minimized.

Copy link
@rlnilsen

rlnilsen Sep 15, 2019

Author

This is how it currently looks. The contents of the other tabs are unchanged.

2019-09-15 21_12_59-vs2017 (1) - Remote Viewer

This comment has been minimized.

Copy link
@jordan-woyak

jordan-woyak Sep 15, 2019

Member

IMO the current UI shown in this recent screenshot is not adequate to deter users from incorrectly trying to map traditional controller inputs to the "raw" mappings. Many users are "smart" enough to know what "Yaw Left" means but won't understand if they bind it to a button that they aren't gonna have a good time.

This comment has been minimized.

Copy link
@rlnilsen

rlnilsen Sep 15, 2019

Author

IMO the current UI shown in this recent screenshot is not adequate to deter users from incorrectly trying to map traditional controller inputs to the "raw" mappings.

I considered not actually showing these mappings, as they don't need modification with UDPServer input devices. I however left them in in the case there were other motion sources - and you showed that the case is so on Linux.

If hiding the mappings is not an option, perhaps a stern warning to the user that the boxes are not intended for regular button mapping is adequate?

This comment has been minimized.

Copy link
@rlnilsen

rlnilsen Sep 16, 2019

Author

Basically the same screenshot as earlier, just changed the titles of the motion tabs. The Motion Controls (Emulated) tab is reverted to Motion Controls. The Motion Controls (Real) tab is renamed to Motion Source. Does this name work?

One could dynamically enable/disable (gray out) the Motion Source tab depending on if an input device is bound to the accelerometer and gyro mappings. This would work with all UDPServer input devices, since the accelerometer and gyro inputs always have the same names. However, this would preclude mapping of accelerometer and gyro inputs from non-UDPServer sources, like @jordan-woyak has done on Linux.

(Removed a paragraph here that was wrong.)

2019-09-16 02_17_53-vs2017 (1) - Remote Viewer

This comment has been minimized.

Copy link
@rlnilsen

rlnilsen Sep 16, 2019

Author

Just a mockup of some warning text.

2019-09-16 02_17_53-vs2017 (1) - Remote Viewer

To determine if accelerometer and gyro data is available, check if th…
…e first mapping control is bound to an input.
@mbc07

This comment has been minimized.

Copy link
Contributor

commented Sep 16, 2019

So, I discovered this page. Might be useful as reference documentation of the UDP protocol...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.