Skip to content

Commit 9f7fe9c

Browse files
authored
Merge pull request #298 from AlchemyViewer/rye/local-assets-review-fixes
Local Assets: post-merge defect-review fixes
2 parents 88b6187 + 1b2fe34 commit 9f7fe9c

21 files changed

Lines changed: 927 additions & 264 deletions

indra/llcharacter/llcharacter.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ void LLCharacter::removeMotion( const LLUUID& id )
109109
mMotionController.removeMotion(id);
110110
}
111111

112+
//-----------------------------------------------------------------------------
113+
// purgeMotionInstances()
114+
//-----------------------------------------------------------------------------
115+
void LLCharacter::purgeMotionInstances( const LLUUID& id )
116+
{
117+
mMotionController.purgeMotionInstances(id);
118+
}
119+
112120
//-----------------------------------------------------------------------------
113121
// findMotion()
114122
//-----------------------------------------------------------------------------

indra/llcharacter/llcharacter.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ class LLCharacter
133133

134134
void removeMotion( const LLUUID& id );
135135

136+
// removes ALL instances of a motion -- the canonical one AND any deprecated
137+
// duplicates still easing out -- for when the motion's backing keyframe
138+
// data is about to be destroyed (see LLMotionController::purgeMotionInstances)
139+
void purgeMotionInstances( const LLUUID& id );
140+
136141
// returns an instance of a registered motion, creating one if necessary
137142
LLMotion* createMotion( const LLUUID &id );
138143

indra/llcharacter/llmotioncontroller.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,27 @@ void LLMotionController::removeMotionInstance(LLMotion* motionp)
335335
}
336336
}
337337

338+
//-----------------------------------------------------------------------------
339+
// purgeMotionInstances()
340+
//-----------------------------------------------------------------------------
341+
void LLMotionController::purgeMotionInstances(const LLUUID& id)
342+
{
343+
removeMotion(id);
344+
for (motion_set_t::iterator iter = mDeprecatedMotions.begin();
345+
iter != mDeprecatedMotions.end(); )
346+
{
347+
motion_set_t::iterator cur_iter = iter++;
348+
LLMotion* motionp = *cur_iter;
349+
if (motionp->getID() == id)
350+
{
351+
// The same complete excision deactivateMotionInstance() applies to
352+
// deprecated motions; removeMotionInstance() deactivates if needed.
353+
removeMotionInstance(motionp); // does not touch mDeprecatedMotions
354+
mDeprecatedMotions.erase(cur_iter);
355+
}
356+
}
357+
}
358+
338359
//-----------------------------------------------------------------------------
339360
// createMotion()
340361
//-----------------------------------------------------------------------------

indra/llcharacter/llmotioncontroller.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ class LLMotionController
122122
// returns true if successful
123123
bool stopMotionLocally( const LLUUID &id, bool stop_immediate );
124124

125+
// immediately deactivates and deletes EVERY instance of a motion id --
126+
// the canonical instance and any deprecated duplicates still easing out.
127+
// For use when the motion's backing data is about to be destroyed (e.g. a
128+
// locally previewed animation whose globally cached keyframe data is purged
129+
// on live reload/removal): removeMotion() only reaches the canonical
130+
// instance, and a deprecated instance left easing out keeps dereferencing
131+
// the freed data every frame.
132+
void purgeMotionInstances( const LLUUID& id );
133+
125134
// Move motions from loading to loaded
126135
void updateLoadingMotions();
127136

indra/llprimitive/llmodelloader.cpp

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "llsdserialize.h"
3333
#include "lljoint.h"
3434
#include "llcallbacklist.h"
35+
#include "workqueue.h"
3536

3637
#include "llmatrix4a.h"
3738
#include <boost/bind.hpp>
@@ -192,9 +193,24 @@ void LLModelLoader::run()
192193
setLoadState(ERROR_PARSING);
193194
}
194195

195-
// todo: we are inside of a thread, push this into main thread worker,
196-
// not into doOnIdleOneTime that laks tread safety
197-
doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback,this));
196+
// Hand completion back to the main thread. run() executes on the loader's
197+
// worker thread, and the LLCallbackList behind doOnIdleOneTime() is not
198+
// thread-safe -- post through the main loop's WorkQueue instead.
199+
LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
200+
if (main_queue)
201+
{
202+
main_queue->post(boost::bind(&LLModelLoader::loadModelCallback, this));
203+
}
204+
else
205+
{
206+
// No main-loop queue registered. The viewer always has one (a global,
207+
// gMainloopWork), so this branch can only run in a binary with no main
208+
// loop concurrently using LLCallbackList -- where the idle list is the
209+
// only delivery left. loadModelCallback() cannot be invoked inline
210+
// here: it blocks until this thread is stopped and then deletes the
211+
// loader, which would deadlock run().
212+
doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback, this));
213+
}
198214
}
199215

