Class InteractionManager

java.lang.Object
com.hypixel.hytale.server.core.entity.InteractionManager
All Implemented Interfaces:
Component<EntityStore>, Cloneable

public class InteractionManager extends Object implements Component<EntityStore>
Handles running (and optionally synchronizing over the network) interactions for an entity.

Implementation

Interactions

All interaction chains start with a RootInteraction as the entry point for the chain. Before use a root interaction is flattened into a list of Operations removing the need to follow the tree-like structure of Interaction as written in assets.

See OperationsBuilder for more information on how interactions generally flatten themselves.

From this flattened structure we can now execute the Operations just like a CPU/VM by using a pointer into the list (Operation Counter InteractionChain.getOperationCounter()) and incrementing it after each completed operation or allowing operations to manually jump to a specific operation via jumps (InteractionContext.jump(Label). Using these base parts we can execute most of the interaction structure already, most of the time interactions just run linearly through the list and if a conditionals are needed then it can be implemented via jumps in/out of the required blocks.

The last case to handle is executing interactions that cannot be known at 'compile' time, e.g. interacting with a block via UseBlockInteraction. For these cases there is a system to allow entering a nested RootInteraction like a function call. When InteractionContext.execute(RootInteraction) is called it stores some state on a stack and then executes the operations of the passed RootInteraction. When that root interaction completes it pops the state from the stack automatically and resumes executing the previous root from the operation after the one that called execute.

Along with the operation counter there is also the operation index (InteractionChain.getOperationIndex()). This index only ever increases and does so linearly, unlike the counter that can jump multiple steps and even backwards. This index is useful for unique data per an operation call as well as being useful when syncing state over the network due to the fact they are predictable.

Forks

Forks are interaction chains that are triggered by other interactions via InteractionContext.fork(InteractionType, InteractionContext, RootInteraction, boolean) )} instead of by the client or server.

Two types of forks exist

  1. Server Forks

    These are forks that the server is in control of triggering. The client (if any) will be told by the server to trigger the interaction chain.

    This is useful for cases where only the server has the required information to know how many forks to trigger e.g. damaging entities. It does however introduce a delay based on the user's ping (for remote clients) which can interrupt the player's feedback if not used carefully.

    This delay can be avoided by allowing the client to predict the fork via the client method InteractionContext.ForkPredicted. Forks created by the client in this fashion will have the wrong fork id (instead using one picked by the client) which will prevent it from being handled correct. In order to handle these cases you must implement Interaction.mapForkChain(InteractionContext, InteractionChainData) on both the client and server for the given interaction. The method allows you to inspect the chain data and manually match it up to an existing fork, both the server/client will then link the two ids together allowing both of them to reference the same chain.

    If the client mis-predicts the server will cancel the forks it created. For the cases where it fails to predict the normal rules for server forks still apply.

  2. Predicted Forks

    These are forks that both the server and client are required to trigger at the same time and in the same order (within a single interaction). This type of fork is the best case and should be used when possible.

    Unlike Server Forks these do not suffer from network latency issues and do not have to wait for the server to confirm anything before running. Care should be taken when implementing interactions that use predicted forks to make sure the client always generates the same forks as the server.

Identifying

Forks extend the chain id used by normal chains with a pair of ints that combine to make the fork id. A fork id can also contain another fork id to allow a fork to fork. The two parts of a fork id are the entry index and an increasing counter per an interaction, the chain id is the same as the chain that created the fork.

By generating ids like this they stay predictable, we can use this to allow both the client to create the Predicted Forks without having to agree on a new chain id to use.

The downside of this is that the deeper the fork the bigger the overhead for referencing it over the network.

Client

