Cross Reference tool

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,098
Reaction score
13,704
First Language
English
Primarily Uses
RMMV
A fun aspect of the problem are ranges such as batch change of switches and variables. How should you deal with those?
Ugh! I didn't even consider batch switch/variable changes.

@Shaz:


This present an excellent training opportunity :D


Can you create a cross-references tool? One where given an id (5, 42, etc) and type (actor, weapon, switch, etc.) it will tell you where that is used. You know, in which events, page properties, etc.
LOL! You tease me :)


I've already created this, in the form of an exporter script. I will add an in-game version to my to-do list :)
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,268
Reaction score
1,422
First Language
English
Primarily Uses
RMXP
Excellent to hear that you have half of the work done :D

Let's say we for common events have a list of [from_id => to_id] mappings. You can then look up the cross-references for both the to_id and from_id. For simplicity I would suggest (initially at least) that you only allow swap between to_id and from_id better called id1 and id2 since that is much easier. By demanding that no id is present in more than one mapping, you do not have to worry about cycles and tree structures.

So all that is left is to change the common event and data cross-referencing one of those two. That is it, and it is applies for a more general case. Only ranges remains to bother you. You can explode those statements into singular statements. I.e. for each say variable in the range, you create a new event command that set the variable to whatever the original command do. Afterwards you can swap away.

As an alternative, you can choose to simply ignore those statements, though telling the user about those would be a very good idea.

*hugs*

 - Zeriab
 

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,098
Reaction score
13,704
First Language
English
Primarily Uses
RMMV
Hey, you didn't say anything about swapping!
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,268
Reaction score
1,422
First Language
English
Primarily Uses
RMXP
What would you prefer to do if there already exists an with the given to_id?

Besides, for you to properly learn an appropriate difficulty is needed..

@Liak: Sorry about the semi-hijack, but maybe this can end up in a script which solves your problems :D

*hugs*
 

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,098
Reaction score
13,704
First Language
English
Primarily Uses
RMMV
I've split it out so we don't take over Liak's thread :)


Now, are we talking about a cross-reference tool, or a swapping tool? I assumed you were just talking about a cross-reference tool, where you could select your switch/variable/whatever, and it would show you all the places in the game where it was used.
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,268
Reaction score
1,422
First Language
English
Primarily Uses
RMXP
Thank you for the split :)

I admit my master plan for the cross-reference tool was to be used as a building-block in an database id changing utility. Distinct swaps of ids seems to be the easiest solution though I could easily be wrong.

It will be difficult, torturous and rewarding. Wanna try your hand at creating it?

*hugs*

 - Zeriab
 

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,098
Reaction score
13,704
First Language
English
Primarily Uses
RMMV
Let me write the cross-reference tool first, then I'll tackle the switcheroo. I don't think the latter lends itself well to an interactive utility, if we're considering multiple things that can be changed in one pass.

Edit: okay, can I have your feedback, please, Zeriab?

This is the method to look at the Conditional Branch event command and add it to the display list if it contains anything that the user asked for. @mode indicates whether they're after switches, variables, etc., and @selection is the id of the switch, variable, etc that they've chosen to search for.

In particular, I'd like to know if doing it this way would add to the processing time? I suspect it would, because if the last thing on the list is what they're after, it's got to go through all of the previous conditions first. A case block would probably give faster processing, but would be much more lengthy to write.

Do you think there would be a significant enough different to resort to using case instead?

def poll_command_111 # This does not currently look at the script part of a conditional branch # (@params[0] == 12, script is in @params[1]) if (@mode == :switches && @params[0] == 0 && @params[1] == @selection) || (@mode == :variables && @params[0] == 1 && @params[1] == @selection) || (@mode == :variables && @params[0] == 1 && @params[2] != 0 && @params[3] == @selection) || (@mode == :actors && @params[0] == 4 && @params[1] == @selection) || (@mode == :classes && @params[0] == 4 && @params[2] == 2 && @params[3] == @selection) || (@mode == :skills && @params[0] == 4 && @params[2] == 3 && @params[3] == @selection) || (@mode == :weapons && @params[0] == 4 && @params[2] == 4 && @params[3] == @selection) || (@mode == :armors && @params[0] == 4 && @params[2] == 5 && @params[3] == @selection) || (@mode == :states && @params[0] == 4 && @params[2] == 6 && @params[3] == @selection) || (@mode == :enemies && @params[0] == 5) || (@mode == :states && @params[0] == 5 && @params[3] == @selection) || (@mode == :items && @params[0] == 8 && @params[1] == @selection) || (@mode == :weapons && @params[0] == 9 && @params[1] == @selection) || (@mode == :armors && @params[0] == 10 && @params[1] == @selection) add_command(@mode == :enemies) end end
This is the equivalent as a case block (yes, it's better commented, but please don't answer based on that)