200216
// static

indra/newview/llfloaterlocalassets.cpp

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -402,16 +402,16 @@ void LLPanelLocalAssetBase::onRemoveBtn()
402402

403403
for (const auto& entry : selected)
404404
{
405-
if (!entry.first.empty())
406-
{
407-
LLLocalAssetPaths::getInstance()->removePath(assetType(), entry.first); // forget the path
408-
}
409-
// Unload every decoded unit backing this row. For a multi-material glTF file
410-
// that's all of the file's material units, not just the selected row.
405+
// Unload every decoded unit backing this row FIRST and forget the path
406+
// only once they are all gone. delUnit() fires the manager signals
407+
// synchronously, and the add-only LLLocalAssetPaths::onUnitsChanged()
408+
// re-records any path a still-loaded unit reports -- removePath() up
409+
// front would be undone by the very first delUnit (mesh teardown, or a
410+
// multi-material glTF file's surviving sibling units).
411411
std::vector<LLUUID> ids;
412412
if (!entry.first.empty())
413413
{
414-
unitsForPath(entry.first, ids);
414+
unitsForPath(entry.first, ids); // all of the file's units, not just the row
415415
}
416416
if (ids.empty() && entry.second.notNull())
417417
{
@@ -421,6 +421,10 @@ void LLPanelLocalAssetBase::onRemoveBtn()
421421
{
422422
delUnit(id); // unload the decoded unit (fires the manager signal)
423423
}
424+
if (!entry.first.empty())
425+
{
426+
LLLocalAssetPaths::getInstance()->removePath(assetType(), entry.first); // forget the path
427+
}
424428
}
425429
refresh(); // removePath() alone (undecoded rows) doesn't fire a manager signal
426430
}
@@ -723,11 +727,15 @@ void LLPanelLocalMesh::onRez()
723727
doSpawn(id); // always rez a new copy
724728
return;
725729
}
726-
// Undecoded: load it and rez once it finishes (addAndSpawn handles the async load).
730+
// Undecoded: load it and rez once it finishes (addAndSpawn handles the async
731+
// load). Decode with the joint-position flag the artist saved for this file,
732+
// like loadPath -- defaulting it would also make onUnitsChanged erase the
733+
// saved flag.
727734
const std::string path = getSelectedPath();
728735
if (!path.empty())
729736
{
730-
LLLocalMeshMgr::getInstance()->addAndSpawn(std::vector<std::string>(1, path));
737+
LLLocalMeshMgr::getInstance()->addAndSpawn(std::vector<std::string>(1, path),
738+
LLLocalAssetPaths::getInstance()->getMeshJoints(path));
731739
}
732740
}
733741

@@ -740,11 +748,12 @@ void LLPanelLocalMesh::onAttach()
740748
return;
741749
}
742750
// Undecoded row: load it and attach once it finishes loading (mirrors how Rez
743-
// handles an undecoded row via addAndSpawn).
751+
// handles an undecoded row via addAndSpawn), honoring the saved joint flag.
744752
const std::string path = getSelectedPath();
745753
if (!path.empty())
746754
{
747-
LLLocalMeshMgr::getInstance()->addAndAttach(path, getComboAttachPoint());
755+
LLLocalMeshMgr::getInstance()->addAndAttach(path, getComboAttachPoint(),
756+
LLLocalAssetPaths::getInstance()->getMeshJoints(path));
748757
}
749758
}
750759

@@ -837,9 +846,13 @@ void LLPanelLocalMesh::doRemove(const LLUUID& tracking_id)
837846
{
838847
if (tracking_id.notNull())
839848
{
840-
LLLocalAssetPaths::getInstance()->removePath(LLLocalAssetPaths::TYPE_MESH,
841-
pathForUnit(tracking_id));
849+
// Resolve the path before the unit dies, but forget it only AFTER
850+
// delUnit: the units-changed listeners fire during teardown and the
851+
// add-only LLLocalAssetPaths::onUnitsChanged would re-record a path
852+
// removed up front (see onRemoveBtn).
853+
const std::string path = pathForUnit(tracking_id);
842854
delUnit(tracking_id); // units-changed signal -> refresh()
855+
LLLocalAssetPaths::getInstance()->removePath(LLLocalAssetPaths::TYPE_MESH, path);
843856
}
844857
}
845858

