Skip to content

Allow Schema fields to be visible when their parent is visible#218

Merged
endel merged 11 commits intocolyseus:masterfrom
FTWinston:master
Apr 29, 2026
Merged

Allow Schema fields to be visible when their parent is visible#218
endel merged 11 commits intocolyseus:masterfrom
FTWinston:master

Conversation

@FTWinston
Copy link
Copy Markdown
Contributor

@FTWinston FTWinston commented Feb 24, 2026

Existing behaviour

When using @view with nested Schema classes, visibility can be unintuitive. Currently:

  1. If a Schema field is decorated with @view and added to a client’s view, that field becomes visible.
  2. Other nested Schema fields inside it do not become visible (unless they are also decorated with @view and explicitly added to the client's view.
  3. The exception is when nested objects are stored inside an ArraySchema (or another collection), whose items inherit their parent's visibility.

if there's a parent that's visible to a client:

@view() @type(Parent) parent: Parent;

Then a child within it will be included in the encoded state, but all of its fields will be undefined:

@type(Child) child: Child;

That is, unless that child is held in an ArraySchema:

@type([Child]) child: ArraySchema<Child>();

Placing the child inside an ArraySchema does propagate field visibility.

It seems as if ArraySchema items inherit visibility from the parent of the ArraySchema. I'm not certain whether that's intentional, but I am finding it helpful!

What this PR changes

This PR removes the restriction that child schemas will inherit their parent's visibility only if the parent is a collection. This means that visibility of fields on the Child class mentioned above behave the same, regardless of whether its contained in an ArraySchema or not:
if the parent is visible, the child is visible and its fields are encoded normally.

Motivation

This avoids the need to wrap single nested objects in ArraySchema purely to get visibility propagation.
I’m using this for several small nested types (e.g., a Cooldown timer object), and while my state could obviously be structured differently, this change means removes the need to occasionally "fight" against the view system.

Comment thread src/encoder/ChangeTree.ts Outdated
Comment thread package.json
"generate-test-11": "bin/schema-codegen test-external/MapSchemaMoveNullifyType.ts --namespace SchemaTest.MapSchemaMoveNullifyType --output ../colyseus-unity-sdk/Assets/Colyseus/Tests/Editor/ColyseusTests/Schema/MapSchemaMoveNullifyType",
"generate-test-12": "bin/schema-codegen test-external/ArraySchemaClear --namespace SchemaTest.ArraySchemaClear --output ../colyseus-unity-sdk/Assets/Colyseus/Tests/Editor/ColyseusTests/Schema/ArraySchemaClear",
"prepublishOnly": "npm run build"
"prepare": "npm run build"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Happy to undo this if this PR is going to be merged. I needed my fork to build on install so I can use it directly from github.

@endel
Copy link
Copy Markdown
Member

endel commented Feb 24, 2026

Wow, thanks for the PR @FTWinston, I will review it very soon. I'm afraid I wouldn't like to add more complexity to StateViews so if we can change the default behaviour to be as you would expect would be better. We are also considering moving away from decorators in the future (see #213).

@FTWinston
Copy link
Copy Markdown
Contributor Author

FTWinston commented Feb 24, 2026

Hey, no worries. If the default behaviour changed that would be awesome, but I didn't want to start with suggesting that 😅

If you have a schema in a state tree with no @view decorators on its ancestors, it is visible unless you decorate it with @view and don't add it to the client's view.

Once you're within a schema decorated with @view, I do think it would be great if you got the same behaviour by default. (If a schema is visible to a client, everything within it is visible to them unless it's also decorated with @view.)

I think that could be achieved by discarding my changes and just removing the && parentIsCollection condition when calculating this.isVisibilitySharedWithParent in the _checkFilteredByParent method of the ChangeTree class.

If you want to go with an approach like this, I'm happy to change this PR, or just close it if that's simpler.

@endel
Copy link
Copy Markdown
Member

endel commented Feb 25, 2026

I think what you proposed here is what makes more sense! I only see advantages :)

I think that could be achieved by discarding my changes and just removing the && parentIsCollection condition when calculating this.isVisibilitySharedWithParent in the _checkFilteredByParent method of the ChangeTree class.

Would you mind adapting the PR? 😇 I'd also suggest to move the tests to StateView.test.ts file 🙏

@FTWinston
Copy link
Copy Markdown
Contributor Author

Gladly, thanks for the tip

@FTWinston
Copy link
Copy Markdown
Contributor Author

Ok, there you have my favourite kind of PR: one that just removes a line of code.

@anaibol
Copy link
Copy Markdown

anaibol commented Apr 29, 2026

Hi @endel friendly bump on this, since the requested change has been applied (the one-line removal of parentIsCollection + tests moved to StateView.test.ts).

Sharing some real-world signal in case it helps: we're carrying this exact patch in production for an MMORPG on colyseus 0.17 + @colyseus/schema 5.0.3. Our Character schema lives inside a @view(AOI) MapSchema<Character> and holds several nested sub-schemas (appearance, status, plus owner-only attributes / stats / equipment gated with @view(OWNER)). Without this fix, those sub-schemas get encoded but never become visible to the relevant client — we'd see refId not found decode errors at high entity counts (50+ players in a single AoI). The diff in this PR resolves it cleanly; we have a regression test driving 20+ filtered entities each with an @view(OWNER) @type(SubSchema) field that's been green since we applied the patch.

Would love to drop the pnpm patch on the next 5.x release. Happy to share the regression test if it'd be a useful addition to StateView.test.ts.

@endel endel merged commit 9d1491b into colyseus:master Apr 29, 2026
endel added a commit that referenced this pull request Apr 29, 2026
@endel
Copy link
Copy Markdown
Member

endel commented Apr 29, 2026

Thanks for the heads-up @anaibol, and thank you so much for fixing the PR @FTWinston 👏 I forgot to merge it! More tests are always welcome if you can send another PR for this that'd be a good addition @anaibol 💙

Cheers!

endel added a commit that referenced this pull request Apr 30, 2026
Brings 4.0.21 (PR #218) and prior 4.x fixes into 5.0.

Conflicts resolved:
- package.json: keep version 5.0.3 (HEAD).
- CHANGELOG.md: keep 5.0.x-only changelog (HEAD).
- .github/workflows/npm-publish.yml: keep 5.0's branch-aware npm tag /
  prerelease logic (master/@latest, 5.0/@next).
- tsconfig.test.json: keep HEAD's empty exclude list.
- src/types/HelperTypes.ts: keep HEAD's ToJSON (already incorporates the
  #222 fix more carefully via ToJSONRequiredKeys/ToJSONOptionalKeys plus
  .optional() support).
- src/encoder/ChangeTree.ts: drop master's old inline parent-chain +
  _checkFilteredByParent block — already refactored on 5.0 into
  changeTree/parentChain.ts and changeTree/inheritedFlags.ts.
- test/StateView.test.ts: keep HEAD's "isNew fast path" suite AND port
  master's three nested-Schema visibility tests under a new
  "nested Schema parent visibility (#218)" describe.

#218 functional fix ported into 5.0:
- src/encoder/changeTree/inheritedFlags.ts: drop the `parentIsCollection`
  constraint from `isVisibilitySharedWithParent` so a nested Schema field
  inherits visibility from a @view-gated parent without needing to be
  wrapped in an ArraySchema. Matches the new test expectations.

All 644 tests pass.

Assisted-by: Claude Opus 4.7
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