def poll_command_111 # This does not currently look at the script part of a conditional branch # (@params[0] == 12, script is in @params[1]) case @params[0] when 0 # Switch add_command if @mode == :switches && @params[1] == @selection when 1 # Variable add_command if @mode == :variables && (@params[1] == @selection || (@params[2] != 0 && @params[3] == @selection)) when 4 # Actor if @mode == :actors && @params[1] == @selection add_command elsif @params[3] == @selection case @params[2] when 2 # Class add_command if @mode == :classes when 3 # Skills add_command if @mode == :skills when 4 # Weapons add_command if @mode == :weapons when 5 # Armors add_command if @mode == :armors when 6 # States add_command if @mode == :states end end when 5 # Enemy if @mode == :enemies add_command(true) elsif @mode == :states && @params[3] == @selection add_command end when 8 # Item add_command if @mode == :items && @params[1] == @selection when 9 # Weapon add_command if @mode == :weapon && @params[1] == @selection when 10 # Armors add_command if @mode == :armors && @params[1] == @selection end end
Grrrrr ... the case one IS better. Do you have any idea how annoying that is to me? Thankfully, I don't have to go back and convert too much at this stage (because, you know, OCD dictates that they must ALL be a series of conditions or ALL case blocks).

*groan*

and now I've just remembered FEATURES and EFFECTS can have references to lots of this stuff too, as well as descriptions in the database and anything that could appear in the battle log.
 
Last edited by a moderator:

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,268
Reaction score
1,422
First Language
English
Primarily Uses
RMXP
Sure, my plan was to let you finish the cross-reference utility first anyway XD

I certainly have thoughts about the performance of your code with its smell of premature optimization. But, tests are what really matters. They will show the differences and their significance.  Maybe it depends on the data. You can easily have two pieces code where the fastest is context-dependent.
Not that the whole question is that significant in the whole picture. You are searching for specific references through the actual game data structure with all its verbosity for our goal. Instead, I suggest a different approach to the problem. *hint* *hint*

Fast look-up is definitely not a property, so instead I suggest you compile all the possible cross-references into some kind of data structure that does allow fast look-up. No matter if creating it takes several minutes. Once created from a type and an id you should be able to tell all the places where it is used. How will you go about achieving this?

*hugs*
 - Zeriab
 
 
Last edited by a moderator:

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,098
Reaction score
13,704
First Language
English
Primarily Uses
RMMV
Oooohhhhhh ... penny drops.

Very nice. Now I'm kicking myself for not talking to you about my approach before I started.

So I'm thinking of something along these lines:

Hash1 = { :switches => [element 001 = [place where switch 1 is used, place where switch 1 is used, place where switch 1 is used], element 002 = [place where switch 2 is used, place where switch 2 is used, place where switch 2 is used] ], :variables => [element 001 = [place where var 1 is used, place where var 1 is used] ] }where the innermost element is a structure that contains the location and identification of the item that holds the reference, along with page, line number, and use, whenever appropriate.Your thoughts?

Now, if I place this on the menu as an option for the developer, so it has its own scene and windows, with a 'build' command and commands to inspect the results, where would this build process belong? In the scene, in one of the windows, in a module? As an add-on to DataManager? And why?
 
Last edited by a moderator:

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,098
Reaction score
13,704
First Language
English
Primarily Uses
RMMV
Here you go, for anyone who wants to have a play.


I have not tested this thoroughly, so interested in any errors anyone can find.


Just paste into a new slot, the references will be rebuilt on launching the game (I have over 200 maps in my project, and it only took a few seconds to build), and available through a new menu command. It aliases the Window_MenuCommand and Scene_Menu methods, so should go below any other custom scripts that overwrite any of those.


