Jump to content

[Idea] Cell scan framework


Monoman1

Recommended Posts

Posted

Seems it's largely a agreed that cloak scripts add a great deal of engine strain but with so many excellent mods having no other method of implementation is there a need now for a cell scan framework?

 

A single mod to continuously scan cells and arrange data in external storage for other mods to access. Is this possible? Would it be beneficial?

 

Maybe other mods could register with the framework what type of object it wants scanned?

 

I know it wouldn't be the most 'glamorous' mod to work on.

Posted

I made a script, thanks to Ashal, in AFS, for Red riding Hood.

You can ask her to her to scan a cell : if there is an appropriate NPC in the cell, she will talk to him, and bring him to you.

 

Beware with constant scans : it can overload your paryrus. Slaverun has this problem, which made this mod very unstable.

Posted

Seems it's largely a agreed that cloak scripts add a great deal of engine strain but with so many excellent mods having no other method of implementation is there a need now for a cell scan framework?

 

A single mod to continuously scan cells and arrange data in external storage for other mods to access. Is this possible? Would it be beneficial?

 

Maybe other mods could register with the framework what type of object it wants scanned?

 

I know it wouldn't be the most 'glamorous' mod to work on.

 

I believe something like this has been discussed for the FO4 framework and I also think this has been implemented into SexOut for sometime.

 

Yes I agree a single scanner for mods to access is certainly something that would be beneficial it would be a lot less strain to have one mod doing all the scanning then having several mods with there own methods scanning at the same time.

 

I'm sure it is possible to make but getting someone at this point to make it may be difficult and unlike the SexOut mods skyrim modders don't seem to be as tight of a group, everyone seems to be kind of doing there own thing so getting everyone on board to use it if it exist may prove to be difficult as well.

Posted

This should definitely be implemented as C plugin to SKSE and providing results with native papyrus functions to keep the script engine lag minimal. Something like plugin will rescan loaded cells every X milliseconds and then saves the data, so when the scripts are asking information with functions it's already precached. Problem is though that this moves the lag from script engine to FPS. Shouldn't be too bad though if the scripts specify which types of objects they are searching and what the maximum interval should be.

 

Edit: I figured out a way to do this using C plugin, if there's enough interest I can make the mod.

Posted

I will defer to those with more knowledge of the underlying systems but I honestly don't think cloak spells are a serious problem and I would have to see some test data that shows cell scanning is significantly less intensive.

 

I suspect that cloak spells have a bad name not because of what they do but what they are used for. What they do is return a set of actors or objects within a given radius that match certain conditions. What they are typically used for is attaching scripts to those actors or objects which then start collecting instance data, ticking updates and firing events. These attached scripts are probably the real issue and using a cell scan to find actors to attach them to will not change their performance (or lack thereof).

 

What would probably be more useful than another framework dependency are some guidelines for modders on what should happen in attached scripts, how to streamline them, how to avoid the overloading and errors that cause players' games to start chugging.

Posted

I agree with you on cloak spells, I use them myself in one of my scripts and never had a problem. Only downside is that they can only be used to find actors?

 

Iterating cell objects is really slow because of the amount of stuff you have to do within papyrus itself and the way SKSE implements the GetNthRef function is a nightmare. It doesn't even lock the cell object list like game does which could lead to crashes. Not to mention the performance:

 

UInt32 numMatching = 0;
UInt32 numRefs = thisCell->objectList.count;
for (UInt32 n = 0; n < numRefs; ++n)
{
    thisCell->objectList.GetNthItem(n, pRef);
    if (pRef && (pRef->formType == formType ||pRef->baseForm->formType == formType))
    {
        if (numMatching++ == index)
            return pRef;
    }
}

This is just one iteration of GetNthRef, and you will do this X total amount or until you find your object. Also it's completely possible that you will not find your object even if its in the list because this papyrus script might not complete before the object list has been changed. And this is just one cell, what if you wanted to search all nearby loaded cells? :)

Posted

I agree with you on cloak spells, I use them myself in one of my scripts and never had a problem. Only downside is that they can only be used to find actors?

 

Iterating cell objects is really slow because of the amount of stuff you have to do within papyrus itself and the way SKSE implements the GetNthRef function is a nightmare. It doesn't even lock the cell object list like game does which could lead to crashes. Not to mention the performance:

 

This is just one iteration of GetNthRef, and you will do this X total amount or until you find your object. Also it's completely possible that you will not find your object even if its in the list because this papyrus script might not complete before the object list has been changed. And this is just one cell, what if you wanted to search all nearby loaded cells? :)

 

Current beta version of PapyrusUtil I have in sexlab adds functions to do this already, though I haven't done much testing with them beyond just making sure they work.

