Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Low-level Access and Replaying Input

Discussion in 'New Input System' started by john_funomena, May 9, 2019.

  1. john_funomena

    john_funomena

    Joined:
    Jun 23, 2014
    Posts:
    3
    First off, just wanted to say thanks for keeping the API flexible and easy to use for low-level input work! Being able to copy around buffers of raw input data and still interpret it through the control system is fantastic.

    I'm building a simple game around the concept of recording and playing player input. I have things working such that I record the full input buffer from the InputDevice every frame, and then sample from the saved buffers on later frames. This works (awesome!), but isn't all that ergonomic. I'd like to figure out how to create a fake InputControl/InputDevice that sources from my recorded buffers, rather than the current InputSystem state.

    Is there a recommended way to create this kind of a fake device? I didn't have a lot of luck creating an InputDevice because the currentStatePtr is inaccessible.

    One requirement of my game is that it ideally needs to support hundreds of recorded devices at once. Is the InputControl system performant enough for something like that?

    For reference, here's the code I've put together for a simple recorder:

    Code (CSharp):
    1. public static class ReinterpretCastExtensions {
    2.    public static unsafe bool AsBool( this float n ) => *(bool*)&n;
    3.    public static unsafe int AsInt( this float n ) => *(int*)&n;
    4. }
    5. public class ActionRecorder : MonoBehaviour
    6. {
    7.     public List<byte[]> SavedControlSamples = new List<byte[]>();
    8.     public InputDevice FakeDevice;
    9.  
    10.     public bool playback = false;
    11.     public int playbackFrame = 0;
    12.  
    13.     public unsafe void FixedUpdate()
    14.     {
    15.         if(!playback) {
    16.             var length = Keyboard.current.stateBlock.sizeInBits;
    17.             byte[] state = new byte[length/8];
    18.  
    19.             fixed(void* b = state) {
    20.                 Keyboard.current.ReadValueIntoBuffer(b, state.Length);
    21.             }
    22.  
    23.             SavedControlSamples.Add(state);
    24.         }
    25.  
    26.         if (Input.GetKey(KeyCode.LeftAlt))     {
    27.             playback = true;
    28.             playbackFrame = 0;
    29.         }
    30.         if(playback) {
    31.  
    32.             byte[] stateData = SavedControlSamples[playbackFrame];
    33.             fixed (void* statePtr = stateData) {
    34.                
    35.                 // TODO: We can poll recorded inputs this way, but I'd really like to create a "Fake" InputControl instead.
    36.                 // The goal would be to get other code to do `myFakeControl.Read()`
    37.  
    38.                 object leftData = ((float)Keyboard.current.leftArrowKey.ReadValueFromStateAsObject(statePtr)).AsInt();
    39.                 Debug.Log(leftData);
    40.                
    41.                 // Debug.Log($"Frame {playbackFrame}: {leftData} {BitConverter.ToString(BitConverter.GetBytes((float)leftData))}");
    42.                 // Debug.Log(BitConverter.ToString(stateData).Replace("-", ""));
    43.             }
    44.             playbackFrame = (playbackFrame + 1) % SavedControlSamples.Count;
    45.         }
    46.     }
    47.  
    48.     public void Start() {
    49.         InputSystem.settings.updateMode = InputSettings.UpdateMode.ProcessEventsInFixedUpdateOnly;
    50.     }
    51.  
    52. }
    53.  
     
  2. Elhimp

    Elhimp

    Joined:
    Jan 6, 2013
    Posts:
    20
    I can ask only, if using low-level more performant than having actions?

    Having single "hot" input stream out of low-level event...

    Code (CSharp):
    1. Observable.FromEvent<InputEventPtr>(
    2.     h => InputSystem.onEvent += h,
    3.     h => InputSystem.onEvent -= h
    4. )
    5. .Where(eventData =>
    6.     eventData.IsA<StateEvent>())
    7.     || eventData.IsA<DeltaStateEvent>())
    8. .GroupBy(eventData => eventData.deviceId)
    9. .Publish();
    ...with multiple readers connected to that stream...
    Code (CSharp):
    1. public static IObservable<T> EventsObservable<T>(
    2.     this InputControl<T> control,
    3.     IObservable<IGroupedObservable<int, InputEventPtr>> eventStream
    4. )
    5. where T : struct =>
    6.     eventStream.Where(stream => stream.Key == control.device.id)
    7.     .SelectMany(stream => stream)
    8.     .Select(eventData => {
    9.         T result;
    10.         if (!control.ReadValueFromEvent(eventData, out result))
    11.             throw new Exception("Can't read input event");
    12.         return result;
    13.     });
    Was much worse than reflexively mapping bunch of actions, and reading this events. I'm pretty sure it's me messed up, but I'm already invest a lot of time in something that subject of change, so I have to stop hating string-pile constructor and switch to other stuff.

    Anyway! UniRx is the answer for you, one of many, yet good one. You can easily buffer, delay, replay, sample, aggregate etc. your inputs with nice linq syntax.
     
  3. john_funomena

    john_funomena

    Joined:
    Jun 23, 2014
    Posts:
    3
    I'm looking to control the recording and playback at a much lower level than UniRx or Actions.

    UniRx isn't really optimized for performance. It comes from the ReactiveX paradigm which sort of assumes it is acting at a higher level. It generates a lot of garbage, and doesn't do anything about cache locality. I've used it a lot, but it doesn't really help what I'm attempting to do specifically. UniRx is event-driven, and I'm working at the frame-by-frame level.