In particular, if you get any crashes, I'd be very interested in seeing console output generated by Mithran's Crash Debugger - that'll help me track it down much more easily.
 
Last edited by a moderator:

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,268
Reaction score
1,422
First Language
English
Primarily Uses
RMXP
Excellent work Shaz :D

Your data structure makes sense. Using an array to map id => cross-references makes sense as you know that only a relatively small space of ids can be used. Dense usage (few empty values in the array) seems like a fair assumption. Whether you create nice wrappers is of course a different question. Of course you can imagine why this data structure willl be helpful in upcoming assignment.

The performance does indeed sound nice and during the upstart it does not matter much, A second wait whenever you move to a new Id in a UI would be annoying. With your current data structure it is fast, right?

As a small note I would suggest resetting the index of the @result_window when making a new selection.

I do not have time to delve deeply into your code atm. Maybe tomorrow, although the weekend is more likely.

*hugs*

 - Zeriab
 

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,098
Reaction score
13,704
First Language
English
Primarily Uses
RMMV
Thank you Zeriab. I did have a few issues with the windows, and I'm pretty sure they're not as good as they could be. I appreciate the tip on resetting the selection index - I did it in one place (where I was having the window issues) but not the other.

Your data structure makes sense. ... Of course you can imagine why this data structure willl be helpful in upcoming assignment.
Ah, I was debating whether to break the structure right down (store the source map/database item separate to its name) or not. I decided not, for the sake of making it easier to print. But now that I think about the next step, I really should have saved every piece in its own property. Going to be a bit difficult extracting the map id from a string that says Map 3: Temple. I will have to redo that - thankfully I think it's only a one-line change.
 
Last edited by a moderator:

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,268
Reaction score
1,422
First Language
English
Primarily Uses
RMXP
As said I took a longer look at your code. This time I focused purely on the data collection. I.e. the first 809 lines.

Let me start with the big one. Given a particular XReference instance. Can you tell me which data file to load, and assuming you have access to the top instance give me the exact object having the cross-reference? Yes, this is with the intention of changing reference to a specific id. Of course the variable references in text may be a bit tricky. Whether you want actually to return such information or only use it internally in the class is up to you.

