Skip to content

FIX: PlayerInput does not work with C# wrappers (ISXB-1535) #2188

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from

Conversation

jfreire-unity
Copy link
Collaborator

@jfreire-unity jfreire-unity commented Jun 2, 2025

Description

This PR attempts to fix a PlayerInput issue when working with code generated from InputActionAssets (vulgarly called a "C# Wrapper).

In that attempt, it reverts #2144 since it introduces a regression resulting in ISXB-1535.

The fundamental problem of #2144 is caused by project-wide actions having ALL action maps enabled by default, which can create a problem if the user has the same binding in both action maps of project-wide actions assigned to PlayerInput. In the first time a control with the same binding or more than one action map is actuated, all of its actions in different action maps will be performed ❌ See ISXB-920 for more.

Also, if a user tries to call InputSystem.actions.Disable() on Start/Awake/OnEnable in their scripts to disable action maps manually, it will not work. The actions will still be enabled with InputSystem.EnableActions() in Play-mode (this doesn't happen in Standalone builds).

Below there's a sketch of the events code flow order in the Play-mode (last step doesn't occur in the standalone build, so the problem wouldn't exist).

flowchart LR
    A["InitializeInEditor(or inPlayer)<br/><i>calls EnableActions()</i>"]
    B["PlayerInput.OnEnable<br/><i>calls SwitchCurrentActionMaps()</i>"]
    C["User Scripts<br/><i>Start(), OnEnable(), Awake(), etc </i>"]
    D["InputSystem.OnPlayModeChange(EnteredPlayMode)<br/><i>calls EnableActions()</i>"]
    A --> B --> C --> D
    
        style D stroke:#ffff00,stroke-width:2px
Loading

In #2144 a copy of the asset for a PlayerInput component is ALWAYS made, regardless if you're in single player or local multiplayer. The copied asset has all its action maps disabled by default. This allows the PlayerInput to enable the ones it needs to and fixes the problem.

However, C# wrappers have "self-contained" assets (see e.g. DefaultInputActions.asset). So if a copy of the self-contained asset is made and assigned to playerInput.asset, using the default wrapper functionality will not work, since it acts on the self-contained asset ❌ See test introduced to make sure we fix the problem reported by the user: PlayerInput_ActionFromCodeGeneratedActionIsTheSameBeingReferenced.

Instead of creating a copy of the asset, I propose a solution where the PlayerInput component using project-wide actions will disable all its action maps at startup. This allows PlayerInput to then select the appropriate action map to be enabled (either a default action map or some other one the user enables in their scripts).

This somehow needs to happen because the intended default behavior of ProjectWideActions is to have all actions enabled by default, which will cause problems with PlayerInput as it only selects the one that should be enabled.

Testing status & QA

Tested both projects of reported bugs, ISXB-1535 and ISXB-920.
Added automated testing to avoid future regressions.

Overall Product Risks

Please rate the potential complexity and halo effect from low to high for the reviewers. Note down potential risks to specific Editor branches if any.

  • Complexity: 0
  • Halo Effect: 1

Comments to reviewers

Alternative: If this is interpreted as big breaking change, then we could instead allow for the user to be able to disable action maps in Start/Awake/OnEnable.

If this PR is accepted, I will add public documentation for it with regards to PlayerInput and project-wide actions sections

  • Still need to add an automated test to make sure the ISXB-920 problem is solved.

Checklist

Before review:

  • Changelog entry added.
    • Explains the change in Changed, Fixed, Added sections.
    • For API change contains an example snippet and/or migration example.
    • JIRA ticket linked, example (case %%). If it is a private issue, just add the case ID without a link.
    • Jira port for the next release set as "Resolved".
  • Tests added/changed, if applicable.
    • Functional tests Area_CanDoX, Area_CanDoX_EvenIfYIsTheCase, Area_WhenIDoX_AndYHappens_ThisIsTheResult.
    • Performance tests.
    • Integration tests.
  • Docs for new/changed API's.
    • Xmldoc cross references are set correctly.
    • Added explanation how the API works.
    • Usage code examples added.
    • The manual is updated, if needed.

During merge:

  • Commit message for squash-merge is prefixed with one of the list:
    • NEW: ___.
    • FIX: ___.
    • DOCS: ___.
    • CHANGE: ___.
    • RELEASE: 1.1.0-preview.3.

After merge:

  • Create forward/backward port if needed. If you are blocked from creating a forward port now please add a task to ISX-1444.

@codecov-github-com
Copy link

codecov-github-com bot commented Jun 2, 2025

Codecov Report

Attention: Patch coverage is 90.90909% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...s/com.unity.inputsystem/InputSystem/InputSystem.cs 83.33% 1 Missing ⚠️
@@             Coverage Diff             @@
##           develop    #2188      +/-   ##
===========================================
- Coverage    65.45%   65.45%   -0.01%     
===========================================
  Files          367      367              
  Lines        53505    53508       +3     
===========================================
  Hits         35024    35024              
- Misses       18481    18484       +3     
Flag Coverage Δ
mac_2021.3_pkg 5.42% <ø> (+<0.01%) ⬆️
mac_2021.3_project 70.42% <ø> (+<0.01%) ⬆️
mac_2022.3_pkg 5.19% <0.00%> (-0.01%) ⬇️
mac_2022.3_project 65.29% <90.90%> (+<0.01%) ⬆️
mac_6000.0_pkg 5.20% <0.00%> (-0.01%) ⬇️
mac_6000.0_project 65.37% <90.90%> (-0.01%) ⬇️
mac_6000.1_pkg 5.20% <0.00%> (-0.01%) ⬇️
mac_6000.1_project 65.37% <90.90%> (+<0.01%) ⬆️
mac_6000.2_pkg 5.20% <0.00%> (-0.01%) ⬇️
mac_6000.2_project 65.37% <90.90%> (+<0.01%) ⬆️
mac_trunk_pkg 5.20% <0.00%> (-0.01%) ⬇️
mac_trunk_project 65.37% <90.90%> (+<0.01%) ⬆️
win_2021.3_pkg 5.42% <ø> (+<0.01%) ⬆️
win_2021.3_project 70.50% <ø> (+<0.01%) ⬆️
win_2022.3_pkg 5.20% <0.00%> (-0.01%) ⬇️
win_2022.3_project 65.37% <90.90%> (+<0.01%) ⬆️
win_6000.0_pkg 5.20% <0.00%> (-0.01%) ⬇️
win_6000.0_project 65.45% <90.90%> (+<0.01%) ⬆️
win_6000.1_pkg 5.20% <0.00%> (-0.01%) ⬇️
win_6000.1_project 65.45% <90.90%> (+<0.01%) ⬆️
win_6000.2_pkg 5.20% <0.00%> (-0.01%) ⬇️
win_6000.2_project 65.45% <90.90%> (+<0.01%) ⬆️
win_trunk_pkg 5.20% <0.00%> (-0.01%) ⬇️
win_trunk_project 65.45% <90.90%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...tem/InputSystem/Plugins/PlayerInput/PlayerInput.cs 86.95% <100.00%> (+0.33%) ⬆️
...s/com.unity.inputsystem/InputSystem/InputSystem.cs 82.96% <83.33%> (-0.02%) ⬇️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jfreire-unity jfreire-unity requested a review from Copilot June 3, 2025 06:38
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR addresses a bug related to PlayerInput support with C# wrappers by removing the automatic copying of action assets and adding logic to correctly disable project-wide actions when a PlayerInput is active.

  • Removed asset copying logic from PlayerInput properties and Awake.
  • Added handling to disable project-wide actions via InputSystem and updated corresponding tests.
  • Revised tests to assert reference equality and proper enabling/disabling of action maps.

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs Removed asset copying in setter and Awake, added disabling of project-wide actions when enabled.
Packages/com.unity.inputsystem/InputSystem/InputSystem.cs Introduced a flag to control enabling of actions after entering Play Mode with updated logic in OnPlayModeChange.
Assets/Tests/InputSystem/Plugins/PlayerInputTests.cs Updated and added tests to validate new behavior in asset assignment and action map states.
Assets/Tests/InputSystem/CoreTests_ProjectWideActions.cs Added tests to verify that project-wide action maps are correctly disabled based on PlayerInput’s default action map.
Comments suppressed due to low confidence (3)

Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs:342

  • Removing the asset copying functionality here changes how action assets are managed. Adding a comment to explain this change will help maintainers understand why modifications to the original asset are now allowed.
var didChange = m_Actions != null;

Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs:1810

  • The removal of the Awake method that copied the action asset should be documented, as it significantly alters asset management and may affect users expecting a copied instance.
private void Awake()

Assets/Tests/InputSystem/Plugins/PlayerInputTests.cs:412

  • Please confirm that the updated assertions correctly reflect the intended behavior, ensuring that only the appropriate action map is enabled while others remain disabled.
Assert.That(actions2.actionMaps[0].enabled, Is.False);

@jfreire-unity jfreire-unity marked this pull request as ready for review June 3, 2025 07:43
@jfreire-unity jfreire-unity requested review from ekcoh and ritamerkl June 3, 2025 08:14
@jfreire-unity jfreire-unity changed the title FIX: PlayerInput does not support with C# wrappers (ISXB-1535) FIX: PlayerInput does not work with C# wrappers (ISXB-1535) Jun 3, 2025
@jfreire-unity jfreire-unity self-assigned this Jun 3, 2025
@jfreire-unity jfreire-unity force-pushed the isxb-1535-playerinput-does-not-work-w-csharp-wrappers branch 2 times, most recently from 35296c7 to 1b40c51 Compare June 3, 2025 08:41
@jfreire-unity jfreire-unity force-pushed the isxb-1535-playerinput-does-not-work-w-csharp-wrappers branch from 1b40c51 to 2eb009a Compare June 3, 2025 09:03
@jfreire-unity jfreire-unity requested a review from Pauliusd01 June 3, 2025 09:22
@jfreire-unity jfreire-unity requested a review from K-Tone June 3, 2025 09:29
Copy link
Collaborator

@ritamerkl ritamerkl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to test this with the ticket where the original PR was raised and now reverted.
This does not look like a solid solution in long term. This area is fragile and the fix is more a workaround. Since the optimal solution is probably not feasible to implement straight away -that's fine-, but I still wonder if there isn't an option without reverting the copy. I remember we talked about some other variants too.

/// </remarks>
internal static bool m_EnableActionsAfterEnterPlayMode = true;

internal static void AllowEnableActionsAfterEnterPlayMode(bool enabled)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the naming of the method a bit odd in combination with the parameter. In this case it is hard to understand immediately if "enabled" means the actions or allowing to enable them.
I would suggest rephrasing the method name or calling the parameter something like "bool allowed".

Copy link
Collaborator Author

@jfreire-unity jfreire-unity Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'll revisit the naming. I did forget to look at the naming again as I did a first iteration of something a bit simpler but that it didn't work.

I'll try to see if I can avoid introducing this and just move EnableActions() to a better place that doesn't require them.

m_Actions = value;

if (didChange || m_Enabled)
// copy action asset for the first player so that the original asset stays untouched
CopyActionAssetAndApplyBindingOverrides();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am still concerned with removing the copy for the first player.
In the long run this potentially leads to conflicts again as soon as action assets are accessed and operated from somewhere out of the player input.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leads to conflicts again

Can you specify which conflicts have occurred before? I'm not aware of any other bugs/conlfits that would require a copy for the first player.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I am referring to is that the project wide actions was the first occurrence, since the asset can be operated freely from anywhere else without further restrictions I think it's not unlikely to encounter similar problems in the future (even if we can not directly think of one now).

Copy link
Collaborator Author

@jfreire-unity jfreire-unity Jun 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe project-wide actions didn't consider this use case during the design phase. In the end, PWA it's just another asset with a "small" difference that has all actions enabled. The normal behavior of an asset is to come with actions disabled. But this is not really relevant to the discussion of doing copies, which I want to explore further.

Making a copy during assignment (playerInput.asset = myAsset) is problematic for two reasons:

  • It breaks expected behavior in C# - as a developer, I assume reference types maintain references rather than creating copies on assignment.
  • It introduces inconsistency with our existing code, like InputSystemUIInputModule.actionAsset which maintains a reference, and doesn't do a copy.

I feel we get into a slippery slope of inconsistencies if we go down the path of doing copies for every asset assignment.

I'm fine with going with a different direction, but I would like to remove the copy behavior on assignment regardless of what we agree on doing.

Copy link
Collaborator

@ritamerkl ritamerkl Jun 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree on that, assigning with a copy under the hood and the reference is indeed confusing.
Since this happens for all other players (except for the first), the most consistent way to do it would probably to not do it at all, or changing the behaviour to make the copy more visible.
To get a consistent behaviour I think wee need to decide for one way. What I am thinking now is:

  • defining a "rootAsset" which can be assigned and then just make the player copy available. (breaking change)
  • not copying the asset but outsourcing the runtime data to a metafile for instance (bigger change, but maybe not necessarily breaking change?)

I think the third option is the solution you proposed, which rolls back to the previous inconsistent state. I think that's valid with the argument to not break anything, though I believe it's not ideal in terms of consistent behaviour.

if (m_Actions != null && m_Actions == InputSystem.actions)
{
InputSystem.AllowEnableActionsAfterEnterPlayMode(false);
InputSystem.actions.Disable();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will have an effect on the PlayerInput asset too, is disabling of all actions intended at this spot?

@jfreire-unity
Copy link
Collaborator Author

I would suggest to test this with the ticket where the original PR was raised and now reverted. This does not look like a solid solution in long term. This area is fragile and the fix is more a workaround. Since the optional solution is probably not feasible to implement straight away -that's fine-, but I still wonder if there isn't an option without reverting the copy. I remember we talked about some other variants too.

The bug mentioned is fixed with this PR, as well as the one this PR targets.
The copy solution has a design flaw: PlayerInput.actions.set does a copy of the asset and should not do it. From an API perspective, it's not explicit. Also, there's no way for users to assign an asset reference in any way to a PlayerInput component since it acts on a copy of the referenced asset.

The other solutions I previously proposed for maintaining the copy expose yet another public API to users. This exposes no other API to the users. Ideally, I would even prefer that this code would not exist, but I'm not seeing another way atm.

I think if we communicate clearly that PlayerInput actions disable project-wide actions at "startup", it's still better than maintaining the copy behavior and introducing a flag to users.

Project-wide actions behavior is to have all action maps enabled so that it's easier for users to get any kind of Input.
On the other hand, PlayerInput works on a particular action map of an InputActionAsset so it should control the asset state.

@ritamerkl
Copy link
Collaborator

ritamerkl commented Jun 3, 2025

I would suggest to test this with the ticket where the original PR was raised and now reverted. This does not look like a solid solution in long term. This area is fragile and the fix is more a workaround. Since the optional solution is probably not feasible to implement straight away -that's fine-, but I still wonder if there isn't an option without reverting the copy. I remember we talked about some other variants too.

The bug mentioned is fixed with this PR, as well as the one this PR targets.
The copy solution has a design flaw: PlayerInput.actions.set does a copy of the asset and should not do it. From an API perspective, it's not explicit. Also, there's no way for users to assign an asset reference in any way to a PlayerInput component since it acts on a copy of the referenced asset.

I am not sure what you are referring to, the setter will create a copy once the PlayerInput action asset is set in runtime (which basically means set by code (which is consistent with the copy after assigning it in the component). To refer to the asset users would need to use the PlayerInput actions instead of the asset, which I think is feasible, since for multiplayer this logic would be wrong anyway- it would only affect the first user. It changes the way to access the PlayerInput asset via code, that is true, but I think the way to access the real asset for the first player, neglecting that all other players may reference to a copy is just wrong in the first place.

The other solutions I previously proposed for maintaining the copy expose yet another public API to users. This exposes no other API to the users. Ideally, I would even prefer that this code would not exist, but I'm not seeing another way atm.

I think if we communicate clearly that PlayerInput actions disable project-wide actions at "startup", it's still better than maintaining the copy behavior and introducing a flag to users.

That might apply to the two cases, I agree. Thinking about future regressions/upcoming bugs I am not very sure if that's the best way.
As I see it we have two not ideal areas which caused the bug:

  • project wide actions being enabled by default
  • playerinput working inconsistent with copies

Now we did not really fix one of the root causes, so my gut feeling is that there are more bugs to come.

Project-wide actions behavior is to have all action maps enabled so that it's easier for users to get any kind of Input. On the other hand, PlayerInput works on a particular action map of an InputActionAsset so it should control the asset state.

@ritamerkl
Copy link
Collaborator

I understand the effort to not introduce new API or change behavior significantly though, and support that.
My biggest concern is just- the line is very thin between not introducing breaking changes everywhere and jumping from bug to bug. If we don't introduce changes I don't see us progress in the bigger scheme of making the system more reliable.

@ekcoh
Copy link
Collaborator

ekcoh commented Jun 3, 2025

Regarding

Instead of creating a copy of the asset, I propose a solution where the PlayerInput component using project-wide actions will disable all its action maps at startup. This allows PlayerInput to then select the appropriate action map to be enabled (either a default action map or some other one the user enables in their scripts).

This somehow needs to happen because the intended default behavior of ProjectWideActions is to have all actions enabled by default, which will cause problems with PlayerInput as it only selects the one that should be enabled.

Is my understanding correct that it essentially says that one cannot use PlayerInput and Project-wide Input Action Assets (in other places) at the same time right? Since PlayerInput acts as "manager of enabled action maps"?

/// <remarks>
/// This property is set to <c>true</c> by default.
/// </remarks>
internal static bool m_EnableActionsAfterEnterPlayMode = true;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is kept, it should be private.


[Category(TestCategory)]
[Test]
// Test that will make sure that a PlayerInput component using project wide actions will:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding an inline description of the test, but maybe put it inside Description(string) attribute instead?

@@ -55,7 +55,7 @@ public void PlayerInput_CanInstantiatePlayer()

Assert.That(player, Is.Not.Null);
Assert.That(player.playerIndex, Is.EqualTo(0));
Assert.That(player.actions.actionMaps.Count, Is.EqualTo(prefabPlayerInput.actions.actionMaps.Count));
Assert.That(player.actions, Is.SameAs(prefabPlayerInput.actions));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this maps to Object.ReferenceEquals, might be good to double check so its not by value

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into the docs, and it checks two references are the same 👍

@jfreire-unity
Copy link
Collaborator Author

jfreire-unity commented Jun 4, 2025

Regarding

Instead of creating a copy of the asset, I propose a solution where the PlayerInput component using project-wide actions will disable all its action maps at startup. This allows PlayerInput to then select the appropriate action map to be enabled (either a default action map or some other one the user enables in their scripts).

This somehow needs to happen because the intended default behavior of ProjectWideActions is to have all actions enabled by default, which will cause problems with PlayerInput as it only selects the one that should be enabled.

Is my understanding correct that it essentially says that one cannot use PlayerInput and Project-wide Input Action Assets (in other places) at the same time right? Since PlayerInput acts as the "manager of enabled action maps"?

Technically, you can. Since you can operate on both assets at runtime without restrictions. Project-wide actions asset is just a normal asset, that comes with all actions enabled by default, right? This default behavior of PWA is what clashes with PlayerInput initialization behavior since it tries to enable just a single action map (such as `PlayerInput.defaultActionMap) which is already enabled. But doesn't disable ALL the other ones.

It is still possible that PlayerInput, for an asset with "Player", "PlayerMenu", and "UI" action maps, acts only on the "Player" action map. But the user has some other "global logic" around other action maps, such as "UI", which are not necessarily used by PlayerInput. See test PlayerInput_CanUseSameActionsForUIInputModule (there's a use case where the UIInputModule is not necessarily attached to PlayerInput.uiInputModule, so the asset can still be controlled by other components.). I'm not saying this scales well for local multiplayer, and there are other ways to do the same, but it seems this can be a use case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants