Can the application to respond reactively to game.combat?
I tried this:
But it's not reactive.
I also tried:
And also tried with
game.combat.active
, which provides a boolean value, but still not reactively.9 Replies
That's not how things work.. I'm cooking up a starter resource for you though that should get things rolling.
You need to create a hook if TJS doesn't provide any helpers.
In order to get or use a reactive value it needs to be reactive from the beginning. Foundry inherently has 0 reactivity.
Otherwise best you can do is listen for any changes with hooks and modify your own reactive values.
I'm working on using TJSDocumentCollection / TJSDocument.
No hooks needed per se. Though this does bring up the v12 situation with the annoyance of
renderContext
vs action
in update data.
The demo code I'll provide uses both of those, but I'll look into making a change for TRL to provide a standardized action
.Neat
It's super frustrating that the document model is so lame. I have tested
TJSDocumentCollection
against macros. When a macro is created the DB operation
has render: true
thus TJSDocumentCollection
receives a notification when a macro is added / created.
When a combat is created from a click of the combat icon from the token HUD the DB operation includes render: false
and no notification is posted.
When an empty combat is created from the combat tracker itself hitting the +
icon render: true
is the DB operation thus TJSDocumentCollection
receives a callback on combat encounter creation.
The whole registering apps directly with documents & collections is so ghetto and is what really should be fixed / updated. It's a weak sauce implementation from the early days of Foundry that hasn't changed except for progressively altering data in the callbacks. It's not good programming.
Unfortunately TRL has only one way to receive callbacks from the document model and it's this antiquated and unevenly distributed mechanism that isn't the product of good quality design / implementation.
So yeah... You can create a reactive data source for the combats collection, but it's super hacky and does require the use of at least one hook due to the render: false
coming in from the token HUD initiation of a combat. The Combat
document itself manually invokes ui.combat.render()
when combatants are added which is the path taken when clicking on the token hud to add combatants.
The problem with creating some sort of standard resource like a reactive combat tracker for TRL is that any of this can be tweaked / changed for any Foundry version and likely is already for v13.
I thought there might be a problem in my code, but there is not. I also happened to be using the token HUD to start a combat the entire time in testing then spent ~2 hours debugging / following the path through Foundry to ensure it wasn't a bug in TJSDocumentCollection
.
Here is the code in TokenDocument that creates a combat with render: false
:
Search for getDocumentClass("Combat")
and you'll see that combats are created at 3 points.. Only the instance from the token HUD is render: false
.
In DocumentCollection
here is _onModifyContents
:
this.render
invokes render
on registered apps of the collection which is the only way to register for callbacks.
A better way of doing this is just making the documents & collections subscribable. There are many reasons why one would want to receive an update.
---
Heh.. Just venting above... I was hoping my demo code would have been clean by just using TJSDocumentCollection / TJSDocument alone and no hooks.
The combat tracker code is a mess IMHO. It's a patchwork implementation. Activation of a Combat document also uses render: false
, so makes it a bit nebulous on providing a clean reactive source.
It's possible, but I'd be handing you a very inefficient foot gun.
game.combat
queries the UI / CombatTracker as things go. Basically the "reactive" version of getting the actively viewed combat would have to check this every single time there was a change to any combat encounter for any reason. Not efficient.
OK... So... The problem is gnarly if one is approaching this from a non system dev situation... I assume this is for your own game system. What you can do is extend the CombatTracker class and override initialize
.
This does work... Not pretty...
What game.combat
does is invoke ui.combat?.viewed ?? null
.
----
There is really no clean way to do this otherwise from a generic / external way that doesn't involve a custom combat tracker. If this is for your game system then that is the best route.
The Foundry core implementation hard coded this path to the UI / combat tracker. TJSDocument / TJSDocumentCollection can't be used reliably because of the render: false
document updates.That works thank you and it's a lot cleaner than what I was doing which was subscribing and unsubscribing to hooks in my component's onMount.
I'm having an axiomatic problem within my combat-tracker.js extension however, which is that I can't find a way to access actor within it in a reactive way.
The above snippet you posted also wouldn't work well because you can create multiple combats on a scene.
Are you talking about embedded documents on the combat doc in regard to actors?
Not sure.
I'd be using this:
But I think I might be wrong and it is reactive, I think I'm having some race conditions with how I'm updating the actor
That you can use
TJSDocument
for and assign it the viewed combat.
Pseudocode ahead. This will work if there is an existing combat on load:
Don't write code like this and not unsubscribe / destroy the listeners when using the JS API. This demo did bring up an item on the TODO list. The added automatic sub / unsub to the underlying document can get in the way if all you want to do is monitor embedded collections. IE see the doc.subscribe(() => void 0);
... That is there to subscribe to the combat doc which feeds the embedded collection updates as well. I have to consider that the main document should potentially be subscribed to if there are any embedded collections created.
Basically TJSDocument / TJSDocumentCollection is awaiting a thorough refactor after the Svelte 5 transition. It's like version 1.1
.
So... Instead of using a store You can do something _LIKE this... A bit closer than the previous scratch demo above.
Alrighty.... Here is some tested code for you to try.. I found a bug in the Foundry CombatTracker. This prevents the usage of private variables / methods in an overridden initialize
implementation. See the queueMicrotask used.
In stress testing with multiple combats and switching between them I also found a bug in the dynamic reducer Map implementation which is how "reactive embedded collections" work. It only occurs when you only have just a sort
function applied and switch between documents (IE different combats). The demo code below does not have a sort function. I will have this fixed next TRL release. 100% test coverage didn't find that one, so glad I spent the time today to try and help you. I also refined TJSDocument / TJSDocumentCollection
allowing undefined
& null
to be used in set
to unset the tracked document and some refinements to the embedded API. I will follow up in this forum post after the next TRL release with more streamlined code example.
Yeah.. You can see Atro's response to the issue I filed. I don't consider Atro to be, well, as informed as one would hope per se. queueMicrotask is the appropriate solution in core. I'm also going to be evaluating areas in TRL where I've used setTimeout(() => {}, 0)
and swap to queueMicrotask
where applicable.
Just to make sure folks are clear. queueMicrotask
shouldn't be abused or is a replacement for next macrotask scheduling IE setTimeout(() => {}, 0)
. On review of the TRL codebase there were no reasons to change any of the macrotask scheduling to microtasks.
Before you consider using queueMicrotask
you should read the guide on microtasks