https://docs.larian.game/index.php?title=Osiris_Gotchas&feed=atom&action=historyOsiris Gotchas - Revision history2024-03-29T12:23:28ZRevision history for this page on the wikiMediaWiki 1.29.0https://docs.larian.game/index.php?title=Osiris_Gotchas&diff=3494&oldid=prevKevin: Protected "Osiris Gotchas" ([Edit=⧼protect-level-larianeditonly⧽] (indefinite) [Move=⧼protect-level-larianeditonly⧽] (indefinite))2017-10-02T11:34:14Z<p>Protected "<a href="/Osiris_Gotchas" title="Osiris Gotchas">Osiris Gotchas</a>" ([Edit=⧼protect-level-larianeditonly⧽] (indefinite) [Move=⧼protect-level-larianeditonly⧽] (indefinite))</p>
<table class="diff diff-contentalign-left" data-mw="interface">
<tr style='vertical-align: top;' lang='en'>
<td colspan='1' style="background-color: white; color:black; text-align: center;">← Older revision</td>
<td colspan='1' style="background-color: white; color:black; text-align: center;">Revision as of 11:34, 2 October 2017</td>
</tr><tr><td colspan='2' style='text-align: center;' lang='en'><div class="mw-diff-empty">(No difference)</div>
</td></tr></table>Kevinhttps://docs.larian.game/index.php?title=Osiris_Gotchas&diff=2808&oldid=prevTinkerer: Added Osiris category2017-09-16T21:31:50Z<p>Added Osiris category</p>
<table class="diff diff-contentalign-left" data-mw="interface">
<col class='diff-marker' />
<col class='diff-content' />
<col class='diff-marker' />
<col class='diff-content' />
<tr style='vertical-align: top;' lang='en'>
<td colspan='2' style="background-color: white; color:black; text-align: center;">← Older revision</td>
<td colspan='2' style="background-color: white; color:black; text-align: center;">Revision as of 21:31, 16 September 2017</td>
</tr><tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l235" >Line 235:</td>
<td colspan="2" class="diff-lineno">Line 235:</td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>  DB_QueryResult(_Rv);</div></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>  DB_QueryResult(_Rv);</div></td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>Since this is now called from a procedure it will not cause a re-evaluate of the conditions in ''ProcCalledWhenDBIsModified''.</div></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>Since this is now called from a procedure it will not cause a re-evaluate of the conditions in ''ProcCalledWhenDBIsModified''.</div></td></tr>
<tr><td colspan="2"> </td><td class='diff-marker'>+</td><td style="color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;"><div><ins style="font-weight: bold; text-decoration: none;"></ins></div></td></tr>
<tr><td colspan="2"> </td><td class='diff-marker'>+</td><td style="color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;"><div><ins style="font-weight: bold; text-decoration: none;">[[Category:Osiris]]</ins></div></td></tr>
</table>Tinkererhttps://docs.larian.game/index.php?title=Osiris_Gotchas&diff=2453&oldid=prevTinkerer: Osiris gotchas section from internal wiki2017-09-14T19:52:51Z<p>Osiris gotchas section from internal wiki</p>
<p><b>New page</b></p><div>= Understanding Result Sets =<br />
Every time you do a query on a database you ask Osiris to give you back all the rows in that database that match the values you passed to it. The collection of these rows is called a result set. Osiris will take all rows in the database and filter out the rows that have keys (columns) that don't match the passed parameters. Any parameter that Osiris doesn't know will be considered an output parameter and will match any value in that column. <br />
<br />
That's the abstract theory, here's a little example:<br />
<br />
PROC<br />
ProcQueryDatabase()<br />
AND<br />
DB_TestDB(_Col1,_Col2)<br />
THEN<br />
ProcProcessValues(_Col1,_Col2);<br />
<br />
In this example, _Col1 and _Col2 don't have a passed value, so Osiris will treat them as output parameters. Since these will match everything, you get a result set that encompasses the complete database. This allows you to iterate over the database, row by row.<br />
<br />
PROC<br />
ProcQueryDatabase((INTEGER)_PassedCol1)<br />
AND<br />
DB_TestDB(_PassedCol1,_Col2)<br />
THEN<br />
ProcProcessValues(_PassedCol1,_Col2);<br />
<br />
In this example, we passed a value for the first column. So now Osiris will only return the rows that have a matching value in the first column. If there are no rows with a matching value for the first column, the result set is empty and the THEN part of this rule will not executed. <br />
<br />
What happens if you combine multiple database queries together? Osiris will then combine the result sets together to come up with 1 big result set. It will do this by taking the result set of each query and use each row in that result set to generate a result set for the subsequent queries.<br />
<br />
To clarify this with an example:<br />
<br />
DB_A("a");<br />
DB_A("b");<br />
<br />
DB_B(1);<br />
DB_B(2);<br />
<br />
PROC<br />
ProcQueryDatabase()<br />
AND<br />
DB_A(_A)<br />
AND<br />
DB_B(_B)<br />
THEN<br />
DB_AB(_A,_B);<br />
<br />
So what happens when Osiris encounters something like this?<br />
<br />
# Osiris sees the query on DB_A and generates a result set for it<br />
#* The Result Set is: ''("a");("b" )'' (where the rows in the Result Set are seperated by ';')<br />
# Osiris now goes over the rest of the conditions for each row in the result set that was generated:<br />
## using row "a" of the Result Set:<br />
### Osiris sees the query on DB_B and generates a result set for it<br />
###* That Result Set is: ''(1);(2)''<br />
### Osiris combines the result sets and ends up with this result set: ''("a",1);("a",2)''<br />
### For each row in that result set:<br />
#### using ''("a",1)''<br />
##### Osiris executes the THEN part of the rule and adds ("a",1) to DB_AB<br />
#### using ''("a",2)''<br />
##### Osiris executes the THEN part of the rule and adds ("a",2) to DB_AB<br />
## using row "b" of the Result Set:<br />
### Osiris sees the query on DB_B and generates a result set for it<br />
###* That Result Set is: ''(1);(2)''<br />
### Osiris combines the result sets and ends up with this result set: ("b",1);("b",2)<br />
### For each row in that result set:<br />
#### using ''("b",1)''<br />
####* Osiris executes the ''THEN''-part of the rule and adds ''("b",1)'' to ''DB_AB''<br />
#### using ''("b",2)''<br />
####* Osiris executes the ''THEN''-part of the rule and adds ''("b",2)'' to ''DB_AB''<br />
<br />
Some things to note about this example:<br />
* Osiris runs over databases in the order the entries were added to. In the example "a" came before "b".<br />
* '''Osiris generates and merges result sets for each database query you do and processes the rest of the conditions like that!'''<br />
<br />
Now let's look at the following example:<br />
<br />
PROC<br />
ProcComplicatedDatabaseQuery((CHARACTERGUID)_Player,(INTEGER)_ID)<br />
AND<br />
DB_CombatCharacters(_Player, _ID) //1<br />
AND<br />
DB_CombatCharacters(_Enemy1, _ID) //2<br />
AND<br />
DB_CombatCharacters(_Enemy2, _ID) //3<br />
AND<br />
CharacterIsEnemy(_Enemy1, _Player, 1) <br />
AND<br />
CharacterIsEnemy(_Enemy2, _Player, 1)<br />
AND<br />
_Enemy2 != _Enemy1<br />
THEN<br />
//do something with _Player,_Enemy1 and _Enemy2<br />
<br />
From the previous discussion we see that this happens:<br />
<br />
# Osiris sees the first query and generates a result set for it. Since all values were passed this will be either empty or contain exactly 1 row. Let's assume it contained exactly 1 row<br />
# For the row of 1, Osiris now generates a Result Set for query 2. Since only _ID was passed this is a Result Set that contains every row that has a second column with _ID as a value. This will be a Result Set with multiple rows in it!<br />
# Osiris now combines the 2 result sets to make a new result set: this will be a set with rows that are of the form:<br />
#:''(_Player,_ID,_Enemy1a)''<br />
#:''(_Player,_ID,_Enemy1b)''<br />
#:''(_Player,_ID,_Enemy1c)''<br />
#:''(_Player,_ID,_Enemy1d)''<br />
#:''...''<br />
#:''(_Player,_ID,_Enemy1z)'' (where ''Z'' denotes the last row in that ''DB_CombatCharacters'' database)<br />
# Osiris now runs over that combined Result Set and encounters query 3. It generates a Result Set for this Query. Since only _ID was a passed value, this is a Result Set that contains every row that has a second column with _ID as a value. Note that these are the same values as the ones returned by query 2!<br />
# Osiris now combines the Result Sets to come up with a new Result Set. These will be of the form:<br />
#: (_Player,_ID,_Enemy1a,_Enemy2a)<br />
#: (_Player,_ID,_Enemy1a,_Enemy2b)<br />
#: (_Player,_ID,_Enemy1a,_Enemy2c)<br />
#: ...<br />
#: (_Player,_ID,_Enemy1a,_Enemy2z)<br />
#: (_Player,_ID,_Enemy1b,_Enemy2a)<br />
#: (_Player,_ID,_Enemy1b,_Enemy2b)<br />
#: (_Player,_ID,_Enemy1b,_Enemy2c)<br />
#: ...<br />
#: (_Player,_ID,_Enemy1b,_Enemy2z)<br />
#: (_Player,_ID,_Enemy1c,_Enemy2a)<br />
#: (_Player,_ID,_Enemy1c,_Enemy2b)<br />
#: (_Player,_ID,_Enemy1c,_Enemy2c)<br />
#: ...<br />
#: (_Player,_ID,_Enemy1c,_Enemy2z)<br />
#: ...<br />
#: '''NOTE that this Result Set's rows has exploded! It runs over the first Result Set of query 1 in order by combining it with the Reuslt Set of query 2'''<br />
# '''For every row in the Result Set of step 5''': execute the ''CharacterIsEnemy'' query on ''_enemy1'' and then on ''_enemy2''. Then compare the 2 for equality.<br />
<br />
This example demonstrates a couple of problems:<br />
<ol><br />
<li> Because of the database queries with unconstrained (ie output) parameters the Result Set grows tremendously. Each resultset gets combined with the previous one and is run over in order. Step 5 of the example clearly demonstrates this. Osiris does NOT generate sets of the form:<br/><br />
&nbsp;''(_Player,_ID,_Enemy1a,_Enemy2a)''<br/><br />
&nbsp;''(_Player,_ID,_Enemy1b,_Enemy2b)''<br/><br />
&nbsp;''(_Player,_ID,_Enemy1c,_Enemy2c)''<br/><br />
Instead it combines each row of the previous result set with each row of the second result set, before moving on to the next row in the first result set.</li><br />
<li>The ''CharacterIsEnemy(_Enemy1, _Player, 1)'' query is executed for every row of the combined Result Set. If it was moved up higher the first Result Set could've been constrained:<br />
<pre><br />
PROC<br />
ProcComplicatedDatabaseQuery((CHARACTERGUID)_Player,(INTEGER)_ID)<br />
AND<br />
DB_CombatCharacters(_Player, _ID) //1<br />
AND<br />
DB_CombatCharacters(_Enemy1, _ID) //2<br />
AND<br />
CharacterIsEnemy(_Enemy1, _Player, 1) <br />
AND<br />
DB_CombatCharacters(_Enemy2, _ID) //3<br />
AND<br />
CharacterIsEnemy(_Enemy2, _Player, 1)<br />
AND<br />
</pre><br />
Using the above construction will not include _Enemy1 rows that do not pass the IsEnemy check. This means that every _Enemy1 is only checked once with the CharacterIsEnemy query. And not every one of them will contribute to the combined Result Set of query //3.</li><br />
<li> The ''_Enemy2 != _Enemy1'' check could've been done as soon as ''_Enemy2'' was queried. Now a ''CharacterIsEnemy'' check is done, followed by a comparison. If the 2 objects were the same, the ''CharacterIsEnemy'' query was unnecessary. Remember that an Osiris comparison or DB lookup is cheaper than making a call to the game code.</li><br />
</ol><br />
<br />
Is there anything we can do to avoid problems like the one shown in the example? Depending on what you want to do, it might be better or possible to divide the problem in separate steps using multiple procedures:<br />
<br />
PROC<br />
ProcComplicatedDatabaseQuery((CHARACTERGUID)_Player,(INTEGER)_ID)<br />
AND<br />
DB_CombatCharacters(_Enemy, _ID) <br />
AND<br />
CharacterIsEnemy(_Enemy, _Player, 1) <br />
THEN<br />
DB_EnemyDB(_Enemy);<br />
<br />
PROC<br />
ProcComplicatedDatabaseQuery((CHARACTERGUID)_Player,(INTEGER)_ID)<br />
AND<br />
DB_EnemyDB(_Enemy)<br />
AND<br />
DB_EnemyDB(_Enemy2)<br />
AND<br />
_Enemy!=_Enemy2<br />
THEN<br />
//do something with _Player,_Enemy and _Enemy2<br />
<br />
//clean up intermediate result<br />
PROC<br />
ProcComplicatedDatabaseQuery((CHARACTERGUID)_Player,(INTEGER)_ID)<br />
AND<br />
DB_EnemyDB(_Enemy)<br />
THEN<br />
NOT DB_EnemyDB(_Enemy);<br />
<br />
Structuring the problem like this, compared to the initial version we can see that each object in the ''DB_CombatCharacters'' database is only checked once with the ''CharacterIsEnemy'' query. The procedure that does the work (the middle definition) is also more compact and thus easier to read and maintain. If complexer filtering rules are needed they could be added to the first procedure that does nothing but filter.<br />
<br />
= Osiris bug with User Queries in Rule Conditions =<br />
There is a bug in Osiris in how it handles User Queries that could result in recursive calls to that same condition. Observe the following example:<br />
<br />
IF<br />
DB_ThatTriggersRule1(_Char)<br />
AND<br />
ObjectIsCharacter(_Char, 1)<br />
AND<br />
NOT DB_Dead(_Char)<br />
AND<br />
QRY_ReturnAResult(_Char)<br />
AND<br />
DB_QueryResult(_Result)<br />
THEN<br />
//do something<br />
<br />
QRY<br />
QRY_ReturnAResult((CHARACTERGUID)_Char)<br />
AND<br />
DB_QueryResult(_Rv)<br />
THEN<br />
NOT DB_QueryResult(_Rv);<br />
<br />
QRY<br />
QRY_ReturnAResult((CHARACTERGUID)_Char)<br />
THEN<br />
DB_QueryResult(_Rv);<br />
<br />
What happens here is that when a fact is added to the ''DB_ThatTriggersRule1'' database, it runs through the rule and executes the User Query ''QRY_ReturnAResult'' as part of the conditions. When the Query adds to the ''DB_QueryResult'' DB, however, Osiris sees that the DB ''DB_QueryResult'' is modified and it re-evaluates the rule again. This results (at the moment) in Osiris overwriting intermediate results and would result in a crash. This will now generate a critical assert (so it won't crash in Shipping) and stops forwarding the invalid results. <br />
<br />
To avoid these situations, avoid having Queries with "return values" as part of rules that react to database adds. Instead defer these to procedures:<br />
PROC<br />
ProcCalledWhenDBIsModified((CHARACTERGUID)_Char)<br />
AND<br />
ObjectIsCharacter(_Char, 1)<br />
AND<br />
NOT DB_Dead(_Char)<br />
AND<br />
QRY_ReturnAResult(_Char)<br />
AND<br />
DB_QueryResult(_Result)<br />
THEN<br />
//do something<br />
<br />
QRY<br />
QRY_ReturnAResult((CHARACTERGUID)_Char)<br />
AND<br />
DB_QueryResult(_Rv)<br />
THEN<br />
NOT DB_QueryResult(_Rv);<br />
<br />
QRY<br />
QRY_ReturnAResult((CHARACTERGUID)_Char)<br />
THEN<br />
DB_QueryResult(_Rv);<br />
Since this is now called from a procedure it will not cause a re-evaluate of the conditions in ''ProcCalledWhenDBIsModified''.</div>Tinkerer