@@ -1217,7 +1230,9 @@ class LLPanelLocalTexture final : public LLPanelLocalApplyAsset
12171230
}
12181231
LLUUID unitForPath(const std::string& path) const override
12191232
{
1220-
return LLLocalBitmapMgr::getInstance()->getUnitID(path);
1233+
// User units only: a mesh-owned import of the same file is a distinct,
1234+
// read-only unit this tab must neither claim as loaded nor delete.
1235+
return LLLocalBitmapMgr::getInstance()->getUnitID(path, /*mesh_owned=*/false);
12211236
}
12221237
std::string pathForUnit(const LLUUID& tracking_id) const override
12231238
{
@@ -1271,8 +1286,9 @@ class LLPanelLocalMaterial final : public LLPanelLocalApplyAsset
12711286
}
12721287
LLUUID unitForPath(const std::string& path) const override
12731288
{
1274-
// A file holds >= 1 material; treat it as loaded if its first material is.
1275-
return LLLocalGLTFMaterialMgr::getInstance()->getUnitID(path, 0);
1289+
// A file holds >= 1 material; treat it as loaded if its first USER material
1290+
// is (a mesh-owned import of the same file is a distinct, read-only set).
1291+
return LLLocalGLTFMaterialMgr::getInstance()->getUnitID(path, 0, /*mesh_owned=*/false);
12761292
}
12771293
std::string pathForUnit(const LLUUID& tracking_id) const override
12781294
{
@@ -1283,8 +1299,10 @@ class LLPanelLocalMaterial final : public LLPanelLocalApplyAsset
12831299
}
12841300
void unitsForPath(const std::string& path, std::vector<LLUUID>& out) const override
12851301
{
1286-
// One .gltf/.glb can decode to several material units; remove them all.
1287-
LLLocalGLTFMaterialMgr::getInstance()->getTrackingIDs(path, out);
1302+
// One .gltf/.glb can decode to several material units; act on all of the
1303+
// USER's. Mesh-owned imports of the same file belong to a loaded mesh and
1304+
// must not be deleted from this tab.
1305+
LLLocalGLTFMaterialMgr::getInstance()->getTrackingIDs(path, out, /*mesh_owned=*/false);
12881306
}
12891307
std::string iconName() const override
12901308
{

indra/newview/lllocalanim.cpp

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include "lllocalanim.h"
2828

29+
#include "fsyspath.h" // UTF-8-safe std::filesystem paths on Windows
2930
#include "llbvhloader.h"
3031
#include "llcharacter.h" // LLCharacter::sInstances (resolve avatar by id)
3132
#include "lldatapacker.h"
@@ -210,7 +211,7 @@ LLUUID LLLocalAnimMgr::loadAnim(const std::string& filename)
210211
if (!alias_deferred)
211212
{
212213
std::error_code ec;
213-
anim.mLastModified = std::filesystem::last_write_time(filename, ec);
214+
anim.mLastModified = std::filesystem::last_write_time(fsyspath(filename), ec);
214215
}
215216

216217
const size_t bytes = anim.mData.size();
@@ -253,23 +254,29 @@ void LLLocalAnimMgr::delUnit(LLUUID tracking_id)
253254
return;
254255
}
255256

256-
// Stop it wherever it's playing, purge the cached motion, and drop the play map.
257+
// Drop the play-map entries, then stop and delete the motion EVERYWHERE
258+
// before freeing its cached keyframe data. The sweep covers instances
259+
// mPlaying no longer tracks -- replaced/stopped ones still easing out and
260+
// deprecated duplicates -- which would otherwise keep dereferencing the
261+
// freed JointMotionList every frame.
257262
for (auto pit = mPlaying.begin(); pit != mPlaying.end(); )
258263
{
259264
if (pit->second == tracking_id)
260265
{
261-
if (LLVOAvatar* av = resolve_avatar(pit->first))
262-
{
263-
av->stopMotion(tracking_id, true);
264-
av->removeMotion(tracking_id);
265-
}
266266
pit = mPlaying.erase(pit);
267267
}
268268
else
269269
{
270270
++pit;
271271
}
272272
}
273+
for (LLCharacter* character : LLCharacter::sInstances)
274+
{
275+
if (character)
276+
{
277+
character->purgeMotionInstances(tracking_id);
278+
}
279+
}
273280

