Skip to content
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

docs: add shared state documentation #699

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions docs/cookbook/shared-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
title: Shared State
description: How to use a reactive composable to share your objects across component files.
author: /
thumbnail: /recipes/animations.png
difficulty: 0
---

# Shared State in TresJS

This guide will help you get started with shared state in TresJS by building a simple scene with a cube that can be shared across component files.

<StackBlitzEmbed project-id="tresjs-minimal-reproduction-rycc4j" />
Copy link
Member

Choose a reason for hiding this comment

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

Hi @whitespacecode since the example doesn't rely on third party libs, can we replace this with a Tresjs repl playground https://play.tresjs.org/ ?

You can find an example on how here

<SandboxDemo url="https://play.tresjs.org/#eNqVVt1y2jwQfRUN30WSKdimhLbjL3Qo9GfaadpM4K7uhbAXUGpLGkn8pJm8e1eSDXZCMmRCGGv37NHZ1XrFXWuqQH+QMlivoBW3LnSqmDREg1lJklO+GCQto5PW+4SzQgplyB3RS5rnYnMNc3JP5koU5ASjT/6vQSzrmPI11W2y0nANPAP1XQhZBQwNIm50mArVjPypZsyMBTdK5HrHv4Mz4EboRsSIapZOljQTm0sq22Ry/WU0FrlQE0lTaJMfYio4oEsyvtgxmqUCOEl4wlPBtSGLnAzIXcIJSXOgyhHE5OS/d68/jsb9k7b1YOK4iY6JUStwFprLJY3JnObaGzwEN5veSogfarMIsTJyhRlWAuOHgi3I7BXHzQTQfb9XPRNbewyD2pmcnu3dd0RwW3XMetA8B4/y3tPTMzJ475Nn81PPGaxpvoIzZ6xbAiUMNUzw4Ja8GpAoiLoWgpruHWXCL0LfRNgyuDBQyJwawBUhF/u+IOvOjPEM22uRJy2ywWex6Wj21yMR2+yEsDJbiitQWkJq2BrGtABFSSyFZlYWEv7qt8nbwH/9Ru54LtZoPu/bZ+oCcdm1K45Hjc9R4FZzt+hGUYSrxoaXoJfNPTqv2wQ/kdugqol1RG1ySc0yuPrqvSVNlTye5BcQBRh1i2LUQtuYbpt0reCeZas2rm09FYIjKShGc5LaVsGosjXrUsMq4JF2BXMM8QeJESnVpuN7tZkWqrefR7pHYntAttVcfb1I+vln+3ec9LrWplisvz2Gx2oncglqX+ejZX0ejaLe6NiKpoD991QVO71DzdEpW4OErnkOab/CqXuoRRC8/3+i2BNDeUZV9jiz+Vv791Rmtdw+FDM7Y7+zxdKQmHEDHPO6LV+YxkvxkWENbGY09/Dnumr3rhym9HL8aEDDRVibG612yw/7TkFlcKMFx5vKDaakdOAFFfv5ZW31u8U6ktbSGKnjMEwzjvEZ5GytAg4m5LII6/BhL+gHUZgxbUJrRnTSchO5QexvoZdw+wikf1OnL83NXcwG6B+JTXAE/w47PA9wiJXMlTEomI2pc9tb7xheixsiY/8d6n0FuqiXAW97vEyOrm8NPuxGrsA47WEbFM3qljhsIAXZC4h9wHPUCOxkULAjSCuoTf48eBPmbFanrO467Emj8ZKds8WDjkxFIVkO6qe03d/sTHdHf3O23U8IF7OE9M8B+43eeslX2Cyg1lju/VHiZADj3Z8mP2CLzztnIbJVXh7OE85r0CJfWY0eNlrxDGXXcE7tV/eC4Q+Pqf60dW9umVRDqMFfO876q5pJu17zht+ucA7vjmP8TJX2mfWC3q7g9/8AWlN6bg==" />

Copy link
Author

Choose a reason for hiding this comment

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

Hi @alvarosabu
I tried with the playground and i'm getting this error:
'get' on proxy: property 'modelViewMatrix' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '#<cr>' but got '#<cr>')
I tried a few things but i'm keep getting errors on the playground.

Copy link
Author

@whitespacecode whitespacecode Nov 12, 2024

Choose a reason for hiding this comment

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


## Creating a State Composable

First, we'll create a composable to store the objects.

```ts
//composables/state.ts
import { reactive, toRefs } from "vue";

const state = reactive({
mesh: null,
//you can add more objects here
})

export function useState() {
return {
...toRefs(state)
Copy link
Contributor

Choose a reason for hiding this comment

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

We should slim down the reactivity here.

Feel free to point out if I'm misunderstanding, but I believe Ref<state.mesh> will be deeply reactive. Since a single attached THREE.Mesh contains the entire THREE.Scene graph (+ materials, geometries, and so on), that normally creates a lot of reactivity that goes unused.

It's fine if users want to use Ref<Mesh> in their own code, but we have run into concrete performance problems in the past even in relatively modest scenes. So as an official recipe, I think it would be best to refactor.

Maybe one or both of:

  • use shallow* alternatives
  • use slimmer data, i.e. state.isMeshVisible = true

@alvarosabu Thoughts here?

Copy link
Member

Choose a reason for hiding this comment

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

That's correct, better to use shallowRefs or shallowReactive. I wonder if toRefs automatically use shallowRef it when the state is shallowReactive 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if toRefs automatically use shallowRef it when the state is shallowReactive

In the Vue docs, the type indicates that Ref values are returned from toRefs.

https://vuejs.org/api/reactivity-utilities.html#torefs

Copy link
Author

@whitespacecode whitespacecode Nov 12, 2024

Choose a reason for hiding this comment

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

@andretchen0
Yes please feel free to change the .md file.
Most of the code is stuff i gathered from discord or by using the stackblitz of alvarosabu

}
}
```

## Setting the object to the state

Next, we'll assign an object to the state and include it in a subcomponent where we can access and use it.

```vue
//Parent component
whitespacecode marked this conversation as resolved.
Show resolved Hide resolved
<script setup lang="ts">
import { BoxGeometry, Mesh, MeshNormalMaterial } from 'three';

import SubComponent from './SubComponent.vue';
import { useState } from '../composables/state';

const { mesh } = useState();

//assinging the object to the state
mesh.value = new Mesh(new BoxGeometry(), new MeshNormalMaterial());
</script>

<template>
<TresPerspectiveCamera />

<SubComponent />
</template>
```

## Using the object in other components

With the mesh assigned to the reactive state, it's available throughout your project.

```vue
//Subcomponent.vue
<script setup lang="ts">
import { useState } from '../composables/state';
const { mesh, ground } = useState();

console.log('ground:', ground);
</script>

<template>
<primitive v-if="mesh" :object="mesh" />
</template>
```

## Using TresMesh components

You can also add TresMesh components to the reactive state. Here, we'll use a reference and assign it to the state when mounted.

```vue
//Parent component
whitespacecode marked this conversation as resolved.
Show resolved Hide resolved
<script setup lang="ts">
import { BoxGeometry, Mesh, MeshNormalMaterial } from 'three';

import SubComponent from './SubComponent.vue';
import { useState } from '../composables/state';

const { mesh } = useState();

//reference the object
const exampleRef = ref(null);

//assinging the object to the state when mounted
onMounted(() => {
mesh.value = exampleRef.value;
});
</script>

<template>
<TresPerspectiveCamera />

<TresMesh ref="exampleRef" :position="[0, 0, 0]" cast-shadow>
<TresBoxGeometry :args="[1.5, 1.5, 1.5]" />
<TresMeshToonMaterial color="#4F4F4F" />
</TresMesh>

<SubComponent />
</template>
```

With these steps, you can easily manage and share objects across different components in your Vue 3 project using a reactive composable.