Actor[] function ScanCellActors(ObjectReference CenterOn, float radius = 5000.0, Keyword HasKeyword = none) global native
ObjectReference[] function ScanCellObjects(int formType, ObjectReference CenterOn, float radius = 5000.0, Keyword HasKeyword = none) global native
Posted

Can I see source?

VMResultArray<Actor*> ScanCellActors(StaticFunctionTag* base, TESObjectREFR* CenterObj, float SearchRadius, BGSKeyword* FindKeyword) {
	VMResultArray<Actor*> output;
	if (CenterObj != NULL) {
		_MESSAGE("Cell scanning from form: %lu", CenterObj->formID);
		TESObjectCELL* Cell = CenterObj->parentCell;
		if (Cell) {
			tArray<TESObjectREFR*> objList = Cell->objectList;
			UInt32 count = objList.count;
			_MESSAGE("\tcell item count: %lu", count);
			for (UInt32 idx = 0; idx < count; ++idx) {
				TESObjectREFR* ObjRef = objList[idx];
				Actor *ActorRef = ObjRef != NULL ? DYNAMIC_CAST(ObjRef, TESObjectREFR, Actor) : NULL;
				if (ActorRef == NULL || ActorRef->IsDead(1)) continue;
				else if (SearchRadius > 0.0f && !IsWithinRadius(CenterObj, ActorRef, SearchRadius)) continue;
				else if (FindKeyword && !HasKeyword(ActorRef, FindKeyword)) continue;
				else output.push_back(ActorRef);
			}
		}
		_MESSAGE("Cell scanning found: %d", (int)output.size());
	}
	return output;
}

VMResultArray<TESObjectREFR*> ScanCellObjects(StaticFunctionTag* base, UInt32 FormType, TESObjectREFR* CenterObj, float SearchRadius, BGSKeyword* FindKeyword) {
	VMResultArray<TESObjectREFR*> output;
	if (CenterObj != NULL) {
		_MESSAGE("Cell scanning from form: %lu", CenterObj->formID);
		TESObjectCELL* Cell = CenterObj->parentCell;
		if (Cell) {
			tArray<TESObjectREFR*> objList = Cell->objectList;
			UInt32 count = objList.count;
			_MESSAGE("\tcell item count: %lu", count);
			for (UInt32 idx = 0; idx < count; ++idx) {
				TESObjectREFR* ObjRef = objList[idx];
				if (ObjRef == NULL || ObjRef->baseForm->GetFormType() != FormType) continue;
				else if (SearchRadius > 0.0f && !IsWithinRadius(CenterObj, ObjRef, SearchRadius)) continue;
				else if (FindKeyword && !HasKeyword(ObjRef, FindKeyword)) continue;
				else output.push_back(ObjRef);
			}
		}
		_MESSAGE("Cell scanning found: %d", (int)output.size());
	}
	return output;
}
Posted

Ah same as SKSE, I think this may cause crash because the object list is not locked. You create a copy of array before iterating but this doesn't help because it points to data with a pointer internally.

 

Some random game function as reference:

 

void TESObjectCELL::unk_4C25D0(TESObjectCELL *this)
{
  unsigned int * cellLock = &this->cellRefLock;
  f_Unknown_RefLock_Enter_401710((volatile LONG *)&this->cellRefLock, "TESObjectCELL::CellRefLockEnter()");
  for ( int i = this->objectList.count - 1; i >= 0; --i )
  {
    TESObjectREFR * objRef = this->objectList.entries[i];
    if ( objRef )
    {
      InterlockedIncrement(&objRef->handleRefObject.refCount);
      if ( TESObjectREFR::unk_CanHaveSound_4D6220(objRef) )
      {
        bool v5 = TESObjectREFR::unk_4D6280(objRef);
        TESObjectREFR::unk_Sound_4DFE80(objRef, v5);
      }
      BSHandleRefObject * objHandle = &objRef->handleRefObject;
      if ( !(InterlockedDecrement(&objHandle->refCount) & 0x3FF) )
        (*((void (__thiscall **)(_DWORD))objHandle->vtable + 1))(objHandle);
    }
  }
  bool v7 = cellLock[1]-- == 1;
  if ( v7 )
    InterlockedCompareExchange((volatile LONG *)cellLock, 0, *cellLock);
}

 

It's used the same way everywhere.

Posted

Lucky game had helper functions for locking and unlocking :P I can't test right now my computer blue screens today when I open any hardware accelerated application, if you have time try something like this:

 

Definition:

 

 

struct CellLocker
{
	CellLocker(TESObjectCELL* cell)
	{
		locked = cell;
		if (cell != NULL)
		{
			static int _lockCellEnterHelper = 0x4C0180;
			_asm
			{
				pushad
					pushfd

					mov ecx, cell
					call _lockCellEnterHelper

					popfd
					popad
			};
		}
	}