274281
LLKeyframeDataCache::removeKeyframeData(tracking_id);
275282
mAnims.erase(iter);
@@ -354,24 +361,27 @@ bool LLLocalAnimMgr::reapplyToAvatar(const LLUUID& av_id, const LLUUID& anim_id)
354361
return false;
355362
}
356363

357-
// Purge the stale parsed motion instance so createMotion() yields a fresh one
358-
// that deserializes the new bytes. The id is keyed per-avatar, so the old motion
359-
// must go before a new one can take its place.
360-
av->stopMotion(anim_id, true);
361-
av->removeMotion(anim_id);
362-
364+
// The caller (doUpdates) already purged the stale motion instances and the
365+
// cached keyframe data, so createMotion() yields a fresh motion. The first
366+
// avatar's deserialize() rebuilds and globally re-caches the new data;
367+
// later avatars must NOT deserialize again -- every deserialize builds
368+
// another JointMotionList and addKeyframeData() overwrites the cache slot
369+
// without freeing it, leaking one list per extra avatar per reload.
363370
LLKeyframeMotion* motionp = dynamic_cast<LLKeyframeMotion*>(av->createMotion(anim_id));
364371
if (!motionp)
365372
{
366373
return false;
367374
}
368-
LLDataPackerBinaryBuffer dp(iter->second.mData.data(), (S32)iter->second.mData.size());
369-
if (motionp->deserialize(dp, anim_id, false))
375+
if (!LLKeyframeDataCache::getKeyframeData(anim_id))
370376
{
371-
av->startMotion(anim_id);
372-
return true;
377+
LLDataPackerBinaryBuffer dp(iter->second.mData.data(), (S32)iter->second.mData.size());
378+
if (!motionp->deserialize(dp, anim_id, false))
379+
{
380+
return false;
381+
}
373382
}
374-
return false;
383+
av->startMotion(anim_id);
384+
return true;
375385
}
376386

377387
void LLLocalAnimMgr::doUpdates()
@@ -385,7 +395,7 @@ void LLLocalAnimMgr::doUpdates()
385395
LocalAnim& anim = entry.second;
386396

387397
std::error_code ec;
388-
const auto mtime = std::filesystem::last_write_time(anim.mFilename, ec);
398+
const auto mtime = std::filesystem::last_write_time(fsyspath(anim.mFilename), ec);
389399
if (ec || mtime == anim.mLastModified)
390400
{
391401
continue;
@@ -398,16 +408,44 @@ void LLLocalAnimMgr::doUpdates()
398408
}
399409
anim.mData = std::move(fresh);
400410

401-
// Refresh the cached keyframe data so the next play uses the new bytes,
402-
// and re-apply live to any avatar currently playing this id.
403-
LLKeyframeDataCache::removeKeyframeData(id);
404-
bool reapply_ok = true;
405-
for (const auto& play : mPlaying)
411+
// Collect the avatars to re-apply to, pruning entries whose avatar is
412+
// gone -- counting a dead avatar as a reapply failure would leave the
413+
// mtime unconsumed and re-parse + restart the anim on every live avatar
414+
// every heartbeat, forever.
415+
std::vector<LLUUID> replaying;
416+
for (auto pit = mPlaying.begin(); pit != mPlaying.end(); )
406417
{
407-
if (play.second == id)
418+
if (pit->second == id)
408419
{
409-
reapply_ok = reapplyToAvatar(play.first, id) && reapply_ok;
420+
if (!resolve_avatar(pit->first))
421+
{
422+
pit = mPlaying.erase(pit);
423+
continue;
424+
}
425+
replaying.push_back(pit->first);
410426
}
427+
++pit;
428+
}
429+
430+
// Stop and delete every live instance BEFORE freeing the cached keyframe
431+
// data: stopping dereferences the JointMotionList (setStopTime, constraint
432+
// teardown), and instances mPlaying no longer tracks (replaced/stopped
433+
// ones still easing out, deprecated duplicates) would keep reading the
434+
// freed data every frame.
435+
for (LLCharacter* character : LLCharacter::sInstances)
436+
{
437+
if (character)
438+
{
439+
character->purgeMotionInstances(id);
440+
}
441+
}
442+
LLKeyframeDataCache::removeKeyframeData(id);
443+
444+
// Re-apply live to any avatar currently playing this id.
445+
bool reapply_ok = true;
446+
for (const LLUUID& av_id : replaying)
447+
{
448+
reapply_ok = reapplyToAvatar(av_id, id) && reapply_ok;
411449
}
412450

413451
// Consume the mtime only once the swap is fully live AND the decode used joint

0 commit comments

Comments
 (0)