If there is a client attached (in the case of Players) then it is required to execute the same interactions as the server and stay in sync (excluding server triggered interactions that don't require a client). The interactions on the client don't have to implement everything the server does (and in many cases it is impossible to do so) but should execute for the same amount of time and must follow the same interaction path. For interactions that don't require a client part using SimpleInteraction is a good option as its client side provides a basic implementation to work with as well.

When a desync occurs there's very little the server needs to do. By inspecting the InteractionSyncData sent by the client we can compare the InteractionSyncData.operationCounter and InteractionSyncData.rootInteraction with our own we can spot the client desyncing (note: this doesn't validate the that the interactions themselves did the same thing, only that the whole chain is executing the same path). The server will react to this by ignoring any incorrect sync data and marking the chain as desynced, this causes the interaction to send its sync data again and relaxes some restrictions on packet ordering (to allow it to sent corrections after it may have attempted to start new chains). The client itself will detect the desync so there is nothing else for the server to do apart from waiting for the correct state to arrive.

When the client detects a desync by reading the server's InteractionSyncData it will revert all interactions after the interaction before the one that reported the desync. It does this because the desync would only be detected when the next interaction goes down the wrong path and not when the interaction itself mis-predicts. After reverting it then begins ticking all interactions in an accelerated state to attempt to catch up with the server. To do this it uses the InteractionSyncData from the server to run instead of its own data. The client will also cancel any predicted forks for these interactions.

  • Field Details

    • MAX_REACH_DISTANCE

      public static final double MAX_REACH_DISTANCE
      The default max reach distance, in blocks, for an interaction
      See Also:
    • DEFAULT_CHARGE_TIMES

      public static final float[] DEFAULT_CHARGE_TIMES
      A default array of charge times for interactions that don't use charging.
  • Constructor Details

    • InteractionManager

      public InteractionManager(@Nullable PlayerRef playerRef, @Nonnull IInteractionSimulationHandler simulationHandler)
      Default Constructor for a InteractionManager instance
      Parameters:
      playerRef - The player ref associated with the entity, if any
      simulationHandler - The associated simulation handler
  • Method Details

    • getChains

      @Nonnull public it.unimi.dsi.fastutil.ints.Int2ObjectMap<InteractionChain> getChains()
      Returns:
      an unmodifiable map of active chains
    • getInteractionSimulationHandler

      @Nonnull public IInteractionSimulationHandler getInteractionSimulationHandler()
      Returns:
      the interaction simulation handler for this interaction manager
    • setHasRemoteClient

      @Deprecated(forRemoval=true) public void setHasRemoteClient(boolean hasRemoteClient)
      Deprecated, for removal: This API element is subject to removal in a future version.
      Temp method used to force special bot users to not need to simulate interactions.
      Called to set whether this interaction manager has a remote client.
      Parameters:
      hasRemoteClient - Whether this interaction manager has a remote client.
    • copyFrom

      @Deprecated public void copyFrom(@Nonnull InteractionManager interactionManager)
      Deprecated.
      Not implemented correctly. Should instead cancel running interactions and start with a fresh interaction manager.
      Called to copy the state from another interaction manager.
      Parameters:
      interactionManager - The interaction manager to copy from.
    • tick

      public void tick(@Nonnull Ref<EntityStore> ref, @Nonnull CommandBuffer<EntityStore> commandBuffer, float dt)
      Runs all active interaction chains for a single tick using the given time step dt.
      Parameters:
      ref - The entity reference of the associated entity.
      commandBuffer - The CommandBuffer<EntityStore> for component access.
      dt - The amount of time that has passed since the last tick (Delta time)
    • sync

      public void sync(@Nonnull Ref<EntityStore> ref, @Nonnull ChainSyncStorage chainSyncStorage, @Nonnull SyncInteractionChain packet)
      Updates the passed chain with information from the client provided in packet.
      Parameters:
      ref - The entity reference of the associated entity.
      chainSyncStorage - The chain to update.
      packet - The packet containing the state information from client.
    • canRun

      public boolean canRun(@Nonnull InteractionType type, @Nonnull RootInteraction rootInteraction)
      Determines whether the given root interaction can run for the given interaction type
      Parameters:
      type - The interaction type
      rootInteraction - The root interaction that we are checking
      Returns:
      true if the interaction can run, false otherwise
    • canRun

      public boolean canRun(@Nonnull InteractionType type, short equipSlot, @Nonnull RootInteraction rootInteraction)
      Determines whether the given interaction can run for the given interaction type
      Parameters:
      type - The interaction type
      equipSlot - The index of the equip slot, if it's an equipped interaction type
      rootInteraction - The root interaction that we are checking
      Returns:
      true if the interaction can run, false otherwise
    • applyRules

      public boolean applyRules(@Nonnull InteractionContext context, @Nonnull InteractionChainData data, @Nonnull InteractionType type, @Nonnull RootInteraction rootInteraction)
      Checks all running chains to see if any of them conflict with the given type or rootInteraction.

      This uses the InteractionRules defined on the passed RootInteraction, any running RootInteraction and any running Interaction

      Parameters:
      context - The interaction context for the interaction that wants to be run.
      data - The data of the interaction chain that wants to be run.
      type - The type interaction that wants to be run.
      rootInteraction - The root interaction that wants to be run.
      Returns:
      Whether the interaction rules are satisfied (or there are no running chains or rules to check).
    • cancelChains

      public void cancelChains(@Nonnull InteractionChain chain)
      Called to cancel the given interaction chain
      Parameters:
      chain - The interaction chain to be cancelled
    • tryStartChain

      public boolean tryStartChain(@Nonnull Ref<EntityStore> ref, @Nonnull CommandBuffer<EntityStore> commandBuffer, @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction)
      Parameters:
      ref - The entity reference of the associated entity.
      commandBuffer - The CommandBuffer<EntityStore> for component access.
      type - The type of interaction to run.
      context - The context to run the interaction chain with.
      rootInteraction - The root interaction to run.
      Returns:
      Whether the chain successfully ran or not.
    • startChain

      public void startChain(@Nonnull Ref<EntityStore> ref, @Nonnull CommandBuffer<EntityStore> commandBuffer, @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction)
      Parameters:
      ref - The entity reference of the associated entity.
      commandBuffer - The command buffer for component access
      type - The type of interaction to run
      context - The context to run the interaction chain with
      rootInteraction - The root interaction to run
    • initChain

      @Nonnull public InteractionChain initChain(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction, boolean forceRemoteSync)
      Creates an interaction chain without starting it with the chain data set to empty values.
      Parameters:
      type - The type of interaction to run.
      context - The context to run the interaction with.
      rootInteraction - The root interaction to run.
      forceRemoteSync - Whether to force this chain to run on the client as well (if any).
      Returns:
      The newly created interaction chain.
      See Also:
    • initChain

      @Nonnull public InteractionChain initChain(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction, int entityId, @Nullable BlockPosition blockPosition, boolean forceRemoteSync)
      Creates an interaction chain without starting it with the chain data using the provided entityId and blockPosition.
      Parameters:
      type - The type of interaction to run.
      context - The context to run the interaction with.
      rootInteraction - The root interaction to run.
      entityId - The network id of the entity this interaction targets.
      blockPosition - The position of the block being targeted.
      forceRemoteSync - Whether to force this chain to run on the client as well (if any).
      Returns:
      The newly created interaction chain.
      See Also:
    • initChain

      @Nonnull public InteractionChain initChain(@Nonnull InteractionChainData data, @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction, @Nullable Runnable onCompletion, boolean forceRemoteSync)
      Creates a new interaction chain without running it.

      The forceRemoteSync flag controls whether this chain will always run on the client or not. Depending on the interactions used in the RootInteraction a client may be required, in that case it will override this flag.

      The onCompletion callback will be called when the chain finishes on both the client and server. It does not wait for any forked chains to complete before being called.

      Parameters:
      data - The chain data for this chain. Will be synced to the client if there is one.
      type - The type of interaction to run.
      context - The context to run the interaction with.
      rootInteraction - The root interaction to run.
      onCompletion - The callback to call when the chain completes, if any.
      forceRemoteSync - Whether to force this chain to run on the client as well (if any).
      Returns:
      The newly created interaction chain.
    • queueExecuteChain

      public void queueExecuteChain(@Nonnull InteractionChain chain)
      Called to queue the given interaction chain to be executed
      Parameters:
      chain - The interaction chain that we wish to execute
    • executeChain

      public void executeChain(@Nonnull Ref<EntityStore> ref, @Nonnull CommandBuffer<EntityStore> commandBuffer, @Nonnull InteractionChain chain)
      Executes the passed chain.

      This instantly runs the chain for a single tick and if it requires ticking afterward/waiting for the client it'll be stored and ticked by future tick(Ref, CommandBuffer, float) )} calls.

      Parameters:
      ref - The entity reference of the associated entity.
      commandBuffer - The CommandBuffer<EntityStore> for component access.
      chain - The chain to execute.
    • tryRunHeldInteraction

      public void tryRunHeldInteraction(@Nonnull Ref<EntityStore> ref, @Nonnull CommandBuffer<EntityStore> commandBuffer, @Nonnull InteractionType type)
      Called to try and run a held item interaction
      Parameters:
      ref - The entity reference of the associated entity.
      commandBuffer - The command buffer for component access
      type - The type of interaction
    • tryRunHeldInteraction

      public void tryRunHeldInteraction(@Nonnull Ref<EntityStore> ref, @Nonnull CommandBuffer<EntityStore> commandBuffer, @Nonnull InteractionType type, short equipSlot)
      Called to try and run a held item interaction
      Parameters:
      ref - The entity reference of the associated entity.
      commandBuffer - The command buffer for component access
      type - The type of interaction
      equipSlot - The index of the equip slot to check, if it's an equipped interaction type
    • sendSyncPacket

      public void sendSyncPacket(@Nonnull InteractionChain chain, int operationBaseIndex, @Nullable List<InteractionSyncData> interactionData)
      Called to send a sync packet to the client associated with this interaction manager
      Parameters:
      chain - The interaction chain that we are syncing to the client
      operationBaseIndex - The operation base index of the interaction
      interactionData - The collection of interaction sync data to be sent
    • sendCancelPacket

      public void sendCancelPacket(int chainId, ForkedChainId forkedChainId)
      Called to send a cancel packet to the client associated with this interaction manager
      Parameters:
      chainId - The unique identifier of the chain to cancel
      forkedChainId - The unique identifier of the forked interaction chain
    • clear

      public void clear()
      Removes all interaction chains and cancels all running interactions
    • clearAllGlobalTimeShift

      public void clearAllGlobalTimeShift(float dt)
      Called to clear all global time shifts
      Parameters:
      dt - The delta time of the current tick
    • setGlobalTimeShift

      public void setGlobalTimeShift(@Nonnull InteractionType type, float shift)
      Changes the amount of time shifting for the given interaction type for this tick.

      This is generally called automatically by Interaction whilst ticking an interaction with a run time or blocking on an animation.

      This is used to help with the fact that the client and server both tick at different rates. Whilst time measurements are based on real time they are only checked once per a tick (server)/per a frame (client). This means by the time we check if the time has passed chances are we have already passed it. The amount of time we will overshoot by (the error) is dependent on the tick/frame rate involved, for example:

      runTime = 0.111
      tickTime = 1 / 30 // Server tick rate
      
      runTime-ceil(runTime/tickTime)*tickTime
      = ~-0.02233
      
      tickTime = 1 / 120 // Example client frame rate
      
      runTime-ceil(runTime/tickTime)*tickTime
      = ~-0.00567
      
      Approx time difference: 0.016667s
      

      Whilst the time difference for a single interaction may be low if error can build up for every interaction in a chain. And due to interaction queueing interactions on the server (due to being slower generally) this error will continue with them as well. In the case of interactions that run continuously whilst an input is held this error can build up quickly over time causing large delays between the client executing an interaction and the server.

      To handle this any error from the previous interaction is carried across to the next interaction/chain via time shifting which is applied when the interaction sets up its timestamp, causing the interaction to start with its time already running slightly ahead. Any time shifting is cleared when the next tick occurs.

      Parameters:
      type - The interaction type that is currently being run
      shift - The new time shift.
    • getGlobalTimeShift

      public float getGlobalTimeShift(@Nonnull InteractionType type)
      Returns the amount of time shifting (in seconds) for the given interaction type.
      Parameters:
      type - The interaction type to look up the time shift for.
      Returns:
      The amount of time shift for the given interaction type. Returns 0 if none is set.
      See Also:
    • forEachInteraction

      public <T> T forEachInteraction(@Nonnull TriFunction<InteractionChain, Interaction, T, T> func, T val)
      Calls func for every active interaction running in this manager.

      The parameter val will be passed into func and the value returned from func will become the new val. Once all interactions have been iterated over the last value of val will be returned from this method.

      Type Parameters:
      T - The type of the helper value that will be passed around.
      Parameters:
      func - The function to be called for every active interaction.
      val - The initial value to be passed into func and transformed.
      Returns:
      The value as transformed by all func calls made during iteration or val if there isn't any active interactions.
    • walkChain

      public void walkChain(@Nonnull Ref<EntityStore> ref, @Nonnull Collector collector, @Nonnull InteractionType type, @Nonnull ComponentAccessor<EntityStore> componentAccessor)
      Walks over all interactions in the default root interaction that would be used for the given interaction type type.
      Parameters:
      ref - The entity reference of the associated entity.
      collector - The collector that will be called whilst walking the chain.
      type - The interaction type of the chain.
      componentAccessor - The component accessor for accessing components
    • walkChain

      public void walkChain(@Nonnull Ref<EntityStore> ref, @Nonnull Collector collector, @Nonnull InteractionType type, @Nullable RootInteraction rootInteraction, @Nonnull ComponentAccessor<EntityStore> componentAccessor)
      Walks over all interactions in the root interaction provided.
      Parameters:
      ref - The entity reference of the associated entity.
      collector - The collector that will be called whilst walking the chain.
      type - The interaction type of the chain.
      rootInteraction - The root interaction to walk over or null.
      componentAccessor - The component accessor for accessing components
    • walkChain

      public static void walkChain(@Nonnull Collector collector, @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable RootInteraction rootInteraction)
      Walks over all interaction in the root interaction provided using the given context.
      Parameters:
      collector - The collector that will be called whilst walking the chain.
      type - The interaction type of the chain.
      context - The context to be used when walking over interactions.
      rootInteraction - The root interaction to walk over or null.
    • walkInteractions

      public static boolean walkInteractions(@Nonnull Collector collector, @Nonnull InteractionContext context, @Nonnull CollectorTag tag, @Nonnull String[] interactionIds)
      Helper method for implementations of Interaction.walk(Collector, InteractionContext).
      Parameters:
      collector - The collector to use whilst walking interactions.
      context - The interaction context.
      tag - The collector tag to use.
      interactionIds - The interaction IDs to walk.
      Returns:
      true if walking should stop, false to continue.
    • walkInteraction

      public static boolean walkInteraction(@Nonnull Collector collector, @Nonnull InteractionContext context, @Nonnull CollectorTag tag, @Nullable String id)
      Helper method for implementations of Interaction.walk(Collector, InteractionContext)
      Parameters:
      collector - The collector to use whilst walking interactions.
      context - The interaction context.
      tag - The collector tag to use.
      id - The interaction ID to walk.
      Returns:
      true if walking should stop, false to continue.
    • getSyncPackets

      public it.unimi.dsi.fastutil.objects.ObjectList<SyncInteractionChain> getSyncPackets()
    • clone

      @Nonnull public Component<EntityStore> clone()
      Specified by:
      clone in interface Component<EntityStore>
      Returns:
      a clone instance of the original component instance.