Roggvir Posted September 11, 2019 Posted September 11, 2019 A lot has been written about this topic, but i didn't see one particular pitfall mentioned anywhere:Sometimes, especially when you cast stuff to something else, the game may happen to store the value as a "reference to the original object, but cast as whatever", which may prevent an object from being deleted when calling Delete(), possibly resulting in save game bloat. Spoiler A simplified realworld example of a script i have:(this is attached to my custom MiscItem in CK, which is getting frequently spawned by another script using PlaceAtMe()) Spoiler Scriptname MorsConjuredItem extends objectReference {attach to a miscItem in CK to make it despawn after some time once it was placed in the world} effectShader property onUnloadShader auto {effectShader to play right before despawning} float property duration = 5.0 auto {how long before the item despawns} potion property foodItem auto {the food item to turn this into when activated} form miscItem = none bool doOnce = true event OnLoad() if doOnce doOnce = false miscItem = self as form ; DO YOU SEE THE PROBLEM? YOU DO? WELL, I DIDN'T UNTIL I FOUND THE HARD WAY GotoState("LINGER") RegisterForSingleUpdate(duration) endIf endEvent state LINGER event OnUpdate() GotoState("FADEOUT") if Is3DLoaded() && onUnloadShader onUnloadShader.Play(self) endIf RegisterForSingleUpdate(2.5) endEvent endState state FADEOUT event OnUpdate() Disable() Delete() miscItem = none ; SEE THIS? DID YOU KNOW THAT YOU NEED THIS TO PREVENT SAVE FILE BLOAT? endEvent endState event OnContainerChanged(objectReference _newContainer, objectReference _oldContainer) if _newContainer UnregisterForUpdate() _newContainer.RemoveItem(miscItem, 1, true) _newContainer.AddItem(foodItem, 1, true) miscItem = none ; HERE IT IS AGAIN. DID YOU REALLY KNOW WHAT WOULD HAPPEN IF YOU WOULDN'T DO THIS? endIf endEvent Do note the lines where i set miscItem to NONE. It turns out, that if you do... miscItem = self as form ...the game seem to literally store the value as "reference to the objectReference this script is attached to, but as a form". In other words, doing the above seems to internally create a reference to the objectReference that script is attached to, preventing the objRef from being deleted after calling Delete() on it. Call me a moron, but i found it surprising, because i always thought that once i cast something to a FORM, it would be internally stored as some kind of structure consisting of plugin and formid. I stumbled on this by accident when i checked my save file with Fallrim Tools out of pure curiosity, and saw over 51000 zombie instances of my item script and same number of related refids that never got deleted. So i recommend using Fallrim Tools from time to time to test what kind of leftovers your mod leaves behind - you may be surprised. EDIT: A more clear example/description is probably in my third post.
Holzfrau Posted September 11, 2019 Posted September 11, 2019 12 hours ago, Roggvir said: It turns out, that if you do... miscItem = self as form ...the game seem to literally store the value as "reference to the objectReference this script is attached to, but as a form". In other words, doing the above seems to internally create a reference to the objectReference that script is attached to, preventing the objRef from being deleted after calling Delete() on it. Call me a moron, but i found it surprising, because i always thought that once i cast something to a FORM, it would be internally stored as some kind of structure consisting of plugin and formid That's just the way polymorphism works. A given object is its own type, as well as all the parent types of its type at the same time. An ObjectReference is derived from the Form type (as all items in the game world are), so it is an ObjectReference and a Form at the same type. Casting your ObjectReference to a Form doesn't actually do anything, you could have the line as miscItem = self and it would still work the same. The Form type is just a less specific type of object than an ObjectReference. If you really wanted to, you could assign an Actor, a Container, and an Armor to miscItem in the same script without any casting. Perhaps a simpler example will make things clearer: Suppose you have a Shape type, and from that you derive specific kinds of shapes like Circle, Square, etc. If you do something like this (not in Papyrus, but in a hypothetical programming language): Circle myCircle = new Circle() . . .Shape pickAShape = myCircle Why should pickAShape point at anything but myCircle? On a conceptual level, using the term 'shape' to refer to a circle is correct, it is just less specific. The same idea applies to this code. You don't need to specify that myCircle is also a Shape by casting it, because it is already a more specific kind of Shape by definition. Later code using pickAShape can't assume it's operating on a Circle - the Shape could be a Square or a Triangle - so calling something a Shape is again less specific.
Roggvir Posted September 12, 2019 Author Posted September 12, 2019 7 hours ago, Holzfrau said: you could have the line as miscItem = self and it would still work the same Nope. Papyrus compiler replaces it with "miscItem = self as form" (as long as the miscItem variable is type form). 7 hours ago, Holzfrau said: If you really wanted to, you could assign an Actor, a Container, and an Armor to miscItem in the same script without any casting Yes, as long as the value on the right side of the assignement can be cast to the required type, because the compiler will do the cast, as mentioned above. 7 hours ago, Holzfrau said: ...not in Papyrus, but in a hypothetical programming language... Why should pickAShape point at anything but myCircle? ... It just depends on the way how the language works, how it translates into machine code, and what that machine code does. "hypothetical language" example seem pretty moot here, because its "hypothetical" - it's like discussing usage of verbs in alien language from Tau Ceti without even knowing whether that language has verbs or exists at all - makes no sense to me. What is "pickAsShape" and how is it stored, depends on the language. I dont understand the point you are trying to make.
Roggvir Posted September 12, 2019 Author Posted September 12, 2019 Let me use different example in Papyrus: scriptname SomeLockpick extends objectReference {script attached to a lockpick miscItem} form varA form varB event OnLoad() varA = self as form varB = Game.GetFormFromFile(0xA, "Skyrim.esm") as form ; 0xA is the formId of the Lockpick in game's data. endEvent To most ppl, both varA and varB assignments will seem equal in their function, usage, and possible consequences (most ppl wont even know if there would be any). After all, they both seem to be storing the same form, but internally, each of these assignements will store the (seemingly same) values as a differnt thing, having different consequences: varA will internally result in a reference pointing to the Lockpick objectReference the script is attached to, counting towards the number of references pointing to that object, preventing the garbage collector (or whatever we call it) to Delete() that object for as long as that variable exists and keeps pointing to that object. And because the variable exists in the topmost scope of the script attached to that objectReference, they will keep each other alive indefinitely, until you stop it by clearing/assigning something else into that variable. varB will have no such effect. That was the whole point of my post - to point out that one should always pay attention to how Papyrus internally works. I didn't see this specifically mentioned, only indirectly in the general description of the Papyrus language and clearly i didn't pay enough attention - and i bet it is the same for most ppl doing anything with Papyrus.
Holzfrau Posted September 12, 2019 Posted September 12, 2019 16 minutes ago, Roggvir said: Nope. Papyrus compiler replaces it with "miscItem = self as form" (as long as the miscItem variable is type form). How do you know this? 16 minutes ago, Roggvir said: It just depends on the way how the language works, how it translates into machine code, and what that machine code does. "hypothetical language" example seem pretty moot here, because its "hypothetical" - it's like discussing usage of verbs in alien language from Tau Ceti without even knowing whether that language has verbs or exists at all - makes no sense to me. What is "pickAsShape" and how is it stored, depends on the language. I dont understand the point you are trying to make. The language here was "hypothetical" in the sense that it was pseudocode for demonstration purposes. In practice, it's not hypothetical at all - we know Skyrim is written in C++, and polymorphism in C++ works like I described. I haven't come across anything in my experience with Papyrus to suggest it handles polymorphism any differently either. Anyway, all I was trying to do is show an example using concepts that are easier to break down than forms and object references.
Holzfrau Posted September 12, 2019 Posted September 12, 2019 19 minutes ago, Roggvir said: Let me use different example in Papyrus: scriptname SomeLockpick extends objectReference {script attached to a lockpick miscItem} form varA form varB event OnLoad() varA = self as form varB = Game.GetFormFromFile(0xA, "Skyrim.esm") as form ; 0xA is the formId of the Lockpick in game's data. endEvent To most ppl, both varA and varB assignments will seem equal in their function, usage, and possible consequences (most ppl wont even know if there would be any). After all, they both seem to be storing the same form, but internally, each of these assignements will store the (seemingly same) values as a differnt thing, having different consequences: varA will internally result in a reference pointing to the Lockpick objectReference, counting towards the number of references pointing to that object, preventing the garbage collector (or whatever we call it) to Delete() that object. varB will have no such effect. That was the whole point of my post - to point out that one should always pay attention to how Papyrus internally works. I didn't see this specifically mentioned, only indirectly in the general description of the Papyrus language and clearly i didn't pay enough attention - and i bet it is the same for most ppl doing anything with Papyrus. Just to clarify what's going on here: varA is still pointing to that particular lockpick object reference, it just 'sees' it as the more basic Form type. If you want varA to point to the generic lockpick item the same as varB does, use self.GetBaseObject() instead.
Roggvir Posted September 12, 2019 Author Posted September 12, 2019 15 minutes ago, Holzfrau said: How do you know this? From the "bytecode" produced by the compiler (tried making my own decompiler once).
GenioMaestro Posted September 13, 2019 Posted September 13, 2019 On 9/12/2019 at 5:38 AM, Roggvir said: scriptname SomeLockpick extends objectReference {script attached to a lockpick miscItem} form varA form varB event OnLoad() varA = self as form varB = Game.GetFormFromFile(0xA, "Skyrim.esm") as form ; 0xA is the formId of the Lockpick in game's data. endEvent The problem is generated because you are creating a circular reference. You have an error in base concept. When you put the line varA = self as form, really you are putting in varA a reference to the actual instance of the script object called SomeLockpick but when you cast it as form you are showing it as the internal code of the form base object that hold the script. Really refer to the script instance id because is self and self, in this context, refer to the script instance id. Simply you are casting it as form and, if you print it, really you see the same code as when you put self.GetBaseObject() because the cast as form give you the id of the FORM base object. But internally reffer to the script instance id. As you has defined varA at the script level and you are putting in varA a reference to the script instance, when the Script Engine try delete the script instance see that varA aim to the same script instance that the Script Engine try destroy and because the script instance is in use by varA the Script Engine can not destroy it. That is a circular reference. For that you cumulate yours scripts in each execution. One solution is put varA = none for liberate the varA of the reference to the same script id. Other solution, as Holzfrau say, is use self.GetBaseObject() that give you exactly the same that self as form.
Roggvir Posted September 13, 2019 Author Posted September 13, 2019 4 hours ago, GenioMaestro said: circular reference The problem has nothing to do with a "circular reference" (which doesn't even really happen here, but i understand what makes you think it might), the problem is only about creating a reference to an object, which simply prevents Delete() from destroying it - that is ALL, this has nothing to do with circular references.
Roggvir Posted September 13, 2019 Author Posted September 13, 2019 5 hours ago, GenioMaestro said: When you put the line varA = self as form, really you are putting in varA a reference to the actual instance of the script object called SomeLockpick but when you cast it as form you are showing it as the internal code of the form base object that hold the script. Really refer to the script instance id because is self and self, in this context, refer to the script instance id. Simply you are casting it as form and, if you print it, really you see the same code as when you put self.GetBaseObject() because the cast as form give you the id of the FORM base object. But internally reffer to the script instance id. This is not true. It will not internally refer to the script instance, it will refer to the objectreference - those are two different things. If it would refer to the script instance, there would be no problem for Delete() to destroy the objectreference. Funny fact: if it would be referencing the script instance, which would actually be a direct circular reference, it would pose no problem at all EDIT: Sorry, that "funny fact" about direct circular ref not posing problems is bullshit, my mistake, i was actually thinking it can deal with those.
Roggvir Posted September 13, 2019 Author Posted September 13, 2019 4 hours ago, GenioMaestro said: Spoiler The problem is generated because you are creating a circular reference. You have an error in base concept. When you put the line varA = self as form, really you are putting in varA a reference to the actual instance of the script object called SomeLockpick but when you cast it as form you are showing it as the internal code of the form base object that hold the script. Really refer to the script instance id because is self and self, in this context, refer to the script instance id. Simply you are casting it as form and, if you print it, really you see the same code as when you put self.GetBaseObject() because the cast as form give you the id of the FORM base object. But internally reffer to the script instance id. As you has defined varA at the script level and you are putting in varA a reference to the script instance, when the Script Engine try delete the script instance see that varA aim to the same script instance that the Script Engine try destroy and because the script instance is in use by varA the Script Engine can not destroy it. That is a circular reference. For that you cumulate yours scripts in each execution. One solution is put varA = none for liberate the varA of the reference to the same script id. Other solution, as Holzfrau say, is use self.GetBaseObject() that give you exactly the same that self as form. I am really not trying to be an ass, but this is funny - in this post, you actually perfectly demonstrated what my warning essentially was about (which was "do not assume things, just because they seem to make sense that way to you"). I was expecting varA to be refering to a form, you are expecting it to refer to the script instance - each of us expected something that makes "sense to us", but that is not how it works ?
GenioMaestro Posted September 13, 2019 Posted September 13, 2019 4 hours ago, Roggvir said: The problem has nothing to do with a "circular reference" (which doesn't even really happen here, but i understand what makes you think it might), the problem is only about creating a reference to an object, which simply prevents Delete() from destroying it - that is ALL, this has nothing to do with circular references. Excuse me but the REAL problem is a circular reference in the script that prohibit the Script Engine delete a script which have a variable aimed to self because that mean that the script is in use, of course, by their OWN variables. The ObjectReference can be disabled, deleted and unloaded but the game can NOT destroy it because have one instance of one script attached to it. While the script instance exist the game can NOT destroy the ObjectReference. I create a copy of the AlavirKatana and attach the script Circular_Reference to it. Create a spell and the magic effect for execute a PlaceAtMe of the Katana. Scriptname Circular_Eff_Script extends activemagiceffect Weapon Property Circular_Katana auto Event OnEffectStart(Actor akTarget, Actor akCaster) ObjectReference Placed_Katana = Game.GetPlayer().PlaceAtMe(Circular_Katana) EndEvent Scriptname Circular_Reference extends ObjectReference form varA form varB form varC event OnLoad() varA = self varB = self as form varC = self as ObjectReference Debug.MessageBox("OnLoad varA:" + varA + "\n varB:" + varB + "\n varC:" + varC) RegisterForSingleUpdate(5) endEvent event OnUpdate() Disable() Delete() endEvent event OnUnLoad() Debug.MessageBox("\n\n\n UNLOAD \n\n\n varA:" + varA + "\n varB:" + varB + "\n varC:" + varC) endEvent Every time i launch the spell the game create a new Katana and call the attached script that show the two messagebox: Spoiler As you can see i create 3 variables and assing it ussin diferent cast but the screnshot show that the code that you get is exactly the same. And you see the Refid of the created object that start by FF. For that you say: "I was expecting varA to be refering to a form" because that is the ID that you see in the game. But go a bit more far. I save the game, open it whit ReSaver and filter by Circular and look what show: Spoiler I cast the spell 10 times and i have 10 instances of the script Circular_Reference and the game create a NEW instance every time i launch the spell. The game can NOT destroy the instances of the script Circular_Reference. And why? Look the VALUE of varA, varB, varC and look with a lot of detail. What value have? 2330a3a0 Look the valued ID in the upper rigth zone. You see ID: 2330a3a0? That is the ID of the script instance. If the value of the variables aim to the same script ID the Script Engine say the script is in use by their OWN variables and can NOT delete the script instance. Aditionally, ReSaver say: There is one instances with member data referring to this script instance. Circular_Reference: *refid=CREATED:00085c (2330a3a0) And here you can see the code that you see in the screenshots. CREATED is transformed to FF addiding 00085c that is the RefID of the object that have the script asociated. If you make double click in the blue letters of the RefID ReSaver transport you to the ChangeForms section of the savegame and you can see: Spoiler That is the ChangeForms zone and store every created or modified reference in the game. In the screenshot you can see that the REFR:FF00085c have ONE script instance atached, of course with the SAME code 2330a3a0 that aim to the script instance. While that script instance exist the Game Engine can NOT destroy the reference. If you know enougth about the ChangeFlags you can see that the REFR:FF00085c is MarkedForDelete. Then, the parragraf i writed 8 hours ago is totally correct. 8 hours ago, GenioMaestro said: But internally reffer to the script instance id. You can not get that information ussing the game. You must save the game and open it with ReSaver for see the real internal codes. Finaly, the problem is a circular reference because the VALUES of varA, varB and varC aim to 2330a3a0 that is the INTERNAL code of the instance of the script.
Roggvir Posted September 14, 2019 Author Posted September 14, 2019 3 hours ago, GenioMaestro said: Excuse me but the REAL problem is a circular reference in the script that prohibit the Script Engine delete a script which have a variable aimed to self because that mean that the script is in use, of course, by their OWN variables. No. First of all, the variable varA references the objectReference, not the script instance as you keep repeating. Secondly, the reason why the game will not destroy the objectReference, is because variable varA references the objectReference. It doesn't matter whether it is a circular reference - ANY reference to this objectReference, in ANY script, will prevent the game from destroying it. So again, the problem is not a circular reference, the problem is simply that SOMETHING is referencing that objectReference. 3 hours ago, GenioMaestro said: The ObjectReference can be disabled, deleted and unloaded but the game can NOT destroy it because have one instance of one script attached to it. While the script instance exist the game can NOT destroy the ObjectReference. No. You can have as many scripts attached to the object as the game can handle, and that alone will never prevent the game from destroying the object. What prevents the game from destroying the object, is if something is referencing the object or any (well, most) of the script isntances attached to it (exceptions would be magic effect scripts, and possibly some others i dont know about). 3 hours ago, GenioMaestro said: As you can see i create 3 variables and assing it ussin diferent cast but the screnshot show that the code that you get is exactly the same. No. What you see in those messages, is an output from some serialization or *ToString method. Look at the variable data in game's memory, or in the save file, and you will see that varA from your example will be referencing the script instance, while while B and C will be referencing the objectReference. 3 hours ago, GenioMaestro said: For that you say: "I was expecting varA to be refering to a form" because that is the ID that you see in the game. Again, the messages you see are not what is actually in the variables, but yes, i made the mistake of assuming that just because i cast something as a form, the variable would be referencing a form, and that is not how it works. 3 hours ago, GenioMaestro said: I cast the spell 10 times and i have 10 instances of the script Circular_Reference and the game create a NEW instance every time i launch the spell. The game can NOT destroy the instances of the script Circular_Reference. Well, i find it cute that you even went so far and named the script "Circular_Reference", but that doesn't magically make all those references circular. I didn't check all the example variables you made, but at least varB will not create a circular reference, and i assume (yeah, shouldn't do that) that neither will varC - both of those will be referencing the objectReference, not the script instance. Only your varA will create a circular reference in the game. And no, the reason why the game doesn't want to destroy the objectReference, is because something is referencing it - circular reference of not doesn't matter. 3 hours ago, GenioMaestro said: You can not get that information ussing the game. You must save the game and open it with ReSaver for see the real internal codes. Of course, and if you do, you will see that in my example, varA points to the objectReference, not the instance of a script attached to the objectReference. I think your problem is that you are using a year old version of the ReSaver, which doesn't show you some details. 3 hours ago, GenioMaestro said: Finaly, the problem is a circular reference because the VALUES of varA, varB and varC aim to 2330a3a0 that is the INTERNAL code of the instance of the script. Nope. The problem is that SOMETHING is referencing the objectReference (var B and C), and SOMETHING is referencing the script instance attached to that objectReference (varA). It does not matter, whether the reference is circular or not - the game just doesn't care what type of reference it is.
GenioMaestro Posted September 14, 2019 Posted September 14, 2019 8 hours ago, Roggvir said: Look at the variable data in game's memory We can make a debug session in assembler, if you want, but i see it a bit excesive for a simple circular reference. Spoiler 8 hours ago, Roggvir said: I think your problem is that you are using a year old version of the ReSaver, which doesn't show you some details. Look what show the last version of ReSaver: 8 hours ago, Roggvir said: you will see that varA from your example will be referencing the script instance, while while B and C will be referencing the objectReference. If you can understand that varA reffer to the script instance you must understand that varB and varC reffer to the SAME script instance because make a cast never, absolutely never, can change the VALUE. Is imposibe get a diferent value making a cast because make a cast is convert the SAME VALUE to another format. Please think a bit more about my words because you are totally confused and i explaining it to you in the best posible way. My screenshots demostrate it whitout any doubt. Make yours own test for verify my words. 11 hours ago, GenioMaestro said: the game can NOT destroy it because have one instance of one script attached to it. While the script instance exist the game can NOT destroy the ObjectReference.
Myst42 Posted September 14, 2019 Posted September 14, 2019 If I'm getting this right, the problem in discussion here is wether the accumulated stuff is either an object reference or a script instance (or both, but which comes first). ReSaver seems to be quite cathegoric in that matter and looks to me like it's a script instance, since it's literally stored on the script instance tree. Every time the object is created, it generates a script instance, and an object reference. The object reference is the active sword, stored at changeforms, the script instance, is the script that spawned with the new object. However... The script, in game, refers to the object reference sword, as the script message function shows, but in reality, it's refering to the script instance? So the game is basically lying about what the script is referring to? Upon deletion, the sword, as object reference, gets deleted and a new sword appears, but the previous script instance is still referring to itself, even though the object reference is no more? I take it ff0005b, 5a, 55, etc... are nowhere to be found on that save? Or they still exist as created objects marked for delete? That would certainly make bad things worse. Referring to Self on any script seems like a massive problem anyway, there's too many things that ca go wrong with that, and it's probably best to avoid it of one can help it. One needs to be absolutely sure of what the hell "Self" even is, on each event call before even thinking of calling it.
GenioMaestro Posted September 14, 2019 Posted September 14, 2019 The problem here is that you (Myst42), Roggvir and many people do not know how the game really works and how it does the things and that leads to false and wrong interpretations. When that is not clarified and defined correctly, it can spread as false knowledge that ultimately causes many people to think incorrectly. I will start at the beginning to make things more clearer. With the Creation Kit we can add a script to many game objects such as quest, aliases, weapons, armor, triggers, scenes ... almost anywhere we can add a script. But there is no way to add a solo script. NONE. Scripts are always associated with an object, whatever it is (quest, alias, weapon ...) and it is impossible to have a script that is not associated with an object. This is done for a good reason. When the game creates that object (search, alias, weapon ...) look in the ESP if that object has a script. But it always does AFTER creating the object because the script may need to access the properties of the object and, therefore, the script cannot exist before the object. Then, the process has to be: 1 - Create the object (search, alias, weapon ...) 2 - See if it has associated scripts 3 - Instance the script 4 - Run the script (if necessary) When we want to destroy the object we must follow the reverse steps: 1 - Stop execution 2 - Remove the script instance 3 - Remove the object. If step 1 or step 2 are not done we cannot delete the object. Because THAT object is the owner of that instance of that script. It is the one that supports the existence of that instance of that script. That script cannot exist without that object. And the script, when executed, does it in the context of THAT object. If the object has an associated script that is running, that object cannot be deleted. But it is not necessary for the script to be actively running in the Active Scripts zone to prohibit the deletion. It is enough that the script is waiting for an update with OnUpdate because that causes the script to register to receive a message and prohibits the removal of that instance of that script. When the game receives the order to delete the object, it looks to see if that object is supporting an instance of a script and if it does, it gives the command to the Script Engine to eliminate it. That is when the problems come. If the script is running, logically, the Script Engine cannot delete that instance, because it is running. If that instance is waiting for an OnUpdate, you cannot delete it either. If none of the two things happen, the Script Engine looks to see if that instance of that script is in use by someone.Because from a script we can connect to another script. If that other script has an active reference to the instance that we want to eliminate, logically, the engine script says that it cannot be deleted because it would leave the other script hung, it would remove something that the other script needs and block it or break it and, therefore , Can not do that. And this is precisely the problem we have in this case. That the engine script says that instance of that script is in use. By who??? By their own variables. Because they refer to themselves (self) and therefore, until their own variables are not deleted, the instance of the script cannot be deleted. As long as the script instance exists, the game cannot eliminate the base object and therefore, using my example, every time the spell is cast, a sword and an instance of a script are created that can never be deleted. They will accumulate and be stored in the savegame eternally increasing their size until it explodes. And the problem, although Roggvir does not asume it, is caused because by matching a variable to self, really, you are creating a reference to use the script (self) in its own variables. A circular reference is really created. Because the script itself is being used by itself, rather, by its own variables. Until those variables are cleared, matching them to none, that script cannot be deleted in any way, and of course, the object that supports that scrip cannot be deleted either. The biggest confusion on this issue is that, when we ask to the game what mean self it tells us that it values is ff000085c and we think, incorrectly, that self refers to the base object that supports the script. But that is totally false. The best way to see it is to look at the value of the variables with ReSaver and it tells us that the value of the variables are not ff000085c and that their REAL value is 2323a3c3 because, really, internally, they refer to the instance of the script. But why does the game tell us that self value is ff00085c? Because that is the context of execution of the script and corresponds to the identification of the object that supports the script. And why don't give us the internal code of the script? Because we can make nothing with the number 2323a3c3. It represents nothing to us. It gives us access to nothing. We can't do anything with him. Actually, the script is running in the context of the object that supports it and in the context of script execution the value of self is transformed, automatically and without us doing anything, in the code of the object to which the script belongs. That not mean that, really, the REAL value of self is ff00085c. That mean that the game give us that value while the REAL, internal value is 2323a3c3. And although it seems strange, that suits us very well. Because when we put a couple of lines like: Disable () Delete () The game runs perfectly and without any problem. But what happens if you ask: Disable () = Can the script be disabled ??? Delete () = How will I delete my own script? Well, obviously, when we put Disable () or Delete () we don't want to disable or delete the script. We really want to disable and delete the base object that supports the script. If we want these lines to work as they do, a consequence derived from it, is that when we ask for self the game will tell us that it is the code of the object that supports the script, because by definition, all the actions that the script does are derived in an action on the base object.
Myst42 Posted September 15, 2019 Posted September 15, 2019 9 hours ago, GenioMaestro said: The biggest confusion on this issue is that, when we ask to the game what mean self it tells us that it values is ff000085c and we think, incorrectly, that self refers to the base object that supports the script. But that is totally false. Then, in short... 11 hours ago, Myst42 said: So the game is basically lying about what the script is referring to? ...the game lies. And this is a very important point because it means we cannot trust it anymore on certain functions such as... asking for "Self". Basically, we cannot remove things that scripts are referencing, right? The bloat problem here, is that a script cannot be removed if it's still referenced by a script. The trick has to be that we need to know exactly when does the script end, AKA "gets released" so we can remove it successfully. Why can't we remove this "circular reference script"? Because someone requires it. So what does it take for it to be released? Most scrips should be removed the moment whatever thing spawned them ceases to exist, IE a magic effect's end. But what about an ObjectReference Script? It begins existing when an object manifests into the game world, and gets removed when...? It should be removed when the object gets removed, unless... ...Unless what happened in this case, happens. A script is still referencing the thing we want to delete. In other words, our deletion target , in this example, the script instance, is required... by a script... which in this case happens to be... itself. I guess the answer to the question of "when does a script truly end" is when it's not running code anymore AND, there nothing referencing it. Normally, we reference "permanent" forms on scripts, stuff that wont go away, weapons, a particular NPC, the Player etc... but when we reference a "dynamic" or "temporary" thing, such as a created object reference, or a script instance, we add it to the save, and it wont go away until no scripts reference it anymore. In a twisted way, it reminds me of why we can't kill essential NPCs, not even via script, no matter how much we code SetEssential(0) into stuff... Some NPCs are required by quests and until such quests are not terminated, a reference to the NPC will always keep them essential. Has nothing to do with this exact script issue, but the mechanic behind it sounds similar. Anyway, regardless of the details of understanding why, I guess one thing we can all agree on, and that is that releasing the variable by setting it to None, makes the world a better place. I'm curious though if this mechanic applies to anything referenced by another script, or just certain types of things. IE, we were unable to remove a script that references itself, but can we safely remove a created object reference if a script still points to it? Sorry if I mess up the example, but it'd probably be something like... Script A has spawn function IE: Quote ObjectReference Property SpawnThing Auto Hidden Form Property SomeThing Auto Event SomeEvent() SpawnThing = Game.GetPlayer().PlaceAtMe(SomeThing) EndEvent Then the spawned thing, has script B and script B says: Quote Event OnLoad() DoSomeStuffBeforeTerminating() Self.Delete() ; I'm assuming this "Self" is also the script instance and not the actual object reference, so can we really do this to terminate a spawned reference? Self.Disable() EndEvent In theory, script A still references SpawnThing for as long as script A exists, and even if we want to remove SpawnThing and script B does not have a locked property on itself, script A is still something that points to it. So can we safely remove SpawnThing and its attached script B, or we'll have the same issue?
Roggvir Posted September 15, 2019 Author Posted September 15, 2019 21 hours ago, GenioMaestro said: We can make a debug session in assembler, if you want, but i see it a bit excesive for a simple circular reference. Reveal hidden contents TESV.exe? Well, i think i have an explanation for most of the discrepancies - you are playing with Oldrim, i am playing with SSE. Then again, i doubt they would differ that much when it comes to what they save in the savefile, and even less how they work in realtime. So i guess your Oldrim ReSaver works a bit differently, not showing the same details. Who knows... at this point its clear we are both talking about a different game. Still... the reason for the objectReference not being deleted is not a circular reference, but simply the fact that there is a reference to that object. The game does not care where the reference is created, it only cares about the fact whether something does reference the object or not (in most cases, in some edge cases it doesnt even care).
GenioMaestro Posted September 15, 2019 Posted September 15, 2019 8 hours ago, Myst42 said: ...the game lies. No, really, the game does not lie because the main problem here is the context. Papyrus always runs the script in a context, usually in the context of the script owner, and I say NORMALLY because sometimes the execution context can be different. This is a very confusing point in Papyrus and the only way to understand it is to do tests and tests and more tests to understand how the contexts in Papyrus are handled. For you, it seems that Papyrus is lying because, as ReSaver says, the stored data does not match the data we see on the screen, but that is the only way to preserve integrity. I will try to explain it. When we ask for the value of self, in this example looking at the value in the message box, we see that the game says FF00085c but we see that number because the Debug.MessageBox () line runs, in this case, within the context of the owner of the script. When we put a line like varA = self, we incorrectly assume that the game store inside varA FF00085c because that is the value we see on the screen. And, in fact, if we add another message box that shows the value of varA we get ff00085c but, again, we get that number because we are running the script in the context of the owner. We, from our point of view, see that the value of self is ff00085c and when we put varA = self we imagine that within varA it will place FF00085c and if we confirm it, showing the value of varA, it tells us that its value is ff00085c. From our point of view, everything is correct. But that's not what the game really does. Because, really, internally, the self is worth 2323a3c3. The game tells us that self and varA are worth FF00085c because we are executing the lines of code in a context that transforms the value 2323a3c3 into FF00085c.But it does because we are under a certain context of execution. The value returned by self may be different in different contexts. If we put a line like varA = self, we are putting self within varA and, by pure logic, varA must have the same behavior as self and must return the same value that returns self in any circumstance, even when we change the context Actually, speaking in C ++ terminology, what Papyrus is doing is copying a pointer, causing varA to point to exactly the same memory location that self points out, and therefore, varA and self are worth the same and return the same value and behave in the same way always and at all times. Under a certain context, self worth one thing and under that same context, varA must be worth the same.If we change the context, self will be worth something diferent and, therefore, the value of varA will change and have the same value as self. This is called integrity preservation. And the only way to do this is to put the real internal value of self inside varA, which in this case is 2323a3c3, so that when we request the value of varA we return the correct value according to the execution context. But of course, by doing that, without us noticing, we are really storing a pointer to self. We are really creating a circular reference. And when Papyrus wants to delete the script, he can't do it because there is a variable that points to the script. Actually, what Papyrus tells us is that you can't delete that script because a variable refers to it. And that variable is our OWN variable that has a circular reference that points to OUR script. And the only way to break the block is to put varA = none so that the variable stops pointing to self. This eliminates the pointer and eliminates the circular use of the script. And that's when Papyrus Engine can delete the script, because nobody uses it anymore. Speaking about your examples and to clarify things further more I have to tell you that it doesn't matter what exactly you write. You can write: Delete (); or you can write: Self.Delete (); because when those lines are executed they do so under a context, in your exact example under the context of the owner of the script, and in that context self is transformed into the identification code of the owner of the script and therefore when those lines are actually executed, What they are running is, using my examples and my codes: FF00085c.Delete () And really the script executes FF00085c.Delete () regardless of whether you put self in front of the delete or don't put it. Because self, in this context, becomes FF00085c and even if you don't put self, really, the compiler is putting it for you, because all the direct actions of the script are redirected to the object that supports the script, depending on the context in which run. We can do absolutely nothing to get rid of the context. When Papyrus executes a script it always does it under a context that encapsulates and transforms the internal codes into the external codes we see. Using papyrus we cannot get the internal identification code of the script. There is no way to get the number 2323a3c3 because any line of code we execute will do so under a context that will transform that value (2323a3c3) into another value depending on the execution context. And always, absolutely always, we will have an execution context that will hide the number 2323a3c3.
Roggvir Posted September 15, 2019 Author Posted September 15, 2019 21 hours ago, GenioMaestro said: Is imposibe get a diferent value making a cast because make a cast is convert the SAME VALUE to another format. No. That depends on the types and how is the cast between them implemented. In some cases the value MUST change - for example when casting from float to int. 21 hours ago, GenioMaestro said: My screenshots demostrate it whitout any doubt. Well, i wouldn't say that. As i mentioned, your screenshots shows what seem to be invalid information. For example, the values for varA/B/C on your screenshots are all shown as "REF : 2330a3a0 (Form)" - what i see are different things depending on whether the value is stored as a reference to the script instance, or the objectReference. 18 hours ago, Myst42 said: If I'm getting this right, the problem in discussion here is wether the accumulated stuff is either an object reference or a script instance (or both, but which comes first). Well, if you read the original post, it was simply to remind people that they may end up creating references to an objectReference without realizing it. Then it got a bit derailed into blaming circular references, which is not only NOT happening in the example case, but is also irelevevant to the problem with live references and Delete() - the game only cares whether and object is referenced, it doesn't care where is it referenced, and whether any reference is circular or not, makes no difference. 18 hours ago, Myst42 said: Every time the object is created, it generates a script instance, and an object reference. The object reference is the active sword, stored at changeforms, the script instance, is the script that spawned with the new object. However... The script, in game, refers to the object reference sword, as the script message function shows, but in reality, it's refering to the script instance? So the game is basically lying about what the script is referring to? Upon deletion, the sword, as object reference, gets deleted and a new sword appears, but the previous script instance is still referring to itself, even though the object reference is no more? I take it ff0005b, 5a, 55, etc... are nowhere to be found on that save? Or they still exist as created objects marked for delete? That would certainly make bad things worse. Referring to Self on any script seems like a massive problem anyway, there's too many things that ca go wrong with that, and it's probably best to avoid it of one can help it. One needs to be absolutely sure of what the hell "Self" even is, on each event call before even thinking of calling it. Refering to Self on any script it not a problem on its own, but it can became a problem if you store the reference in a scope where it wont get destroyed when the script leaves that scope, or if you create conditions causing the script not being able to leave that scope, and you do not clear it yourself - then yes, the game may refuse to Delete() the object, because it is being referenced either directly (referencing the objectReference), or indirectly (referencing a script attached to it, which is typically extending objectReference - but other types of scripts attached to it may not pose the same problem). So, doing things like self.GetReference() or self.GetName() poses no danger, but if you do variable = self, then you need to make sure that variable gets cleared at some point (either automatically due to being declared in a scope that the script is bound to leave, like inside a function, or inside if-endif block, etc. - or you need to clear it manually). And here we get again to the original post, which was about reminding people that if they do variable = self as SOME_TYPE it often ends up internally creating a reference to the script or object (depending on what SOME_TYPE is and how the game implements the cast between those two types - for example, doing string variable = self as string is harmless as it wont result in the game creating any kind of reference to self, but doing form variable = self as form will result in the game storing the variable value as a reference to self).
GenioMaestro Posted September 15, 2019 Posted September 15, 2019 1 hour ago, Roggvir said: TESV.exe? Well, i think i have an explanation for most of the discrepancies - you are playing with Oldrim, i am playing with SSE. Then again, i doubt they would differ that much when it comes to what they save in the savefile, and even less how they work in realtime. So i guess your Oldrim ReSaver works a bit differently, not showing the same details. Who knows... at this point its clear we are both talking about a different game. Still... the reason for the objectReference not being deleted is not a circular reference, but simply the fact that there is a reference to that object. The game does not care where the reference is created, it only cares about the fact whether something does reference the object or not (in most cases, in some edge cases it doesnt even care). Please, read my messages. I know that i'm writing very large text and you must waste a lot of time in reading my words. But is the only way for you understan what is REALLY the problem. Because not have any relation to TESV.exe or to ReSaver or to the use of the base object. Your script, really, not have any reference to the base object. Simply, you are creating a circular reference at script level whitout any necesity. Let me explaing it ussing your own code in your first post. I go to remove all the unnecesary lines for understand the problem: Scriptname MorsConjuredItem extends objectReference form miscItem = none event OnLoad() miscItem = self as form ; DO YOU SEE THE PROBLEM? YOU DO? RegisterForSingleUpdate(5) endEvent event OnUpdate() Disable() Delete() miscItem = none ; SEE THIS? DID YOU KNOW THAT YOU NEED THIS TO PREVENT SAVE FILE BLOAT? endEvent event OnContainerChanged(objectReference _newContainer, objectReference _oldContainer) if _newContainer UnregisterForUpdate() _newContainer.RemoveItem(miscItem, 1, true) _newContainer.AddItem(foodItem, 1, true) miscItem = none ; HERE IT IS AGAIN. DID YOU REALLY KNOW WHAT WOULD HAPPEN endIf endEvent You are declaring a variable called miscItem at script level. In the OnLoad you write miscItem = self as form ; In the OnContainerChanged you write: _newContainer.RemoveItem(miscItem, 1, true) That is totally unnecesary. The value of self is the same in all the lines of the script. You not need put the value of self inside miscItem for use it latter. If you change the line of code from: _newContainer.RemoveItem(miscItem, 1, true) to _newContainer.RemoveItem( self , 1, true) Your code works in the same way and make exactly the same and not block the deletion of the script. Because you can USE self in any line of the script whitout any problem. And self, thanks to the execution context, reffer to the code of the item. When you put self inside a variable you are creating a circular reference and, because you declare the variable at script level, that variable is cleared only and exclusively when the script instance is deleted. But the Papyrus Engine can not delete the script because the variable aim to the script. Is a circular reference. The only way for break it is put the value of the variable to none in a manual way ussing a line of code like: miscItem = none If you not put that line, when the script end the execution, the value of miscItem aim to self and when the Script Engine try delete the script can NOT make it because their OWN variable, declared at script level, aim to self and self is the script that must be deleted. Please, read my words, read my messages, make test with your own game until you understand it CORRECTLY. If you not end understanding this problem in the correct way you can start spreading false advice and false knowneledge about how really works the Papyrus Engine and the game. Every developer with low knowneledge that talk to you can start getting false knowneledge. Until you talk to a expert developer, as me, that say the same things that i'm saying. And in this case, you can say: "oohhh... another stupid like GenioMaestro that not know what are saying..." While the REAL problem is in YOUR side becacuse you imagine the things in the incorrect way. Let me say that i have 50 years, i'm professional developer for more than 30 years, i develop in C++ for more than 20 years and i write papyrus code for more than 6 years. I perfectly know what i'm saying about this problem and why and i only try to you understand the problem in the correct way. You are creating a circular reference at papyrus level that block the deletion of the script and that not have any relation to the use of the undeliying object.
Roggvir Posted September 15, 2019 Author Posted September 15, 2019 19 hours ago, GenioMaestro said: If the object has an associated script that is running, that object cannot be deleted. But it is not necessary for the script to be actively running in the Active Scripts zone to prohibit the deletion. No. Whether an object has an associated script, running or not, never prevents the game from deleting the object (and the script with it). Yes, my mistake - as long as the script has an active thread. 19 hours ago, GenioMaestro said: It is enough that the script is waiting for an update with OnUpdate because that causes the script to register to receive a message and prohibits the removal of that instance of that script. No. If the script is registered for OnUpdate, it will never prevent the game from deleting the object (and the script with it). Yes, my mistake. 19 hours ago, GenioMaestro said: As long as the script instance exists, the game cannot eliminate the base object No. Well... provided that by "base object" you actually mean the "object the script is attached to" and not the real base object, which you can NEVER destroy. Existing script instance, even if attached to an object, has no effect on whether the game can delete that object. 19 hours ago, GenioMaestro said: If the script is running, logically, the Script Engine cannot delete that instance, because it is running. NO! Not only what you find "logical" has nothing to do with how things work, but what the hell do you even find logical about that? Again, whether a script is attached to an object, and whether that script is running or suspended, never prevents the game from destroying the object (and any such scripts with it). Yeah, if "running" means active thread. 19 hours ago, GenioMaestro said: If that instance is waiting for an OnUpdate, you cannot delete it either. NO! This is not true. Being registered for OnUpdate event does NOT prevent the game from destroying any objects and any associated scripts with them, including the scripts that is registered for the OnUpdate! And you accuse ME of spreading misinformation? Yes, my mistake. 19 hours ago, GenioMaestro said: If the script is running, logically, the Script Engine cannot delete that instance, because it is running. If that instance is waiting for an OnUpdate, you cannot delete it either. If none of the two things happen, the Script Engine looks to see if that instance of that script is in use by someone.Because from a script we can connect to another script. If that other script has an active reference to the instance that we want to eliminate, logically, the engine script says that it cannot be deleted because it would leave the other script hung, it would remove something that the other script needs and block it or break it and, therefore , Can not do that. NOTHING in that paragraph is true, except the bit about OnUpdate and whether the "script is in use by someone", where you finally mention the ONLY reason why a game wouldn't destroy the object, which is if there is a reference to it (direct or indirect). Ok, provided that by "running" you mean the script has an active thread, not that it is just existing (as you wrote in some other place). 19 hours ago, GenioMaestro said: And the problem, although Roggvir does not asume it, is caused because by matching a variable to self, really, you are creating a reference to use the script (self) in its own variables. A circular reference is really created. Because the script itself is being used by itself, rather, by its own variables. Until those variables are cleared, matching them to none, that script cannot be deleted in any way, and of course, the object that supports that scrip cannot be deleted either. No. The problem (game refusing to destory the object) happens because of a reference to that object, period. The game doesn't give a shit about whether we call that reference circular. And that is waht i am saying from the very beginning, so where the hell did you get the "Roggvir does not asume it"??? WTF? I am done. You are just piling up nonsense after nonsesen, just because "it seems logical" to you, but 90% of it is completely wrong. I dont have the patience for this.
Roggvir Posted September 15, 2019 Author Posted September 15, 2019 24 minutes ago, GenioMaestro said: That is totally unnecesary. The value of self is the same in all the lines of the script. You not need put the value of self inside miscItem for use it latter. It is totally neccessary in the real script, this was a simplified example. 25 minutes ago, GenioMaestro said: Please, read my words, read my messages, make test with your own game until you understand it CORRECTLY. If you not end understanding this problem in the correct way you can start spreading false advice and false knowneledge about how really works the Papyrus Engine and the game. ARE YOU FUCKING KIDDING ME? YOU are the one spreading nonsense - see my previous post!
GenioMaestro Posted September 15, 2019 Posted September 15, 2019 20 minutes ago, Roggvir said: NO! This is not true. Being registered for OnUpdate event does NOT prevent the game from destroying any objects and any associated scripts with them, including the scripts that is registered for the OnUpdate! And you accuse ME of spreading misinformation? Excuse me but YES... you not know how really works the Script Engine and the game. I'm explaining it to you because you have a lot of bad concepts and you simple close your eyes. Please, read the oficial documentation in the web page of the Creation Kit, and please, read and understand it in the correct way: https://www.creationkit.com/index.php?title=Delete_-_ObjectReference The parragraf say literaly: It is no longer registered for updates
Roggvir Posted September 15, 2019 Author Posted September 15, 2019 10 minutes ago, GenioMaestro said: Excuse me but YES... you not know how really works the Script Engine and the game. I'm explaining it to you because you have a lot of bad concepts and you simple close your eyes. Please, read the oficial documentation in the web page of the Creation Kit, and please, read and understand it in the correct way: https://www.creationkit.com/index.php?title=Delete_-_ObjectReference The parragraf say literaly: It is no longer registered for updates Yeah, i made a mistake when testing it (used single update registration while i thought i used the periodical one and it expired too soon), so yes, registering for OnUpdate does prevent the game from deleting the object. Sorry about that - hope i fixed it in all the quotes.
Recommended Posts
Archived
This topic is now archived and is closed to further replies.