I am not particular fond of the exposed xref hash. If I want cross-references for a particular id and type I have to use $data_xrefs[type][id] and still be aware of possible empty collections. Why should I need to know about the internals. Something like DataXRefs.xrefs(type, id) or DataXRefs.each_xref(type, id) {|xref| #do something} would be much nicer. You can also make save_xref nicer:

    def self.save_xref(type, id, reference = command(@cmd))      xref = XReference.new(@source, sprintf('%d: %s', @refid, @ref), @page, @line, reference)      DataXRefs.add(type, id, xref)    endYou can do more niceties with such an object. Whether you actually protect the global variable behind DataXRefs or simply just use $data_xrefs.add(type, id, xref) is less important.

Small bug:

def command_285should be called

self.build_xrefs_command_285It is a good idea to specially craft a test map that ensures you can at least have a happy path for  cross-reference cases. I.e. each command relevant should be present. When divergent paths are available for a command each of those should be present as well. Not only will such a map give you a higher confidence in the current script version, it will also help you find regression errors when changing the script.
I agree that you should keep track of the map id when processing maps. It will simplify matters to have a possible map id directly in the xref. You can consider whether subclassing is valuable. One hand you can use it to distinguish between map xrefs and other xrefs. On the other hand it does come at the cost of more complexity.

You did good work. ^^

*hugs*
 - Zeriab
 
 

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,098
Reaction score
13,704
First Language
English
Primarily Uses
RMMV
Thanks Zeriab. Yes, I need to add the keys into the data itself so it's easy to locate the source of the reference, and I think I'll split out all of those formatted items for the same reason, and just do the combining in the windows.


I like your idea of hiding the hash and adding methods for access. I'll do that.


And thanks for the pickup on the bug. Yes, a map (or several) to test every case is on my to-do list.


I would say this is low on my priority list, but now that discussions are happening, I actually COULD use the switcher tool in my own project, so it's no longer so low. Liak has a lot to answer for! :D
 
Last edited by a moderator:

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,098
Reaction score
13,704
First Language
English
Primarily Uses
RMMV
I don't really get this:

I am not particular fond of the exposed xref hash. If I want cross-references for a particular id and type I have to use $data_xrefs[type][id] and still be aware of possible empty collections. Why should I need to know about the internals. Something like DataXRefs.xrefs(type, id) or DataXRefs.each_xref(type, id) {|xref| #do something} would be much nicer. You can also make save_xref nicer:

    def self.save_xref(type, id, reference = command(@cmd))      xref = XReference.new(@source, sprintf('%d: %s', @refid, @ref), @page, @line, reference)      DataXRefs.add(type, id, xref)    endYou can do more niceties with such an object. Whether you actually protect the global variable behind DataXRefs or simply just use $data_xrefs.add(type, id, xref) is less important.
What's the difference between me saying this:

def self.save_xref(type, id, reference = command(@cmd)) $data_xrefs[type] = [] if $data_xrefs[type].nil? $data_xrefs[type][id] = [] if $data_xrefs[type][id].nil? $data_xrefs[type][id].push(XReference.new(@source, sprintf('%d: %s', @refid, @ref), @page, @line, reference)) endand this?
Code:
  def self.add(type, id, reference)    $data_xrefs[type] = [] if $data_xrefs[type].nil?    $data_xrefs[type][id] = [] if $data_xrefs[type][id].nil?    $data_xrefs[type][id].push(type, id, reference)  end  def self.save_xref(type, id, reference = command(@cmd))    xref = XReference.new(@source, sprintf('%d: %s', @refid, @ref), @page, @line, reference)    add(type, id, xref)  end
All I can see is that I've split the steps from a single method into two methods. But they're doing exactly the same thing, and will be called from exactly the same places.Re, this:

If I want cross-references for a particular id and type I have to use $data_xrefs[type][id] and still be aware of possible empty collections. Why should I need to know about the internals.
This is just the same way the $data_* classes work. If I want to look at actors, I have to do $data_actors[id] and be aware of nil elements, as well as its structure.The only references to $data_xrefs is within the module itself, and within the windows and scenes I write for them. So yes, I could provide a getter method rather than use a global variable for the hash (a setter method, I think, is pointless, because the only additions are done within the module, not from outside), and return either an array or nil, but the scene/window would still have to cater for nil or empty results. I'm not anticipating that anyone will be writing add-on modules or classes for this, the way we all do with the database classes.

You may need to explain the benefits in more detail, as I think I'm just not really getting your point here.
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,268
Reaction score
1,422
First Language
English
Primarily Uses
RMXP
*Shaz uses Extract Method*

There you can google it now :D

Consider Extract Method as basically an algorithm which takes a method and gives you two method preserving the functionality.

All I can see is that I've split the steps from a single method into two methods. But they're doing exactly the same thing, and will be called from exactly the same places.
Refactoring is about altering the structure/architecture of code without change the functionality. Why refactor? Multiple reasons can exist. The original abstraction level for an object changed, responsibility changed. Maybe you want to add a new feature where a different structure is necessary or will help. Typically, the goal of improving the code and design is implied.

Of course misusing refactoring can reduce the quality of the code. They are but certain transformations of code.

My point does seem to have been missed, so let me elaborate. I want you to abstract nitty gritty details of your xref data structure away by encapsulating it in an object. The interesting part for both the xref creation and especially for the consumers is that a given type and id is related to 0, 1 or more cross-references. Help the data creation by providing an API for adding new such relations. Help the consumers by implementing nice iterators.

This is just the same way the $data_* classes work. If I want to look at actors, I have to do $data_actors[id] and be aware of nil elements, as well as its structure.
You are right that consistency is good to preserve. In cases where it leads to a sub-optimal solution you would have to weigh the benefits of the preferred method versus the detriments caused breaking the consistency. Rarely is there a specific right or wrong. In the name of teaching I suggest we look differently on this. Do not follow the normal $data_ pattern. Revisiting this later once you have had some time to play around with different concepts sounds like a good idea.

The only references to $data_xrefs is within the module itself, and within the windows and scenes I write for them. So yes, I could provide a getter method rather than use a global variable for the hash (a setter method, I think, is pointless, because the only additions are done within the module, not from outside), and return either an array or nil, but the scene/window would still have to cater for nil or empty results. I'm not anticipating that anyone will be writing add-on modules or classes for this, the way we all do with the database classes.
Make it so that the scenes/windows do not have to cater for nil. Empty results is a different matter, one they still should cater to.

*hugs*

 - Zeriab
 

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,098
Reaction score
13,704
First Language
English
Primarily Uses
RMMV
You know you're hurting my brain, right?!


So, I have my SHAZ::XRef module, which contains all the processing needed to extract the data from the files and put it into the hash. But you want me to NOT store the hash in this module? You want me to have it as its own object, with accessor methods? Will this be a class that I have to instantiate, or a new module with the hash as a private variable? And the save_xref method would be in the existing module, but the add method would be a part of the new class/module/whatever that contains the data structure?


Is there another example, in RTP or in other scripts, that you can think of, that does this in the way you're suggesting? It might come together for me if I can see another implementation.


You will need to speak slowly, because I have not been using modules long, so I may not really have a grip on WHY they should be used in preference to classes, or how to use them in the best way.
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,268
Reaction score
1,422
First Language
English
Primarily Uses
RMXP
No, but I do not mind the good news :D
This turned out better than anticipated. I know you will be able to figure out in the end. Your struggle will help you learn more.

To make things easier let us just start with:

$data_xrefs = MyDatastructureManagingObject.new   # Pick a good name - This is also part of the exercise
Where to split the responsibilities is something you must decide. Yes, it is usually an ongoing discussion. I know you want my opinion on how to design the relationships, but I would rather that you try on your own first.

Fun, isn't it :D
*hugs*
 - Zeriab
P.s. someone just told me I am 'lawful evil'
 

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,098
Reaction score
13,704
First Language
English
Primarily Uses
RMMV
I know you will be able to figure out in the end. Your struggle will help you learn more.
Blah, blah, blah! I want MA. You're so mean :(


lol - 'lawful evil' - that's not what I was thinking, but hey, if the name fits :D


Alright. I can do that. But this time, I'll tell you what I'm going to do first, so you can pull it apart BEFORE I've made massive changes to my script that I'll only have to redo, because you seem to take some sort of sadistic delight in sending me in different directions ;)
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,846
First Language
English
Is there another example, in RTP or in other scripts, that you can think of, that does this in the way you're suggesting? It might come together for me if I can see another implementation.
An RTP example would be the RPG::UsableItem::Damage class, where the Damage object is referenced by an RPG::Skill or RPG::Item, but it's the damage that handles all the damage calculations, and not the skill or item that's doing it.

You could easily have a skill determine how much damage it does, but the devs chose not to do it that way.

Here's another example of my own

http://himeworks.wordpress.com/2013/11/26/custom-use-conditions/ (line 208) Though, it is not the best example cause the RPG::Skill class is full of parsing code. I can look for a cleaner example if you want.

Instead of storing the "use conditions" as an array straight in the RPG::Skill object, instead I simply store an array of Data_UseCondition objects and have each object handle any methods related to the use conditions (in this case, only evaluating whether the condition is met or not)

The difference is how you are asking this question:

1. Can the skill be used?2. Is the condition satisfied?For the first question, you are asking the skill to determine whether the conditions are satisfied.For the second question, you are asking the conditions to determine whether they are satisfied themselves.

It should be obvious that I could easily move that "eval_use_condition" method from the Data class straight into RPG::Skill, and then say

Code:
skill.eval_use_condition
instead of saying

Code:
skill.use_conditions.each {|cond| cond.eval_use_condition }
Whether there is any real value in how it is designed is another matter.
 
Last edited by a moderator:

Users Who Are Viewing This Thread (Users: 0, Guests: 1)

Latest Threads

Latest Posts

Latest Profile Posts

Couple hours of work. Might use in my game as a secret find or something. Not sure. Fancy though no? :D
Holy stink, where have I been? Well, I started my temporary job this week. So less time to spend on game design... :(
Cartoonier cloud cover that better fits the art style, as well as (slightly) improved blending/fading... fading clouds when there are larger patterns is still somewhat abrupt for some reason.
Do you Find Tilesetting or Looking for Tilesets/Plugins more fun? Personally I like making my tileset for my Game (Cretaceous Park TM) xD
How many parameters is 'too many'??

Forum statistics

Threads
105,862
Messages
1,017,049
Members
137,569
Latest member
Shtelsky
Top