	~CellLocker()
	{
		TESObjectCELL * cell = locked;
		if (cell != NULL)
		{
			static int _lockCellExitHelper = 0x4C0190;
			_asm
			{
				pushad
					pushfd

					mov ecx, cell
					call _lockCellExitHelper

					popfd
					popad
			}
		}
	}

private:
	TESObjectCELL * locked;
};

 

 

Usage:

 

 

VMResultArray<TESObjectREFR*> ScanCellObjects(StaticFunctionTag* base, UInt32 FormType, TESObjectREFR* CenterObj, float SearchRadius, BGSKeyword* FindKeyword) {
	VMResultArray<TESObjectREFR*> output;
	if (CenterObj != NULL) {
		_MESSAGE("Cell scanning from form: %lu", CenterObj->formID);
		TESObjectCELL* Cell = CenterObj->parentCell;
		if (Cell) {
			CellLocker _locker(Cell); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
			tArray<TESObjectREFR*> objList = Cell->objectList;
			UInt32 count = objList.count;
			_MESSAGE("\tcell item count: %lu", count);
			for (UInt32 idx = 0; idx < count; ++idx) {
				TESObjectREFR* ObjRef = objList[idx];
				if (ObjRef == NULL || ObjRef->baseForm->GetFormType() != FormType) continue;
				else if (SearchRadius > 0.0f && !IsWithinRadius(CenterObj, ObjRef, SearchRadius)) continue;
				else if (FindKeyword && !HasKeyword(ObjRef, FindKeyword)) continue;
				else output.push_back(ObjRef);
			}
		}
		_MESSAGE("Cell scanning found: %d", (int)output.size());
	}
	return output;
}

 

 

CellLocker _locker(Cell);
I added before using the cell object list.
Posted

Lucky game had helper functions for locking and unlocking :P I can't test right now my computer blue screens today when I open any hardware accelerated application, if you have time try something like this:

 

Definition:

 

 

struct CellLocker
{
	CellLocker(TESObjectCELL* cell)
	{
		locked = cell;
		if (cell != NULL)
		{
			static int _lockCellEnterHelper = 0x4C0180;
			_asm
			{
				pushad
					pushfd

					mov ecx, cell
					call _lockCellEnterHelper

					popfd
					popad
			};
		}
	}

	~CellLocker()
	{
		TESObjectCELL * cell = locked;
		if (cell != NULL)
		{
			static int _lockCellExitHelper = 0x4C0190;
			_asm
			{
				pushad
					pushfd

					mov ecx, cell
					call _lockCellExitHelper

					popfd
					popad
			}
		}
	}

private:
	TESObjectCELL * locked;
};

 

 

Usage:

 

 

VMResultArray<TESObjectREFR*> ScanCellObjects(StaticFunctionTag* base, UInt32 FormType, TESObjectREFR* CenterObj, float SearchRadius, BGSKeyword* FindKeyword) {
	VMResultArray<TESObjectREFR*> output;
	if (CenterObj != NULL) {
		_MESSAGE("Cell scanning from form: %lu", CenterObj->formID);
		TESObjectCELL* Cell = CenterObj->parentCell;
		if (Cell) {
			CellLocker _locker(Cell); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
			tArray<TESObjectREFR*> objList = Cell->objectList;
			UInt32 count = objList.count;
			_MESSAGE("\tcell item count: %lu", count);
			for (UInt32 idx = 0; idx < count; ++idx) {
				TESObjectREFR* ObjRef = objList[idx];
				if (ObjRef == NULL || ObjRef->baseForm->GetFormType() != FormType) continue;
				else if (SearchRadius > 0.0f && !IsWithinRadius(CenterObj, ObjRef, SearchRadius)) continue;
				else if (FindKeyword && !HasKeyword(ObjRef, FindKeyword)) continue;
				else output.push_back(ObjRef);
			}
		}
		_MESSAGE("Cell scanning found: %d", (int)output.size());
	}
	return output;
}

 

 

CellLocker _locker(Cell);
I added before using the cell object list.

 

 

 

Added it, plugin compiles fine and I tested the actor scan function using different radius amounts and it all seemed to work fine as far as I could tell. Thanks.

Posted

Cool, btw how do you handle searching nearby objects if the object is in another cell and you are near the border of your cell ? Or is such a scenario even not possible to occur ?

Posted

Why are cloak spells a problem?

Because a new instance of the script to process the spell gets applied to every target of the spell, too many cloak spells at once and you get the dreaded Stack Dump and some scripts simply do not get run because there's no way the game can start them (stack is full!). This can effect EVERY scripted mod that is running, not just the mod or mods using cloak spells since if the stack is full and any other mod also need to start a script tries to do so, it will fail to run and your mod is now unstable.

How to solve the issue?

