1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005 |
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Linq;
- using Unity.Collections;
- using UnityEngine.InputSystem.Utilities;
-
- ////REVIEW: given we have the global ActionPerformed callback, do we really need the per-map callback?
-
- ////TODO: remove constraint of not being able to modify bindings while enabled from both actions and maps
- //// (because of the sharing of state between multiple maps in an asset, we'd have to extend that constraint
- //// to all maps in an asset in order to uphold it properly)
-
- namespace UnityEngine.InputSystem
- {
- /// <summary>
- /// A mechanism for collecting a series of input actions (see <see cref="InputAction"/>)
- /// and treating them as a group.
- /// </summary>
- /// <remarks>
- /// Each action map is a named collection of bindings and actions. Both are stored
- /// as a flat list. The bindings are available through the <see cref="bindings"/>
- /// property and the actions are available through the <see cref="actions"/> property.
- ///
- /// The actions in a map are owned by the map. No action can appear in two maps
- /// at the same time. To find the action map an action belongs to, use the
- /// <see cref="InputAction.actionMap"/> property. Note that actions can also stand
- /// on their own and thus do not necessarily need to belong to a map (in which case
- /// the <see cref="InputAction.actionMap"/> property is <c>null</c>).
- ///
- /// Within a map, all actions have to have names and each action name must
- /// be unique. The <see cref="InputBinding.action"/> property of bindings in a map
- /// are resolved within the <see cref="actions"/> in the map. Looking up actions
- /// by name can be done through <see cref="FindAction(string,bool)"/>.
- ///
- /// The <see cref="name"/> of the map itself can be empty, except if the map is part of
- /// an <see cref="InputActionAsset"/> in which case it is required to have a name
- /// which also must be unique within the asset.
- ///
- /// Action maps are most useful for grouping actions that contextually
- /// belong together. For example, one common usage is to separate the actions
- /// that can be performed in the UI or in the main menu from those that can
- /// be performed during gameplay. However, even within gameplay, multiple action
- /// maps can be employed. For example, one could have different action maps for
- /// driving and for walking plus one more map for the actions shared between
- /// the two modes.
- ///
- /// Action maps are usually created in the <a href="../manual/ActionAssets.html">action
- /// editor</a> as part of <see cref="InputActionAsset"/>s. However, they can also be
- /// created standing on their own directly in code or from JSON (see <see cref="FromJson"/>).
- ///
- /// <example>
- /// <code>
- /// // Create a free-standing action map.
- /// var map = new InputActionMap();
- ///
- /// // Add some actions and bindings to it.
- /// map.AddAction("action1", binding: "<Keyboard>/space");
- /// map.AddAction("action2", binding: "<Gamepad>/buttonSouth");
- /// </code>
- /// </example>
- ///
- /// Actions in action maps, like actions existing by themselves outside of action
- /// maps, do not actively process input except if enabled. Actions can either
- /// be enabled individually (see <see cref="InputAction.Enable"/> and <see
- /// cref="InputAction.Disable"/>) or in bulk by enabling and disabling the
- /// entire map (see <see cref="Enable"/> and <see cref="Disable"/>).
- /// </remarks>
- /// <seealso cref="InputActionAsset"/>
- /// <seealso cref="InputAction"/>
- [Serializable]
- public sealed class InputActionMap : ICloneable, ISerializationCallbackReceiver, IInputActionCollection2, IDisposable
- {
- /// <summary>
- /// Name of the action map.
- /// </summary>
- /// <value>Name of the action map.</value>
- /// <remarks>
- /// For action maps that are part of <see cref="InputActionAsset"/>s, this will always be
- /// a non-null, non-empty string that is unique within the maps in the asset. For action maps
- /// that are standing on their own, this can be null or empty.
- /// </remarks>
- public string name => m_Name;
-
- /// <summary>
- /// If the action map is part of an asset, this refers to the asset. Otherwise it is <c>null</c>.
- /// </summary>
- /// <value>Asset to which the action map belongs.</value>
- public InputActionAsset asset => m_Asset;
-
- /// <summary>
- /// A stable, unique identifier for the map.
- /// </summary>
- /// <value>Unique ID for the action map.</value>
- /// <remarks>
- /// This can be used instead of the name to refer to the action map. Doing so allows referring to the
- /// map such that renaming it does not break references.
- /// </remarks>
- /// <seealso cref="InputAction.id"/>
- public Guid id
- {
- get
- {
- if (string.IsNullOrEmpty(m_Id))
- GenerateId();
- return new Guid(m_Id);
- }
- }
-
- internal Guid idDontGenerate
- {
- get
- {
- if (string.IsNullOrEmpty(m_Id))
- return default;
- return new Guid(m_Id);
- }
- }
-
- /// <summary>
- /// Whether any action in the map is currently enabled.
- /// </summary>
- /// <value>True if any action in <see cref="actions"/> is currently enabled.</value>
- /// <seealso cref="InputAction.enabled"/>
- /// <seealso cref="Enable"/>
- /// <seealso cref="InputAction.Enable"/>
- public bool enabled => m_EnabledActionsCount > 0;
-
- /// <summary>
- /// List of actions contained in the map.
- /// </summary>
- /// <value>Collection of actions belonging to the map.</value>
- /// <remarks>
- /// Actions are owned by their map. The same action cannot appear in multiple maps.
- ///
- /// Accessing this property. Note that values returned by the property become invalid if
- /// the setup of actions in a map is changed.
- /// </remarks>
- /// <seealso cref="InputAction.actionMap"/>
- public ReadOnlyArray<InputAction> actions => new ReadOnlyArray<InputAction>(m_Actions);
-
- /// <summary>
- /// List of bindings contained in the map.
- /// </summary>
- /// <value>Collection of bindings in the map.</value>
- /// <remarks>
- /// <see cref="InputBinding"/>s are owned by action maps and not by individual actions.
- ///
- /// Bindings that trigger actions refer to the action by <see cref="InputAction.name"/>
- /// or <see cref="InputAction.id"/>.
- ///
- /// Accessing this property does not allocate. Note that values returned by the property
- /// become invalid if the setup of bindings in a map is changed.
- /// </remarks>
- /// <seealso cref="InputAction.bindings"/>
- public ReadOnlyArray<InputBinding> bindings => new ReadOnlyArray<InputBinding>(m_Bindings);
-
- IEnumerable<InputBinding> IInputActionCollection2.bindings => bindings;
-
- /// <summary>
- /// Control schemes defined for the action map.
- /// </summary>
- /// <value>List of available control schemes.</value>
- /// <remarks>
- /// Control schemes can only be defined at the level of <see cref="InputActionAsset"/>s.
- /// For action maps that are part of assets, this property will return the control schemes
- /// from the asset. For free-standing action maps, this will return an empty list.
- /// </remarks>
- /// <seealso cref="InputActionAsset.controlSchemes"/>
- public ReadOnlyArray<InputControlScheme> controlSchemes
- {
- get
- {
- if (m_Asset == null)
- return new ReadOnlyArray<InputControlScheme>();
- return m_Asset.controlSchemes;
- }
- }
-
- /// <summary>
- /// Binding mask to apply to all actions in the asset.
- /// </summary>
- /// <value>Optional mask that determines which bindings in the action map to enable.</value>
- /// <remarks>
- /// Binding masks can be applied at three different levels: for an entire asset through
- /// <see cref="InputActionAsset.bindingMask"/>, for a specific map through this property,
- /// and for single actions through <see cref="InputAction.bindingMask"/>. By default,
- /// none of the masks will be set (that is, they will be <c>null</c>).
- ///
- /// When an action is enabled, all the binding masks that apply to it are taken into
- /// account. Specifically, this means that any given binding on the action will be
- /// enabled only if it matches the mask applied to the asset, the mask applied
- /// to the map that contains the action, and the mask applied to the action itself.
- /// All the masks are individually optional.
- ///
- /// Masks are matched against bindings using <see cref="InputBinding.Matches"/>.
- ///
- /// Note that if you modify the masks applicable to an action while it is
- /// enabled, the action's <see cref="InputAction.controls"/> will get updated immediately to
- /// respect the mask. To avoid repeated binding resolution, it is most efficient
- /// to apply binding masks before enabling actions.
- ///
- /// Binding masks are non-destructive. All the bindings on the action are left
- /// in place. Setting a mask will not affect the value of the <see cref="InputAction.bindings"/>
- /// and <see cref="bindings"/> properties.
- /// </remarks>
- /// <seealso cref="InputBinding.MaskByGroup"/>
- /// <seealso cref="InputAction.bindingMask"/>
- /// <seealso cref="InputActionAsset.bindingMask"/>
- public InputBinding? bindingMask
- {
- get => m_BindingMask;
- set
- {
- if (m_BindingMask == value)
- return;
-
- m_BindingMask = value;
- LazyResolveBindings(fullResolve: true);
- }
- }
-
- /// <summary>
- /// Set of devices that bindings in the action map can bind to.
- /// </summary>
- /// <value>Optional set of devices to use by bindings in the map.</value>
- /// <remarks>
- /// By default (with this property being <c>null</c>), bindings will bind to any of the
- /// controls available through <see cref="InputSystem.devices"/>, that is, controls from all
- /// devices in the system will be used.
- ///
- /// By setting this property, binding resolution can instead be restricted to just specific
- /// devices. This restriction can either be applied to an entire asset using <see
- /// cref="InputActionMap.devices"/> or to specific action maps by using this property. Note that
- /// if both this property and <see cref="InputActionAsset.devices"/> is set for a specific action
- /// map, the list of devices on the action map will take precedence and the list on the
- /// asset will be ignored for bindings in that action map.
- ///
- /// <example>
- /// <code>
- /// // Create an action map containing a single action with a gamepad binding.
- /// var actionMap = new InputActionMap();
- /// var fireAction = actionMap.AddAction("Fire", binding: "<Gamepad>/buttonSouth");
- /// asset.AddActionMap(actionMap);
- ///
- /// // Let's assume we have two gamepads connected. If we enable the
- /// // action map now, the 'Fire' action will bind to both.
- /// actionMap.Enable();
- ///
- /// // This will print two controls.
- /// Debug.Log(string.Join("\n", fireAction.controls));
- ///
- /// // To restrict the setup to just the first gamepad, we can assign
- /// // to the 'devices' property.
- /// actionMap.devices = new InputDevice[] { Gamepad.all[0] };
- ///
- /// // Now this will print only one control.
- /// Debug.Log(string.Join("\n", fireAction.controls));
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="InputActionAsset.devices"/>
- public ReadOnlyArray<InputDevice>? devices
- {
- // Return asset's device list if we have none (only if we're part of an asset).
- get => m_Devices.Get() ?? m_Asset?.devices;
- set
- {
- if (m_Devices.Set(value))
- LazyResolveBindings(fullResolve: false);
- }
- }
-
- /// <summary>
- /// Look up an action by name or ID.
- /// </summary>
- /// <param name="actionNameOrId">Name (as in <see cref="InputAction.name"/>) or ID (as in <see cref="InputAction.id"/>)
- /// of the action. Note that matching of names is case-insensitive.</param>
- /// <exception cref="ArgumentNullException"><paramref name="actionNameOrId"/> is <c>null</c>.</exception>
- /// <exception cref="KeyNotFoundException">No action with the name or ID of <paramref name="actionNameOrId"/>
- /// was found in the action map.</exception>
- /// <remarks>
- /// This method is equivalent to <see cref="FindAction(string,bool)"/> except it throws <c>KeyNotFoundException</c>
- /// if no action with the given name or ID can be found.
- /// </remarks>
- /// <seealso cref="FindAction(string,bool)"/>
- /// <seealso cref="FindAction(Guid)"/>
- /// <see cref="actions"/>
- public InputAction this[string actionNameOrId]
- {
- get
- {
- if (actionNameOrId == null)
- throw new ArgumentNullException(nameof(actionNameOrId));
- var action = FindAction(actionNameOrId);
- if (action == null)
- throw new KeyNotFoundException($"Cannot find action '{actionNameOrId}'");
- return action;
- }
- }
-
- ////REVIEW: inconsistent naming; elsewhere we use "onActionTriggered" (which in turn is inconsistent with InputAction.started etc)
- /// <summary>
- /// Add or remove a callback that is triggered when an action in the map changes its <see cref="InputActionPhase">
- /// phase</see>.
- /// </summary>
- /// <seealso cref="InputAction.started"/>
- /// <seealso cref="InputAction.performed"/>
- /// <seealso cref="InputAction.canceled"/>
- public event Action<InputAction.CallbackContext> actionTriggered
- {
- add => m_ActionCallbacks.AddCallback(value);
- remove => m_ActionCallbacks.RemoveCallback(value);
- }
-
- /// <summary>
- /// Construct an action map with default values.
- /// </summary>
- public InputActionMap()
- {
- }
-
- /// <summary>
- /// Construct an action map with the given name.
- /// </summary>
- /// <param name="name">Name to give to the action map. By default <c>null</c>, i.e. does
- /// not assign a name to the map.</param>
- public InputActionMap(string name)
- : this()
- {
- m_Name = name;
- }
-
- /// <summary>
- /// Release internal state held on to by the action map.
- /// </summary>
- /// <remarks>
- /// Once actions in a map are enabled, the map will allocate a block of state internally that
- /// it will hold on to until disposed of. All actions in the map will share the same internal
- /// state. Also, if the map is part of an <see cref="InputActionAsset"/> all maps and actions
- /// in the same asset will share the same internal state.
- ///
- /// Note that the internal state holds on to GC heap memory as well as memory from the
- /// unmanaged, C++ heap.
- /// </remarks>
- public void Dispose()
- {
- m_State?.Dispose();
- }
-
- internal int FindActionIndex(string nameOrId)
- {
- ////REVIEW: have transient lookup table? worth optimizing this?
- //// Ideally, this should at least be an InternedString comparison but due to serialization,
- //// that's quite tricky.
-
- if (string.IsNullOrEmpty(nameOrId))
- return -1;
- if (m_Actions == null)
- return -1;
-
- // First time we hit this method, we populate the lookup table.
- SetUpActionLookupTable();
-
- var actionCount = m_Actions.Length;
-
- var isOldBracedFormat = nameOrId.StartsWith("{") && nameOrId.EndsWith("}");
- if (isOldBracedFormat)
- {
- var length = nameOrId.Length - 2;
- for (var i = 0; i < actionCount; ++i)
- {
- if (string.Compare(m_Actions[i].m_Id, 0, nameOrId, 1, length) == 0)
- return i;
- }
- }
-
- if (m_ActionIndexByNameOrId.TryGetValue(nameOrId, out var actionIndex))
- return actionIndex;
-
- for (var i = 0; i < actionCount; ++i)
- {
- var action = m_Actions[i];
- if (action.m_Id == nameOrId || string.Compare(m_Actions[i].m_Name, nameOrId, StringComparison.InvariantCultureIgnoreCase) == 0)
- return i;
- }
-
- return InputActionState.kInvalidIndex;
- }
-
- private void SetUpActionLookupTable()
- {
- if (m_ActionIndexByNameOrId != null || m_Actions == null)
- return;
-
- m_ActionIndexByNameOrId = new Dictionary<string, int>();
-
- var actionCount = m_Actions.Length;
- for (var i = 0; i < actionCount; ++i)
- {
- var action = m_Actions[i];
-
- // We want to make sure an action ID cannot change *after* we have created the table.
- // NOTE: The *name* of an action, however, *may* change.
- action.MakeSureIdIsInPlace();
-
- // We create two lookup paths for each action:
- // (1) By case-sensitive name.
- // (2) By GUID string.
- m_ActionIndexByNameOrId[action.name] = i;
- m_ActionIndexByNameOrId[action.m_Id] = i;
- }
- }
-
- internal void ClearActionLookupTable()
- {
- m_ActionIndexByNameOrId?.Clear();
- }
-
- private int FindActionIndex(Guid id)
- {
- if (m_Actions == null)
- return InputActionState.kInvalidIndex;
- var actionCount = m_Actions.Length;
- for (var i = 0; i < actionCount; ++i)
- if (m_Actions[i].idDontGenerate == id)
- return i;
-
- return InputActionState.kInvalidIndex;
- }
-
- /// <summary>
- /// Find an action in the map by name or ID.
- /// </summary>
- /// <param name="actionNameOrId">Name (as in <see cref="InputAction.name"/>) or ID (as in <see cref="InputAction.id"/>)
- /// of the action. Note that matching of names is case-insensitive.</param>
- /// <param name="throwIfNotFound">If set to <see langword="true"/> will cause an exception to be thrown when the action was not found.</param>
- /// <returns>The action with the given name or ID or <c>null</c> if no matching action
- /// was found.</returns>
- /// <exception cref="ArgumentNullException"><paramref name="actionNameOrId"/> is <c>null</c>.</exception>
- /// <seealso cref="FindAction(Guid)"/>
- public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false)
- {
- if (actionNameOrId == null)
- throw new ArgumentNullException(nameof(actionNameOrId));
- var index = FindActionIndex(actionNameOrId);
- if (index == -1)
- {
- if (throwIfNotFound)
- throw new ArgumentException($"No action '{actionNameOrId}' in '{this}'", nameof(actionNameOrId));
- return null;
- }
- return m_Actions[index];
- }
-
- /// <summary>
- /// Find an action by ID.
- /// </summary>
- /// <param name="id">ID (as in <see cref="InputAction.id"/>) of the action.</param>
- /// <returns>The action with the given ID or null if no action in the map has
- /// the given ID.</returns>
- /// <seealso cref="FindAction(string,bool)"/>
- public InputAction FindAction(Guid id)
- {
- var index = FindActionIndex(id);
- if (index == -1)
- return null;
- return m_Actions[index];
- }
-
- /// <summary>
- /// Check whether there are any bindings in the action map that can bind to
- /// controls on the given device.
- /// </summary>
- /// <param name="device">An input device.</param>
- /// <returns>True if any of the bindings in the map can resolve to controls on the device, false otherwise.</returns>
- /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception>
- /// <remarks>
- /// The logic is entirely based on the contents of <see cref="bindings"/> and, more specifically,
- /// <see cref="InputBinding.effectivePath"/> of each binding. Each path is checked using <see
- /// cref="InputControlPath.Matches"/>. If any path matches, the method returns <c>true</c>.
- ///
- /// Properties such as <see cref="devices"/> and <see cref="bindingMask"/> are ignored.
- ///
- /// <example>
- /// <code>
- /// // Create action map with two actions and bindings.
- /// var actionMap = new InputActionMap();
- /// actionMap.AddAction("action1", binding: "<Gamepad>/buttonSouth");
- /// actionMap.AddAction("action2", binding: "<XRController{LeftHand}>/{PrimaryAction}");
- ///
- /// //
- /// var gamepad = InputSystem.AddDevice<Gamepad>();
- /// var xrController = InputSystem.AddDevice<XRController>();
- ///
- /// // Returns true:
- /// actionMap.IsUsableWith(gamepad);
- ///
- /// // Returns false: (the XRController does not have the LeftHand usage assigned to it)
- /// actionMap.IsUsableWith(xrController);
- /// </code>
- /// </example>
- /// </remarks>
- public bool IsUsableWithDevice(InputDevice device)
- {
- if (device == null)
- throw new ArgumentNullException(nameof(device));
-
- if (m_Bindings == null)
- return false;
-
- foreach (var binding in m_Bindings)
- {
- var path = binding.effectivePath;
- if (string.IsNullOrEmpty(path))
- continue;
-
- if (InputControlPath.Matches(path, device))
- return true;
- }
-
- return false;
- }
-
- /// <summary>
- /// Enable all the actions in the map.
- /// </summary>
- /// <remarks>
- /// This is equivalent to calling <see cref="InputAction.Enable"/> on each
- /// action in <see cref="actions"/>, but is more efficient as the actions
- /// will get enabled in bulk.
- /// </remarks>
- /// <seealso cref="Disable"/>
- /// <seealso cref="enabled"/>
- public void Enable()
- {
- if (m_Actions == null || m_EnabledActionsCount == m_Actions.Length)
- return;
-
- ResolveBindingsIfNecessary();
- m_State.EnableAllActions(this);
- }
-
- /// <summary>
- /// Disable all the actions in the map.
- /// </summary>
- /// <remarks>
- /// This is equivalent to calling <see cref="InputAction.Disable"/> on each
- /// action in <see cref="actions"/>, but is more efficient as the actions
- /// will get disabled in bulk.
- /// </remarks>
- /// <seealso cref="Enable"/>
- /// <seealso cref="enabled"/>
- public void Disable()
- {
- if (!enabled)
- return;
-
- m_State.DisableAllActions(this);
- }
-
- /// <summary>
- /// Produce an identical copy of the action map with its actions and bindings.
- /// </summary>
- /// <returns>A copy of the action map.</returns>
- /// <remarks>
- /// If the action map is part of an <see cref="InputActionAsset"/>, the clone will <em>not</em>
- /// be. It will be a free-standing action map and <see cref="asset"/> will be <c>null</c>.
- ///
- /// Note that the IDs for the map itself as well as for its <see cref="actions"/> and
- /// <see cref="bindings"/> are not copied. Instead, new IDs will be assigned. Also, callbacks
- /// installed on actions or on the map itself will not be copied over.
- /// </remarks>
- public InputActionMap Clone()
- {
- Debug.Assert(m_SingletonAction == null, "Internal (hidden) action maps of singleton actions should not be cloned");
-
- var clone = new InputActionMap
- {
- m_Name = m_Name
- };
-
- // Clone actions.
- if (m_Actions != null)
- {
- var actionCount = m_Actions.Length;
- var actions = new InputAction[actionCount];
- for (var i = 0; i < actionCount; ++i)
- {
- var original = m_Actions[i];
- actions[i] = new InputAction
- {
- m_Name = original.m_Name,
- m_ActionMap = clone,
- m_Type = original.m_Type,
- m_Interactions = original.m_Interactions,
- m_Processors = original.m_Processors,
- m_ExpectedControlType = original.m_ExpectedControlType,
- m_Flags = original.m_Flags,
- };
- }
- clone.m_Actions = actions;
- }
-
- // Clone bindings.
- if (m_Bindings != null)
- {
- var bindingCount = m_Bindings.Length;
- var bindings = new InputBinding[bindingCount];
- Array.Copy(m_Bindings, 0, bindings, 0, bindingCount);
- for (var i = 0; i < bindingCount; ++i)
- bindings[i].m_Id = default;
- clone.m_Bindings = bindings;
- }
-
- return clone;
- }
-
- /// <summary>
- /// Return an boxed instance of the action map.
- /// </summary>
- /// <returns>An boxed clone of the action map</returns>
- /// <seealso cref="Clone"/>
- object ICloneable.Clone()
- {
- return Clone();
- }
-
- /// <summary>
- /// Return <c>true</c> if the action map contains the given action.
- /// </summary>
- /// <param name="action">An input action. Can be <c>null</c>.</param>
- /// <returns>True if the action map contains <paramref name="action"/>, false otherwise.</returns>
- public bool Contains(InputAction action)
- {
- if (action == null)
- return false;
-
- return action.actionMap == this;
- }
-
- /// <summary>
- /// Return a string representation of the action map useful for debugging.
- /// </summary>
- /// <returns>A string representation of the action map.</returns>
- /// <remarks>
- /// For unnamed action maps, this will always be <c>"<Unnamed Action Map>"</c>.
- /// </remarks>
- public override string ToString()
- {
- if (m_Asset != null)
- return $"{m_Asset}:{m_Name}";
- if (!string.IsNullOrEmpty(m_Name))
- return m_Name;
- return "<Unnamed Action Map>";
- }
-
- /// <summary>
- /// Enumerate the actions in the map.
- /// </summary>
- /// <returns>An enumerator going over the actions in the map.</returns>
- /// <remarks>
- /// This method supports to generically iterate over the actions in a map. However, it will usually
- /// lead to GC allocation. Iterating directly over <see cref="actions"/> avoids allocating GC memory.
- /// </remarks>
- public IEnumerator<InputAction> GetEnumerator()
- {
- return actions.GetEnumerator();
- }
-
- /// <summary>
- /// Enumerate the actions in the map.
- /// </summary>
- /// <returns>An enumerator going over the actions in the map.</returns>
- /// <seealso cref="GetEnumerator"/>
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- // The state we persist is pretty much just a name, a flat list of actions, and a flat
- // list of bindings. The rest is state we keep at runtime when a map is in use.
-
- [SerializeField] internal string m_Name;
- [SerializeField] internal string m_Id; // Can't serialize System.Guid and Unity's GUID is editor only.
- [SerializeField] internal InputActionAsset m_Asset;
-
- /// <summary>
- /// List of actions in this map.
- /// </summary>
- [SerializeField] internal InputAction[] m_Actions;
-
- /// <summary>
- /// List of bindings in this map.
- /// </summary>
- /// <remarks>
- /// For singleton actions, we ensure this is always the same as <see cref="InputAction.m_SingletonActionBindings"/>.
- /// </remarks>
- [SerializeField] internal InputBinding[] m_Bindings;
-
- // These fields are caches. If m_Bindings is modified, these are thrown away
- // and re-computed only if needed.
- // NOTE: Because InputBindings are structs, m_BindingsForEachAction actually duplicates each binding
- // (only in the case where m_Bindings has scattered references to actions).
- ////REVIEW: this will lead to problems when overrides are thrown into the mix
-
- /// <summary>
- /// For each entry in <see cref="m_Actions"/>, a slice of this array corresponds to the
- /// action's bindings.
- /// </summary>
- /// <remarks>
- /// Ideally, this array is the same as <see cref="m_Bindings"/> (the same as in literally reusing the
- /// same array). However, we have no guarantee that <see cref="m_Bindings"/> is sorted by actions. In case it
- /// isn't, we create a separate array with the bindings sorted by action and have each action reference
- /// a slice through <see cref="InputAction.m_BindingsStartIndex"/> and <see cref="InputAction.m_BindingsCount"/>.
- /// </remarks>
- /// <seealso cref="SetUpPerActionControlAndBindingArrays"/>
- [NonSerialized] private InputBinding[] m_BindingsForEachAction;
-
- [NonSerialized] private InputControl[] m_ControlsForEachAction;
-
- /// <summary>
- /// Number of actions currently enabled in the map.
- /// </summary>
- /// <remarks>
- /// This should only be written to by <see cref="InputActionState"/>.
- /// </remarks>
- [NonSerialized] internal int m_EnabledActionsCount;
-
- // Action maps that are created internally by singleton actions to hold their data
- // are never exposed and never serialized so there is no point allocating an m_Actions
- // array.
- [NonSerialized] internal InputAction m_SingletonAction;
-
- [NonSerialized] internal int m_MapIndexInState = InputActionState.kInvalidIndex;
-
- /// <summary>
- /// Current execution state.
- /// </summary>
- /// <remarks>
- /// Initialized when map (or any action in it) is first enabled.
- /// </remarks>
- [NonSerialized] internal InputActionState m_State;
- [NonSerialized] internal InputBinding? m_BindingMask;
- [NonSerialized] private Flags m_Flags;
- [NonSerialized] internal int m_ParameterOverridesCount;
- [NonSerialized] internal InputActionRebindingExtensions.ParameterOverride[] m_ParameterOverrides;
-
- [NonSerialized] internal DeviceArray m_Devices;
-
- [NonSerialized] internal CallbackArray<Action<InputAction.CallbackContext>> m_ActionCallbacks;
-
- [NonSerialized] internal Dictionary<string, int> m_ActionIndexByNameOrId;
-
- private bool needToResolveBindings
- {
- get => (m_Flags & Flags.NeedToResolveBindings) != 0;
- set
- {
- if (value)
- m_Flags |= Flags.NeedToResolveBindings;
- else
- m_Flags &= ~Flags.NeedToResolveBindings;
- }
- }
-
- private bool bindingResolutionNeedsFullReResolve
- {
- get => (m_Flags & Flags.BindingResolutionNeedsFullReResolve) != 0;
- set
- {
- if (value)
- m_Flags |= Flags.BindingResolutionNeedsFullReResolve;
- else
- m_Flags &= ~Flags.BindingResolutionNeedsFullReResolve;
- }
- }
-
- private bool controlsForEachActionInitialized
- {
- get => (m_Flags & Flags.ControlsForEachActionInitialized) != 0;
- set
- {
- if (value)
- m_Flags |= Flags.ControlsForEachActionInitialized;
- else
- m_Flags &= ~Flags.ControlsForEachActionInitialized;
- }
- }
-
- private bool bindingsForEachActionInitialized
- {
- get => (m_Flags & Flags.BindingsForEachActionInitialized) != 0;
- set
- {
- if (value)
- m_Flags |= Flags.BindingsForEachActionInitialized;
- else
- m_Flags &= ~Flags.BindingsForEachActionInitialized;
- }
- }
-
- [Flags]
- private enum Flags
- {
- NeedToResolveBindings = 1 << 0,
- BindingResolutionNeedsFullReResolve = 1 << 1,
- ControlsForEachActionInitialized = 1 << 2,
- BindingsForEachActionInitialized = 1 << 3,
- }
-
- internal static int s_DeferBindingResolution;
-
- internal struct DeviceArray
- {
- private bool m_HaveValue;
- private int m_DeviceCount;
- private InputDevice[] m_DeviceArray; // May have extra capacity; we won't let go once allocated.
-
- public int IndexOf(InputDevice device)
- {
- return m_DeviceArray.IndexOfReference(device, m_DeviceCount);
- }
-
- public bool Remove(InputDevice device)
- {
- var index = IndexOf(device);
- if (index < 0)
- return false;
- m_DeviceArray.EraseAtWithCapacity(ref m_DeviceCount, index);
- return true;
- }
-
- public ReadOnlyArray<InputDevice>? Get()
- {
- if (!m_HaveValue)
- return null;
- return new ReadOnlyArray<InputDevice>(m_DeviceArray, 0, m_DeviceCount);
- }
-
- public bool Set(ReadOnlyArray<InputDevice>? devices)
- {
- if (!devices.HasValue)
- {
- if (!m_HaveValue)
- return false; // No change.
- if (m_DeviceCount > 0)
- Array.Clear(m_DeviceArray, 0, m_DeviceCount);
- m_DeviceCount = 0;
- m_HaveValue = false;
- }
- else
- {
- // See if the array actually changes content. Avoids re-resolving when there
- // is no need to.
- var array = devices.Value;
- if (m_HaveValue && array.Count == m_DeviceCount && array.HaveEqualReferences(m_DeviceArray, m_DeviceCount))
- return false;
-
- if (m_DeviceCount > 0)
- m_DeviceArray.Clear(ref m_DeviceCount);
- m_HaveValue = true;
- m_DeviceCount = 0;
- ArrayHelpers.AppendListWithCapacity(ref m_DeviceArray, ref m_DeviceCount, array);
- }
-
- return true;
- }
- }
-
- /// <summary>
- /// Return the list of bindings for just the given actions.
- /// </summary>
- /// <param name="action"></param>
- /// <returns></returns>
- /// <remarks>
- /// The bindings for a single action may be contiguous in <see cref="m_Bindings"/> or may be scattered
- /// around. We don't keep persistent storage for these and instead set up a transient
- /// array if and when bindings are queried directly from an action. In the simple case,
- /// we don't even need a separate array but rather just need to find out which slice in the
- /// bindings array corresponds to which action.
- ///
- /// NOTE: Bindings for individual actions aren't queried by the system itself during normal
- /// runtime operation so we only do this for cases where the user asks for the
- /// information. If the user never asks for bindings or controls on a per-action basis,
- /// none of this data gets initialized.
- /// </remarks>
- internal ReadOnlyArray<InputBinding> GetBindingsForSingleAction(InputAction action)
- {
- Debug.Assert(action != null, "Action cannot be null");
- Debug.Assert(action.m_ActionMap == this, "Action must be in action map");
- Debug.Assert(!action.isSingletonAction || m_SingletonAction == action, "Action is not a singleton action");
-
- // See if we need to refresh.
- if (!bindingsForEachActionInitialized)
- SetUpPerActionControlAndBindingArrays();
-
- return new ReadOnlyArray<InputBinding>(m_BindingsForEachAction, action.m_BindingsStartIndex,
- action.m_BindingsCount);
- }
-
- internal ReadOnlyArray<InputControl> GetControlsForSingleAction(InputAction action)
- {
- Debug.Assert(m_State != null);
- Debug.Assert(m_MapIndexInState != InputActionState.kInvalidIndex);
- Debug.Assert(m_Actions != null);
- Debug.Assert(action != null);
- Debug.Assert(action.m_ActionMap == this);
- Debug.Assert(!action.isSingletonAction || m_SingletonAction == action);
-
- if (!controlsForEachActionInitialized)
- SetUpPerActionControlAndBindingArrays();
-
- return new ReadOnlyArray<InputControl>(m_ControlsForEachAction, action.m_ControlStartIndex,
- action.m_ControlCount);
- }
-
- /// <summary>
- /// Collect data from <see cref="m_Bindings"/> and <see cref="m_Actions"/> such that we can
- /// we can cleanly expose it from <see cref="InputAction.bindings"/> and <see cref="InputAction.controls"/>.
- /// </summary>
- /// <remarks>
- /// We set up per-action caches the first time their information is requested. Internally, we do not
- /// use those arrays and thus they will not get set up by default.
- ///
- /// Note that it is important to allow to call this method at a point where we have not resolved
- /// controls yet (i.e. <see cref="m_State"/> is <c>null</c>). Otherwise, using <see cref="InputAction.bindings"/>
- /// may trigger a control resolution which would be surprising.
- /// </remarks>
- private unsafe void SetUpPerActionControlAndBindingArrays()
- {
- // Handle case where we don't have any bindings.
- if (m_Bindings == null)
- {
- m_ControlsForEachAction = null;
- m_BindingsForEachAction = null;
- controlsForEachActionInitialized = true;
- bindingsForEachActionInitialized = true;
- return;
- }
-
- if (m_SingletonAction != null)
- {
- // Dead simple case: map is internally owned by action. The entire
- // list of bindings is specific to the action.
-
- Debug.Assert(m_Bindings == m_SingletonAction.m_SingletonActionBindings,
- "For singleton action, bindings array must match that of the action");
-
- m_BindingsForEachAction = m_Bindings;
- m_ControlsForEachAction = m_State?.controls;
-
- m_SingletonAction.m_BindingsStartIndex = 0;
- m_SingletonAction.m_BindingsCount = m_Bindings.Length;
- m_SingletonAction.m_ControlStartIndex = 0;
- m_SingletonAction.m_ControlCount = m_State?.totalControlCount ?? 0;
-
- // Only complication, InputActionState allows a control to appear multiple times
- // on the same action and InputAction.controls[] doesn't.
- if (m_ControlsForEachAction.HaveDuplicateReferences(0, m_SingletonAction.m_ControlCount))
- {
- var numControls = 0;
- var controls = new InputControl[m_SingletonAction.m_ControlCount];
- for (var i = 0; i < m_SingletonAction.m_ControlCount; ++i)
- {
- if (!controls.ContainsReference(m_ControlsForEachAction[i]))
- {
- controls[numControls] = m_ControlsForEachAction[i];
- ++numControls;
- }
- }
-
- m_ControlsForEachAction = controls;
- m_SingletonAction.m_ControlCount = numControls;
- }
- }
- else
- {
- ////REVIEW: now that we have per-action binding information in UnmanagedMemory, this here can likely be done more easily
-
- // Go through all bindings and slice them out to individual actions.
-
- Debug.Assert(m_Actions != null, "Action map is associated with action but action map has no array of actions"); // Action isn't a singleton so this has to be true.
- var mapIndices = m_State?.FetchMapIndices(this) ?? new InputActionState.ActionMapIndices();
-
- // Reset state on each action. Important if we have actions that are no longer
- // referred to by bindings.
- for (var i = 0; i < m_Actions.Length; ++i)
- {
- var action = m_Actions[i];
- action.m_BindingsCount = 0;
- action.m_BindingsStartIndex = -1;
- action.m_ControlCount = 0;
- action.m_ControlStartIndex = -1;
- }
-
- // Count bindings on each action.
- // After this loop, we can have one of two situations:
- // 1) The bindings for any action X start at some index N and occupy the next m_BindingsCount slots.
- // 2) The bindings for some or all actions are scattered across non-contiguous chunks of the array.
- var bindingCount = m_Bindings.Length;
- for (var i = 0; i < bindingCount; ++i)
- {
- var action = FindAction(m_Bindings[i].action);
- if (action != null)
- ++action.m_BindingsCount;
- }
-
- // Collect the bindings and controls and bundle them into chunks.
- var newBindingsArrayIndex = 0;
- if (m_State != null && (m_ControlsForEachAction == null || m_ControlsForEachAction.Length != mapIndices.controlCount))
- {
- if (mapIndices.controlCount == 0)
- m_ControlsForEachAction = null;
- else
- m_ControlsForEachAction = new InputControl[mapIndices.controlCount];
- }
- InputBinding[] newBindingsArray = null;
- var currentControlIndex = 0;
- for (var currentBindingIndex = 0; currentBindingIndex < m_Bindings.Length;)
- {
- var currentAction = FindAction(m_Bindings[currentBindingIndex].action);
- if (currentAction == null || currentAction.m_BindingsStartIndex != -1)
- {
- // Skip bindings not targeting an action or bindings we have already processed
- // (when gathering bindings for a single actions scattered across the array we may have
- // skipping ahead).
- ++currentBindingIndex;
- continue;
- }
-
- // Bindings for current action start at current index.
- currentAction.m_BindingsStartIndex = newBindingsArray != null
- ? newBindingsArrayIndex
- : currentBindingIndex;
- currentAction.m_ControlStartIndex = currentControlIndex;
-
- // Collect all bindings for the action. As part of that, also copy the controls
- // for each binding over to m_ControlsForEachAction.
- var bindingCountForCurrentAction = currentAction.m_BindingsCount;
- Debug.Assert(bindingCountForCurrentAction > 0);
- var sourceBindingToCopy = currentBindingIndex;
- for (var i = 0; i < bindingCountForCurrentAction; ++i)
- {
- // See if we've come across a binding that doesn't belong to our currently looked at action.
- if (FindAction(m_Bindings[sourceBindingToCopy].action) != currentAction)
- {
- // Yes, we have. Means the bindings for our actions are scattered in m_Bindings and
- // we need to collect them.
-
- // If this is the first action that has its bindings scattered around, switch to
- // having a separate bindings array and copy whatever bindings we already processed
- // over to it.
- if (newBindingsArray == null)
- {
- newBindingsArray = new InputBinding[m_Bindings.Length];
- newBindingsArrayIndex = sourceBindingToCopy;
- Array.Copy(m_Bindings, 0, newBindingsArray, 0, sourceBindingToCopy);
- }
-
- // Find the next binding belonging to the action. We've counted bindings for
- // the action in the previous pass so we know exactly how many bindings we
- // can expect.
- do
- {
- ++sourceBindingToCopy;
- Debug.Assert(sourceBindingToCopy < m_Bindings.Length);
- }
- while (FindAction(m_Bindings[sourceBindingToCopy].action) != currentAction);
- }
- else if (currentBindingIndex == sourceBindingToCopy)
- ++currentBindingIndex;
-
- // Copy binding over to new bindings array, if need be.
- if (newBindingsArray != null)
- newBindingsArray[newBindingsArrayIndex++] = m_Bindings[sourceBindingToCopy];
-
- // Copy controls for binding, if we have resolved controls already and if the
- // binding isn't a composite (they refer to the controls from all of their part bindings
- // but do not really resolve to controls themselves).
- if (m_State != null && !m_Bindings[sourceBindingToCopy].isComposite)
- {
- ref var bindingState = ref m_State.bindingStates[mapIndices.bindingStartIndex + sourceBindingToCopy];
-
- var controlCountForBinding = bindingState.controlCount;
- if (controlCountForBinding > 0)
- {
- // Internally, we allow several bindings on a given action to resolve to the same control.
- // Externally, however, InputAction.controls[] is a set and thus should not contain duplicates.
- // So, instead of just doing a straight copy here, we copy controls one by one.
-
- var controlStartIndexForBinding = bindingState.controlStartIndex;
- for (var n = 0; n < controlCountForBinding; ++n)
- {
- var control = m_State.controls[controlStartIndexForBinding + n];
- if (!m_ControlsForEachAction.ContainsReference(currentAction.m_ControlStartIndex,
- currentAction.m_ControlCount, control))
- {
- m_ControlsForEachAction[currentControlIndex] = control;
- ++currentControlIndex;
- ++currentAction.m_ControlCount;
- }
- }
- }
- }
-
- ++sourceBindingToCopy;
- }
- }
-
- if (newBindingsArray == null)
- {
- // Bindings are already clustered by action in m_Bindings
- // so we can just stick to having one array only.
- m_BindingsForEachAction = m_Bindings;
- }
- else
- {
- // Bindings are not clustered by action in m_Bindings so
- // we had to allocate a separate array where the bindings are sorted.
- m_BindingsForEachAction = newBindingsArray;
- }
- }
-
- controlsForEachActionInitialized = true;
- bindingsForEachActionInitialized = true;
- }
-
- internal void OnWantToChangeSetup()
- {
- if (asset != null)
- {
- foreach (var assetMap in asset.actionMaps)
- if (assetMap.enabled)
- throw new InvalidOperationException(
- $"Cannot add, remove, or change elements of InputActionAsset {asset} while one or more of its actions are enabled");
- }
- else if (enabled)
- {
- throw new InvalidOperationException(
- $"Cannot add, remove, or change elements of InputActionMap {this} while one or more of its actions are enabled");
- }
- }
-
- internal void OnSetupChanged()
- {
- if (m_Asset != null)
- {
- m_Asset.MarkAsDirty();
- foreach (var map in m_Asset.actionMaps)
- map.m_State = default;
- }
- else
- {
- m_State = default;
- }
- ClearCachedActionData();
- LazyResolveBindings(fullResolve: true);
- }
-
- internal void OnBindingModified()
- {
- ClearCachedActionData();
- LazyResolveBindings(fullResolve: true);
- }
-
- ////TODO: re-use allocations such that only grow the arrays and hit zero GC allocs when we already have enough memory
- internal void ClearCachedActionData(bool onlyControls = false)
- {
- if (!onlyControls)
- {
- bindingsForEachActionInitialized = false;
- m_BindingsForEachAction = default;
- m_ActionIndexByNameOrId = default;
- }
-
- controlsForEachActionInitialized = false;
- m_ControlsForEachAction = default;
- }
-
- internal void GenerateId()
- {
- m_Id = Guid.NewGuid().ToString();
- }
-
- /// <summary>
- /// Resolve bindings right away if we have to. Otherwise defer it to when we next need
- /// the bindings.
- /// </summary>
- internal bool LazyResolveBindings(bool fullResolve)
- {
- // Clear cached controls for actions. Don't need to necessarily clear m_BindingsForEachAction.
- m_ControlsForEachAction = null;
- controlsForEachActionInitialized = false;
-
- // If we haven't had to resolve bindings yet, we can wait until when we
- // actually have to.
- if (m_State == null)
- return false;
-
- // We used to defer binding resolution here in case the map had no enabled actions. That behavior,
- // however, leads to rather unpredictable BoundControlsChanged notifications (especially for
- // rebinding UIs), so now we just always re-resolve anything that ever had an InputActionState
- // created. Unfortunately, this can lead to some unnecessary re-resolving.
-
- needToResolveBindings = true;
- bindingResolutionNeedsFullReResolve |= fullResolve;
-
- if (s_DeferBindingResolution > 0)
- return false;
-
- // Have to do it straight away.
- ResolveBindings();
- return true;
- }
-
- internal bool ResolveBindingsIfNecessary()
- {
- // NOTE: We only check locally for the current map here. When there are multiple maps
- // in an asset, we may have maps that require re-resolution while others don't.
- // We only resolve if a map is used that needs resolution to happen. Note that
- // this will still resolve bindings for *all* maps in the asset.
-
- if (m_State == null || needToResolveBindings)
- {
- if (m_State != null && m_State.isProcessingControlStateChange)
- {
- Debug.Assert(s_DeferBindingResolution > 0, "While processing control state changes, binding resolution should be suppressed");
- return false;
- }
-
- ResolveBindings();
- return true;
- }
-
- return false;
- }
-
- // We have three different starting scenarios for binding resolution:
- //
- // (1) From scratch.
- // There is no InputActionState and we resolve everything from a completely fresh start. This happens when
- // we either have not resolved bindings at all yet or when something touches the action setup (e.g. adds
- // or removes an action or binding) and we thus throw away the existing InputActionState.
- // NOTE:
- // * Actions can be in enabled state.
- // * No action can be in an in-progress state (since binding resolution is needed for actions to
- // be processed, no action processing can have happened yet)
- //
- // (2) From an existing InputActionState when a device has been added or removed.
- // There is an InputActionState and the action setup (maps, actions, bindings, binding masks) has not changed. However,
- // the set of devices usable with the action has changed (either the per-asset/map device list or the global
- // list, if we're using it).
- // NOTE:
- // * Actions can be in enabled state.
- // * Actions *can* be in an in-progress state.
- // IF the control currently driving the action is on a device that is no longer usable with the action, the
- // action is CANCELLED. OTHERWISE, the action will be left as is and keep being in progress from its active control.
- // * A device CONFIGURATION change will NOT go down this path (e.g. changing the Keyboard layout). This is because
- // any binding path involving display names may now resolve to different controls -- which may impact currently
- // active controls of in-progress actions.
- // * A change in the USAGES of a device will NOT go down this path either. This is for the same reason -- i.e. an
- // active control may no longer match the binding path it matched before. If, for example, we switch the left-hand
- // and right-hand roles of two controllers, will will go down path (3) and not (2).
- //
- // (3) From an existing InputActionState on any other change not covered before.
- // There is an InputActionState and the action setup (maps, actions, bindings, binding masks) may have changed. Also,
- // any change may have happened in the set of usable devices and targeted controls. This includes binding overrides
- // having been applied.
- // NOTE:
- // * Action can be in enabled state.
- // * Actions *can* be in an in-progress state.
- // Any such action will be CANCELLED as part of the re-resolution process.
- //
- // Both (1) and (3) are considered a "full resolve". (2) is not.
-
- /// <summary>
- /// Resolve all bindings to their controls and also add any action interactions
- /// from the bindings.
- /// </summary>
- /// <remarks>
- /// This is the core method of action binding resolution. All binding resolution goes through here.
- ///
- /// The best way is for binding resolution to happen once for each action map at the beginning of the game
- /// and to then enable and disable the maps as needed. However, the system will also re-resolve
- /// bindings if the control setup in the system changes (i.e. if devices are added or removed
- /// or if layouts in the system are changed).
- ///
- /// Bindings can be re-resolved while actions are enabled. This happens changing device or binding
- /// masks on action maps or assets (<see cref="devices"/>, <see cref="bindingMask"/>, <see cref="InputAction.bindingMask"/>,
- /// <see cref="InputActionAsset.devices"/>, <see cref="InputActionAsset.bindingMask"/>). Doing so will
- /// not affect the enable state of actions and, as much as possible, will try to take current
- /// action states across.
- /// </remarks>
- internal void ResolveBindings()
- {
- // Make sure that if we trigger callbacks as part of disabling and re-enabling actions,
- // we don't trigger a re-resolve while we're already resolving bindings.
- using (InputActionRebindingExtensions.DeferBindingResolution())
- {
- // In case we have actions that are currently enabled, we temporarily retain the
- // UnmanagedMemory of our InputActionState so that we can sync action states after
- // we have re-resolved bindings.
- var oldMemory = new InputActionState.UnmanagedMemory();
- try
- {
- OneOrMore<InputActionMap, ReadOnlyArray<InputActionMap>> actionMaps;
-
- // Start resolving.
- var resolver = new InputBindingResolver();
-
- // If we're part of an asset, we share state and thus binding resolution with
- // all maps in the asset.
- var needFullResolve = m_State == null;
- if (m_Asset != null)
- {
- actionMaps = m_Asset.actionMaps;
- Debug.Assert(actionMaps.Count > 0, "Asset referred to by action map does not have action maps");
-
- // If there's a binding mask set on the asset, apply it.
- resolver.bindingMask = m_Asset.m_BindingMask;
-
- foreach (var map in actionMaps)
- {
- needFullResolve |= map.bindingResolutionNeedsFullReResolve;
- map.needToResolveBindings = false;
- map.bindingResolutionNeedsFullReResolve = false;
- map.controlsForEachActionInitialized = false;
- }
- }
- else
- {
- // Standalone action map (possibly a hidden one created for a singleton action).
- // Gets its own private state.
-
- actionMaps = this;
- needFullResolve |= bindingResolutionNeedsFullReResolve;
- needToResolveBindings = false;
- bindingResolutionNeedsFullReResolve = false;
- controlsForEachActionInitialized = false;
- }
-
- // If we already have a state, re-use the arrays we have already allocated.
- // NOTE: We will install the arrays on the very same InputActionState instance below. In the
- // case where we didn't have to grow the arrays, we should end up with zero GC allocations
- // here.
- var hasEnabledActions = false;
- InputControlList<InputControl> activeControls = default;
- if (m_State != null)
- {
- // Grab a clone of the current memory. We clone because disabling all the actions
- // in the map will alter the memory state and we want the state before we start
- // touching it.
- oldMemory = m_State.memory.Clone();
-
- m_State.PrepareForBindingReResolution(needFullResolve, ref activeControls, ref hasEnabledActions);
-
- // Reuse the arrays we have so that we can avoid managed memory allocations, if possible.
- resolver.StartWithPreviousResolve(m_State, isFullResolve: needFullResolve);
-
- // Throw away old memory.
- m_State.memory.Dispose();
- }
-
- // Resolve all maps in the asset.
- foreach (var map in actionMaps)
- resolver.AddActionMap(map);
-
- // Install state.
- if (m_State == null)
- {
- m_State = new InputActionState();
- m_State.Initialize(resolver);
- }
- else
- {
- m_State.ClaimDataFrom(resolver);
- }
- if (m_Asset != null)
- {
- foreach (var map in actionMaps)
- map.m_State = m_State;
- m_Asset.m_SharedStateForAllMaps = m_State;
- }
-
- m_State.FinishBindingResolution(hasEnabledActions, oldMemory, activeControls, isFullResolve: needFullResolve);
- }
- finally
- {
- oldMemory.Dispose();
- }
- }
- }
-
- /// <inheritdoc/>
- public int FindBinding(InputBinding mask, out InputAction action)
- {
- var index = FindBindingRelativeToMap(mask);
- if (index == -1)
- {
- action = null;
- return -1;
- }
-
- action = m_SingletonAction ?? FindAction(bindings[index].action);
- return action.BindingIndexOnMapToBindingIndexOnAction(index);
- }
-
- /// <summary>
- /// Find the index of the first binding that matches the given mask.
- /// </summary>
- /// <param name="mask">A binding. See <see cref="InputBinding.Matches"/> for details.</param>
- /// <returns>Index into <see cref="InputAction.bindings"/> of <paramref name="action"/> of the binding
- /// that matches <paramref name="mask"/>. If no binding matches, will return -1.</returns>
- /// <remarks>
- /// For details about matching bindings by a mask, see <see cref="InputBinding.Matches"/>.
- ///
- /// <example>
- /// <code>
- /// var index = playerInput.actions.FindBindingRelativeToMap(
- /// new InputBinding { path = "<Gamepad>/buttonSouth" });
- ///
- /// if (index != -1)
- /// Debug.Log($"Found binding with index {index}");
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="InputBinding.Matches"/>
- /// <seealso cref="bindings"/>
- internal int FindBindingRelativeToMap(InputBinding mask)
- {
- var bindings = m_Bindings;
- var bindingsCount = bindings.LengthSafe();
-
- for (var i = 0; i < bindingsCount; ++i)
- {
- ref var binding = ref bindings[i];
- if (mask.Matches(ref binding))
- return i;
- }
-
- return -1;
- }
-
- #region Serialization
-
- ////REVIEW: when GetParameter/SetParameter is coming, should these also be considered part of binding override data?
-
- [Serializable]
- internal struct BindingOverrideListJson
- {
- public List<BindingOverrideJson> bindings;
- }
-
- [Serializable]
- internal struct BindingOverrideJson
- {
- // We save both the "map/action" path of the action as well as the binding ID.
- // This gives us two avenues into finding our target binding to apply the override
- // to.
- public string action;
- public string id;
- public string path;
- public string interactions;
- public string processors;
-
- public static BindingOverrideJson FromBinding(InputBinding binding, string actionName)
- {
- return new BindingOverrideJson
- {
- action = actionName,
- id = binding.id.ToString() ,
- path = binding.overridePath ?? "null",
- interactions = binding.overrideInteractions ?? "null",
- processors = binding.overrideProcessors ?? "null"
- };
- }
-
- public static BindingOverrideJson FromBinding(InputBinding binding)
- {
- return FromBinding(binding, binding.action);
- }
-
- public static InputBinding ToBinding(BindingOverrideJson bindingOverride)
- {
- return new InputBinding
- {
- overridePath = bindingOverride.path != "null" ? bindingOverride.path : null,
- overrideInteractions = bindingOverride.interactions != "null" ? bindingOverride.interactions : null,
- overrideProcessors = bindingOverride.processors != "null" ? bindingOverride.processors : null,
- };
- }
- }
-
- // Action maps are serialized in two different ways. For storage as imported assets in Unity's Library/ folder
- // and in player data and asset bundles as well as for surviving domain reloads, InputActionMaps are serialized
- // directly by Unity. For storage as source data in user projects, InputActionMaps are serialized indirectly
- // as JSON by setting up a separate set of structs that are then read and written using Unity's JSON serializer.
-
- [Serializable]
- internal struct BindingJson
- {
- public string name;
- public string id;
- public string path;
- public string interactions;
- public string processors;
- public string groups;
- public string action;
- public bool isComposite;
- public bool isPartOfComposite;
-
- public InputBinding ToBinding()
- {
- return new InputBinding
- {
- name = string.IsNullOrEmpty(name) ? null : name,
- m_Id = string.IsNullOrEmpty(id) ? null : id,
- path = path,
- action = string.IsNullOrEmpty(action) ? null : action,
- interactions = string.IsNullOrEmpty(interactions) ? null : interactions,
- processors = string.IsNullOrEmpty(processors) ? null : processors,
- groups = string.IsNullOrEmpty(groups) ? null : groups,
- isComposite = isComposite,
- isPartOfComposite = isPartOfComposite,
- };
- }
-
- public static BindingJson FromBinding(ref InputBinding binding)
- {
- return new BindingJson
- {
- name = binding.name,
- id = binding.m_Id,
- path = binding.path,
- action = binding.action,
- interactions = binding.interactions,
- processors = binding.processors,
- groups = binding.groups,
- isComposite = binding.isComposite,
- isPartOfComposite = binding.isPartOfComposite,
- };
- }
- }
-
- // Backwards-compatible read format.
- [Serializable]
- internal struct ReadActionJson
- {
- public string name;
- public string type;
- public string id;
- public string expectedControlType;
- public string expectedControlLayout;
- public string processors;
- public string interactions;
- public bool passThrough;
- public bool initialStateCheck;
-
- // Bindings can either be on the action itself (in which case the action name
- // for each binding is implied) or listed separately in the action file.
- public BindingJson[] bindings;
-
- public InputAction ToAction(string actionName = null)
- {
- // FormerlySerializedAs doesn't seem to work as expected so manually
- // handling the rename here.
- if (!string.IsNullOrEmpty(expectedControlLayout))
- expectedControlType = expectedControlLayout;
-
- // Determine type.
- InputActionType actionType = default;
- if (!string.IsNullOrEmpty(type))
- actionType = (InputActionType)Enum.Parse(typeof(InputActionType), type, true);
- else
- {
- // Old format that doesn't have type. Try to infer from settings.
-
- if (passThrough)
- actionType = InputActionType.PassThrough;
- else if (initialStateCheck)
- actionType = InputActionType.Value;
- else if (!string.IsNullOrEmpty(expectedControlType) &&
- (expectedControlType == "Button" || expectedControlType == "Key"))
- actionType = InputActionType.Button;
- }
-
- return new InputAction(actionName ?? name, actionType)
- {
- m_Id = string.IsNullOrEmpty(id) ? null : id,
- m_ExpectedControlType = !string.IsNullOrEmpty(expectedControlType)
- ? expectedControlType
- : null,
- m_Processors = processors,
- m_Interactions = interactions,
- wantsInitialStateCheck = initialStateCheck,
- };
- }
- }
-
- [Serializable]
- internal struct WriteActionJson
- {
- public string name;
- public string type;
- public string id;
- public string expectedControlType;
- public string processors;
- public string interactions;
- public bool initialStateCheck;
-
- public static WriteActionJson FromAction(InputAction action)
- {
- return new WriteActionJson
- {
- name = action.m_Name,
- type = action.m_Type.ToString(),
- id = action.m_Id,
- expectedControlType = action.m_ExpectedControlType,
- processors = action.processors,
- interactions = action.interactions,
- initialStateCheck = action.wantsInitialStateCheck,
- };
- }
- }
-
- [Serializable]
- internal struct ReadMapJson
- {
- public string name;
- public string id;
- public ReadActionJson[] actions;
- public BindingJson[] bindings;
- }
-
- [Serializable]
- internal struct WriteMapJson
- {
- public string name;
- public string id;
- public WriteActionJson[] actions;
- public BindingJson[] bindings;
-
- public static WriteMapJson FromMap(InputActionMap map)
- {
- WriteActionJson[] jsonActions = null;
- BindingJson[] jsonBindings = null;
-
- var actions = map.m_Actions;
- if (actions != null)
- {
- var actionCount = actions.Length;
- jsonActions = new WriteActionJson[actionCount];
-
- for (var i = 0; i < actionCount; ++i)
- jsonActions[i] = WriteActionJson.FromAction(actions[i]);
- }
-
- var bindings = map.m_Bindings;
- if (bindings != null)
- {
- var bindingCount = bindings.Length;
- jsonBindings = new BindingJson[bindingCount];
-
- for (var i = 0; i < bindingCount; ++i)
- jsonBindings[i] = BindingJson.FromBinding(ref bindings[i]);
- }
-
- return new WriteMapJson
- {
- name = map.name,
- id = map.id.ToString(),
- actions = jsonActions,
- bindings = jsonBindings,
- };
- }
- }
-
- // We write JSON in a less flexible format than we allow to be read. JSON files
- // we read can just be flat lists of actions with the map name being contained in
- // the action name and containing their own bindings directly. JSON files we write
- // go map by map and separate bindings and actions.
- [Serializable]
- internal struct WriteFileJson
- {
- public WriteMapJson[] maps;
-
- public static WriteFileJson FromMap(InputActionMap map)
- {
- return new WriteFileJson
- {
- maps = new[] {WriteMapJson.FromMap(map)}
- };
- }
-
- public static WriteFileJson FromMaps(IEnumerable<InputActionMap> maps)
- {
- var mapCount = maps.Count();
- if (mapCount == 0)
- return new WriteFileJson();
-
- var mapsJson = new WriteMapJson[mapCount];
- var index = 0;
- foreach (var map in maps)
- mapsJson[index++] = WriteMapJson.FromMap(map);
-
- return new WriteFileJson {maps = mapsJson};
- }
- }
-
- // A JSON representation of one or more sets of actions.
- // Contains a list of actions. Each action may specify the set it belongs to
- // as part of its name ("set/action").
- [Serializable]
- internal struct ReadFileJson
- {
- public ReadActionJson[] actions;
- public ReadMapJson[] maps;
-
- public InputActionMap[] ToMaps()
- {
- var mapList = new List<InputActionMap>();
- var actionLists = new List<List<InputAction>>();
- var bindingLists = new List<List<InputBinding>>();
-
- // Process actions listed at toplevel.
- var actionCount = actions?.Length ?? 0;
- for (var i = 0; i < actionCount; ++i)
- {
- var jsonAction = actions[i];
-
- if (string.IsNullOrEmpty(jsonAction.name))
- throw new InvalidOperationException($"Action number {i + 1} has no name");
-
- ////REVIEW: make sure all action names are unique?
-
- // Determine name of action map.
- string mapName = null;
- var actionName = jsonAction.name;
- var indexOfFirstSlash = actionName.IndexOf('/');
- if (indexOfFirstSlash != -1)
- {
- mapName = actionName.Substring(0, indexOfFirstSlash);
- actionName = actionName.Substring(indexOfFirstSlash + 1);
-
- if (string.IsNullOrEmpty(actionName))
- throw new InvalidOperationException(
- $"Invalid action name '{jsonAction.name}' (missing action name after '/')");
- }
-
- // Try to find existing map.
- InputActionMap map = null;
- var mapIndex = 0;
- for (; mapIndex < mapList.Count; ++mapIndex)
- {
- if (string.Compare(mapList[mapIndex].name, mapName, StringComparison.InvariantCultureIgnoreCase) == 0)
- {
- map = mapList[mapIndex];
- break;
- }
- }
-
- // Create new map if it's the first action in the map.
- if (map == null)
- {
- // NOTE: No map IDs supported on this path.
- map = new InputActionMap(mapName);
- mapIndex = mapList.Count;
- mapList.Add(map);
- actionLists.Add(new List<InputAction>());
- bindingLists.Add(new List<InputBinding>());
- }
-
- // Create action.
- var action = jsonAction.ToAction(actionName);
- actionLists[mapIndex].Add(action);
-
- // Add bindings.
- if (jsonAction.bindings != null)
- {
- var bindingsForMap = bindingLists[mapIndex];
- for (var n = 0; n < jsonAction.bindings.Length; ++n)
- {
- var jsonBinding = jsonAction.bindings[n];
- var binding = jsonBinding.ToBinding();
- binding.action = action.m_Name;
- bindingsForMap.Add(binding);
- }
- }
- }
-
- // Process maps.
- var mapCount = maps?.Length ?? 0;
- for (var i = 0; i < mapCount; ++i)
- {
- var jsonMap = maps[i];
-
- var mapName = jsonMap.name;
- if (string.IsNullOrEmpty(mapName))
- throw new InvalidOperationException($"Map number {i + 1} has no name");
-
- // Try to find existing map.
- InputActionMap map = null;
- var mapIndex = 0;
- for (; mapIndex < mapList.Count; ++mapIndex)
- {
- if (string.Compare(mapList[mapIndex].name, mapName, StringComparison.InvariantCultureIgnoreCase) == 0)
- {
- map = mapList[mapIndex];
- break;
- }
- }
-
- // Create new map if we haven't seen it before.
- if (map == null)
- {
- map = new InputActionMap(mapName)
- {
- m_Id = string.IsNullOrEmpty(jsonMap.id) ? null : jsonMap.id
- };
- mapIndex = mapList.Count;
- mapList.Add(map);
- actionLists.Add(new List<InputAction>());
- bindingLists.Add(new List<InputBinding>());
- }
-
- // Process actions in map.
- var actionCountInMap = jsonMap.actions?.Length ?? 0;
- for (var n = 0; n < actionCountInMap; ++n)
- {
- var jsonAction = jsonMap.actions[n];
-
- if (string.IsNullOrEmpty(jsonAction.name))
- throw new InvalidOperationException($"Action number {i + 1} in map '{mapName}' has no name");
-
- // Create action.
- var action = jsonAction.ToAction();
- actionLists[mapIndex].Add(action);
-
- // Add bindings.
- if (jsonAction.bindings != null)
- {
- var bindingList = bindingLists[mapIndex];
- for (var k = 0; k < jsonAction.bindings.Length; ++k)
- {
- var jsonBinding = jsonAction.bindings[k];
- var binding = jsonBinding.ToBinding();
- binding.action = action.m_Name;
- bindingList.Add(binding);
- }
- }
- }
-
- // Process bindings in map.
- var bindingCountInMap = jsonMap.bindings?.Length ?? 0;
- var bindingsForMap = bindingLists[mapIndex];
- for (var n = 0; n < bindingCountInMap; ++n)
- {
- var jsonBinding = jsonMap.bindings[n];
- var binding = jsonBinding.ToBinding();
- bindingsForMap.Add(binding);
- }
- }
-
- // Finalize arrays.
- for (var i = 0; i < mapList.Count; ++i)
- {
- var map = mapList[i];
-
- var actionArray = actionLists[i].ToArray();
- var bindingArray = bindingLists[i].ToArray();
-
- map.m_Actions = actionArray;
- map.m_Bindings = bindingArray;
-
- for (var n = 0; n < actionArray.Length; ++n)
- {
- var action = actionArray[n];
- action.m_ActionMap = map;
- }
- }
-
- return mapList.ToArray();
- }
- }
-
- /// <summary>
- /// Load one or more action maps from JSON.
- /// </summary>
- /// <param name="json">JSON representation of the action maps. Can be empty.</param>
- /// <exception cref="ArgumentNullException"><paramref name="json"/> is <c>null</c>.</exception>
- /// <returns>The array of action maps (may be empty) read from the given JSON string. Will not be
- /// <c>null</c>.</returns>
- /// <remarks>
- /// Note that the format used by this method is different than what you
- /// get if you call <c>JsonUtility.ToJson</c> on an InputActionMap instance. In other
- /// words, the JSON format is not identical to the Unity serialized object representation
- /// of the asset.
- ///
- /// <example>
- /// <code>
- /// var maps = InputActionMap.FromJson(@"
- /// {
- /// ""maps"" : [
- /// {
- /// ""name"" : ""Gameplay"",
- /// ""actions"" : [
- /// { ""name"" : ""fire"", ""type"" : ""button"" }
- /// ],
- /// ""bindings"" : [
- /// { ""path"" : ""<Gamepad>/leftTrigger"", ""action"" : ""fire"" }
- /// ],
- /// }
- /// ]
- /// }
- /// ");
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="InputActionAsset.FromJson"/>
- /// <seealso cref="ToJson(IEnumerable{InputActionMap})"/>
- public static InputActionMap[] FromJson(string json)
- {
- if (json == null)
- throw new ArgumentNullException(nameof(json));
- var fileJson = JsonUtility.FromJson<ReadFileJson>(json);
- return fileJson.ToMaps();
- }
-
- /// <summary>
- /// Convert a set of action maps to JSON format.
- /// </summary>
- /// <param name="maps">List of action maps to serialize.</param>
- /// <exception cref="ArgumentNullException"><paramref name="maps"/> is <c>null</c>.</exception>
- /// <returns>JSON representation of the given action maps.</returns>
- /// <remarks>
- /// The result of this method can be loaded with <see cref="FromJson"/>.
- ///
- /// Note that the format used by this method is different than what you
- /// get if you call <c>JsonUtility.ToJson</c> on an InputActionMap instance. In other
- /// words, the JSON format is not identical to the Unity serialized object representation
- /// of the asset.
- /// </remarks>
- /// <seealso cref="FromJson"/>
- public static string ToJson(IEnumerable<InputActionMap> maps)
- {
- if (maps == null)
- throw new ArgumentNullException(nameof(maps));
- var fileJson = WriteFileJson.FromMaps(maps);
- return JsonUtility.ToJson(fileJson, true);
- }
-
- /// <summary>
- /// Convert the action map to JSON format.
- /// </summary>
- /// <returns>A JSON representation of the action map.</returns>
- /// <remarks>
- /// The result of this method can be loaded with <see cref="FromJson"/>.
- ///
- /// Note that the format used by this method is different than what you
- /// get if you call <c>JsonUtility.ToJson</c> on an InputActionMap instance. In other
- /// words, the JSON format is not identical to the Unity serialized object representation
- /// of the asset.
- /// </remarks>
- public string ToJson()
- {
- var fileJson = WriteFileJson.FromMap(this);
- return JsonUtility.ToJson(fileJson, true);
- }
-
- /// <summary>
- /// Called by Unity before the action map is serialized using Unity's
- /// serialization system.
- /// </summary>
- public void OnBeforeSerialize()
- {
- }
-
- /// <summary>
- /// Called by Unity after the action map has been deserialized using Unity's
- /// serialization system.
- /// </summary>
- public void OnAfterDeserialize()
- {
- m_State = null;
- m_MapIndexInState = InputActionState.kInvalidIndex;
- m_EnabledActionsCount = 0;
-
- // Restore references of actions linking back to us.
- if (m_Actions != null)
- {
- var actionCount = m_Actions.Length;
- for (var i = 0; i < actionCount; ++i)
- m_Actions[i].m_ActionMap = this;
- }
-
- // Make sure we don't retain any cached per-action data when using serialization
- // to doctor around in action map configurations in the editor.
- ClearCachedActionData();
- ClearActionLookupTable();
- }
-
- #endregion
- }
- }
|