A ) Make the spell specific enough. If you filter out items/npcs etc in the script then you are doing it wrong, the filtering condition ought to be on the spell so anything not needing to be affected by the spell doesn't get affected in the first place and the script doesn't even get started.

Example: Sexlab Defeat Submit (corrected, sorry Guobo) uses a cloaking spell to determine if a Guard is close enough to see the sex act in progress and therefore shut it down but the spell starts for every NPC within range and then the script tests if the NPC is a Guard or not. Changing the spell to only apply it to guards in the first place reduced the overhead significantly. Also the mod has an option to turn off such guard interactions but that option was also tested in the script AFTER the spell was cast instead of being tested and not casting the spell in the first place.

B ) Use a Quest instead of a cloaking spell, you can have the game auto-fill Quest Aliases based on conditions (same as for a cloak spell). That is how Sexlab Aroused Redux eliminated the cloak spell from Sexlab Aroused and reduced the overhead of that mod.

Posted

Example: Sexlab Defeat uses a cloaking spell to determine if a Guard is close enough to see the sex act in progress and therefore shut it down but the spell starts for every NPC within range and then the script tests if the NPC is a Guard or not. Changing the spell to only apply it to guards in the first place reduced the overhead significantly. Also the mod has an option to turn off such guard interactions but that option was also tested in the script AFTER the spell was cast instead of being tested and not casting the spell in the first place.

 

B ) Use a Quest instead of a cloaking spell, you can have the game auto-fill Quest Aliases based on conditions (same as for a cloak spell). That is how Sexlab Aroused Redux eliminated the cloak spell from Sexlab Aroused and reduced the overhead of that mod.

No, I do not use a cloak system in Defeat for this, I use a targeted spell aggressor -> victim who send a story event every 5 seconds with OnUpdate event that starts a quest with the victim & the aggressor, from there the quest fills its aliases with npcs around if they met different conditions (friend of victim, accomplice, guard...)

if an alias is filled a script runs on OnInit event for this NPC to take action (start combat with aggressor, join the party etc..) 

 

I also use a quest that reset another quest every 10 seconds for the NPC vs NPC system, the second quest fills its aliases with in combat NPCs/creature and then uses the OnHit event, I'm satisfied with this system, it's quite solid and does the job without a huge impact on the performances, I just had to limit the OnHit events with a cooldown though, too much papyrus during huge battles.

 

A cloak system can be used too if well done, I personally didn't use it cause I needed more flexibility and didn't want any incompatibility with vanilla quest that forbid spells but it is a good option depending of what you want to do.

 

tl;dr My advice, use in game conditions as much as possible, fast and works fine, less papyrus the better.

Posted

 

Example: Sexlab Defeat uses a cloaking spell to determine if a Guard is close enough to see the sex act in progress and therefore shut it down but the spell starts for every NPC within range and then the script tests if the NPC is a Guard or not. Changing the spell to only apply it to guards in the first place reduced the overhead significantly. Also the mod has an option to turn off such guard interactions but that option was also tested in the script AFTER the spell was cast instead of being tested and not casting the spell in the first place.

 

B ) Use a Quest instead of a cloaking spell, you can have the game auto-fill Quest Aliases based on conditions (same as for a cloak spell). That is how Sexlab Aroused Redux eliminated the cloak spell from Sexlab Aroused and reduced the overhead of that mod.

No, I do not use a cloak system in Defeat for this, I use a targeted spell aggressor -> victim who send a story event every 5 seconds with OnUpdate event that starts a quest with the victim & the aggressor, from there the quest fills its aliases with npcs around if they met different conditions (friend of victim, accomplice, guard...)

if an alias is filled a script runs on OnInit event for this NPC to take action (start combat with aggressor, join the party etc..) 

 

I also use a quest that reset another quest every 10 seconds for the NPC vs NPC system, the second quest fills its aliases with in combat NPCs/creature and then uses the OnHit event, I'm satisfied with this system, it's quite solid and does the job without a huge impact on the performances, I just had to limit the OnHit events with a cooldown though, too much papyrus during huge battles.

 

A cloak system can be used too if well done, I personally didn't use it cause I needed more flexibility and didn't want any incompatibility with vanilla quest that forbid spells but it is a good option depending of what you want to do.

 

tl;dr My advice, use in game conditions as much as possible, fast and works fine, less papyrus the better.

 

Sorry, I meant to write Sexlab SUBMIT.

 

You know, the "other" mod, in fact the patch for it hidden in my signature is to fix those defects I mentioned. Not because I use that mod for the battle portion but for the dialogue and only the dialogue which is one reason I was upset to get stack dumps when it was throwing checks for guards and I had disabled its guard interactions.

 

The method YOU are using is exactly the right way to be doing it, via quest aliases so the compiled game code does the searching instead of Papyrus language interpreter which is significantly slower.

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...