Some ways to store RGSS3 code strings in notetags

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
Most of us know what notetag is, and many of us know it can store RGSS3 string codes. But how it can be done? I want to share the 2 such ways I know so far. Just don't automatically take them as hard facts, as what I'm going to share is just my opinion.

Storing RGSS3 code strings in the notebox

Using this way, users need to write a complete RGSS3 code string for each notebox with each corresponding notetag. As sometimes it's just outright impossible to fit in just 1 line, the notetag must be able to read the multiline RGSS3 code strings. The below are some examples:

Yanfly Engine Ace - Skill Restrictions

# <restrict eval># string# string# </restrict eval># For the more advanced users, replace string with code to determine whether# or not the skill is restricted. If multiple lines are used, they are all# considered part of the same line.
Code:
    RESTRICT_EVAL_ON  = /<(?:RESTRICT_EVAL|restrict eval)>/i    RESTRICT_EVAL_OFF = /<\/(?:RESTRICT_EVAL|restrict eval)>/i
Code:
    self.note.split(/[\r\n]+/).each { |line|      case line      #---      when YEA::REGEXP::SKILL::COOLDOWN        @cooldown = $1.to_i      when YEA::REGEXP::SKILL::WARMUP        @warmup = $1.to_i      when YEA::REGEXP::SKILL::LIMITED_USES        @limited_uses = $1.to_i      #---      when YEA::REGEXP::SKILL::CHANGE_COOLDOWN        @change_cooldown[0] = $1.to_i      when YEA::REGEXP::SKILL::STYPE_COOLDOWN        @change_cooldown[$1.to_i] = $2.to_i      when YEA::REGEXP::SKILL::SKILL_COOLDOWN        @skill_cooldown[$1.to_i] = $2.to_i      #---      when YEA::REGEXP::SKILL::RESTRICT_IF_SWITCH        @restrict_any_switch.push($1.to_i)      when YEA::REGEXP::SKILL::RESTRICT_ANY_SWITCH        $1.scan(/\d+/).each { |num|        @restrict_any_switch.push(num.to_i) if num.to_i > 0 }      when YEA::REGEXP::SKILL::RESTRICT_ALL_SWITCH        $1.scan(/\d+/).each { |num|        @restrict_all_switch.push(num.to_i) if num.to_i > 0 }      #---      when YEA::REGEXP::SKILL::RESTRICT_EVAL_ON        @restrict_eval_on = true      when YEA::REGEXP::SKILL::RESTRICT_EVAL_OFF        @restrict_eval_off = true      else        @restrict_eval += line.to_s if @restrict_eval_on      #---      end    } # self.note.split
So the restrict eval can be used like this:

<restrict eval>if actor? && state?(x); if $game_variables[y] > z; auto_battle?; else; confusion?; end; else; @hp != mhp; end</restrict eval>
And @restrict_eval should become this:

if actor? && state?(x); if $game_variables[y] > z; auto_battle?; else; confusion?; end; else; @hp != mhp; end
State Add/Remove Commands(Written by Shaz)

# To execute a command when a state is added, add the following line to the# state's notes:# onadd: command## To execute a command when a state is removed, add the following line to the# state's notes:# onremove: command## command is a RGSS script command that will be eval'd, so it may consist of# any valid script command. It may extend over more than one line, and may# consist of several commands separated by semi-colons, but it must NOT be# broken by pressing the Enter key (just keep typing and let the editor put# in its own line breaks by default).
Code:
  #--------------------------------------------------------------------------  # * State Add Commands  #--------------------------------------------------------------------------  def state_add_command(state_id)    $data_states[state_id].note.split(/[\r\n]/).each do |line|      case line      when /onadd:\s*(.*)/i        eval($1)      end    end  end  #--------------------------------------------------------------------------  # * State Remove Commands  #--------------------------------------------------------------------------  def state_remove_command(state_id)    $data_states[state_id].note.split(/[\r\n]/).each do |line|      case line      when /onremove:\s*(.*)/i        eval($1)      end    end  end
Shaz also gave some examples:

# Examples:# - this will just print a line of text to the console when the state is added# onadd: p 'state added'## - this will print a line of text to the console AND reserve a common event# to be run on the map when the state is removed - note it's done over# two commands/lines (the second line will automatically wrap to 2 lines)# onremove: p 'state removed'# onremove: $game_temp.reserve_common_event(1)## - this will run one of two commands based on the battler's current hp when# a state is added (enter key is not used here to go to a second line)# onadd: if hp_rate < 0.5; p 'low hp - need topup';# else; p 'hp good'; end;

Storing RGSS3 code strings in the script's user editable region

Using this way, users need to write all the complete or partial RGSS3 code strings in the script's user editable region, each of which is stored by a constant added by users. Then the notetag using those RGSS3 code strings need to store the name of the constant storing the RGSS3 code string to be used. For example:

DoubleX RMVXA State Triggers

#------------------------------------------------------------------------------|# * State Notetags: |# 1. <timing state trigger: STCX, STAX> |# - Sets a state to trigger STAX when timing and STCX are met |# - timing can be either add, turn or remove |# - add means the state's just added |# - turn means the state's remaining turn's just reduced by 1 |# - remove means the state's just removed |# - STCX can be set in State Trigger Condition Notetag Values |# - STAX can be set in State Trigger Action Notetag Values |#------------------------------------------------------------------------------|
Code:
    #--------------------------------------------------------------------------|    #  State Trigger Condition Notetag Values                                  |    #  - Setups STCX used by this script's notetags                            |    #--------------------------------------------------------------------------|    # STCX are used at:    # 1. Game_Battler    #    - eval(trigger[timing][1]) if trigger[timing] &&     #      eval(trigger[timing][0]) in eval_state_triggers    # STCX are strings of RGSS3 codes    # STCX names can only use alphanumeric characters    # The below STCX are examples added to help you set your STCX    # You can freely use, rewrite and/or delete these examples    # Sets the state trigger condition as always true    STC1 = "true"    # Sets the state trigger condition as always false    STC2 = "false"    # Sets the state trigger condition as needing switch with id x to be on    STC3 = "$game_switches[x]"    # Adds new STCX here        #--------------------------------------------------------------------------|    #  State Trigger Action Notetag Values                                     |    #  - Setups STAX used by this script's notetags                            |    #--------------------------------------------------------------------------|    # STAX are used at:    # 1. Game_Battler    #    - eval(trigger[timing][1]) if trigger[timing] &&     #      eval(trigger[timing][0]) in eval_state_triggers    # STAX are strings of RGSS3 codes    # STAX names can only use alphanumeric characters    # The below STAX are examples added to help you set your STAX    # You can freely use, rewrite and/or delete these examples    # Sets the state trigger action as what Special Effect Escape does    STA1 = "hide"    # Sets the state trigger action as calling common event with id    # common_event_id    STA2 = "$game_temp.reserve_common_event(common_event_id)"    # Sets the state trigger action as executing damage equal to the value of    # game variable with id x to self with type equal to that of skill with id    # equal to y    STA3 = "@result.clear; @result.make_damage($game_variables[x],             $data_skills[y]); execute_damage(self)"    # Adds new STAX here
If I want to add a state trigger with the timing, conditions and actions triple as add, STC1 and STA1, turn, STC2 and STA2, and remove, STC3 and STA3, to a state, I need to add the below notetags to that state's notebox:

<add state trigger: STC1, STA1><turn state trigger: STC2, STA2><remove state trigger: STC3, STA3>
The below shows how those notetags are read:

  #----------------------------------------------------------------------------|  #  New method: load_notetags_state_triggers                                  |  #  - Loads each <timing state trigger: STCX, STAX> notetag from its notebox  |  #----------------------------------------------------------------------------|  def load_notetags_state_triggers    # Stores all timing, STCX and STAX triples from matching lines sequentially    @state_triggers = { :add => [], :turn => [], :remove => [] }    st = "DoubleX_RMVXA::State_Triggers::"    @note.split(/[\r\n]+/).each { |line|      next unless line =~ /<(\w+) state trigger:\s*(\w+)\s*,\s*(\w+)\s*>/      @state_triggers[$1.to_sym].push([eval("#{st}#{$2}"), eval("#{st}#{$3}")])    }    #  end # load_notetags_state_triggers
Using the above notetag usage example, @state_triggers should become:

{ :add => ["true", "hide"], :turn => ["false", "$game_temp.reserve_common_event(common_event_id)"], :remove => ["$game_switches[x]", "@result.clear; @result.make_damage($game_variables[x],            $data_skills[y]); execute_damage(self)"]}

Comparison between the above 2 ways

I'm not going to say which one is objectively better, as I think that each has their own advantages and disadvantages, making some cases preferring the 1st way and some preferring the 2nd one.

Advantages of the 1st way over the 2nd one

1. More straightforward

To set a new RGSS3 code string to be used by a notetag, you just need to directly put it inside that notetag in the 1st way, while you first need to add a new constant storing that RGSS3 code string, then put the name of that constant inside that notetag in the 2nd way.

2. Smaller script user editable region

In the 1st way, no RGSS3 string code's stored in the script user editable region at all, making that region more user friendly; In the 2nd way, all those RGSS3 string codes are stored in the script user editable region, making that region less user friendly, unless the users don't mind having a long list of RGSS3 string codes in the script user editable region and have at least little scripting proficiency(In all of my scripts using the 2nd way, decent scripting proficiency's needed to fully utilize it).

Advantages of the 2nd way over the 1st one

1. Better RGSS3 string code reusability

If many notetags uses the same RGSS3 string code and you want to change that code itself(and not just specific notetags using it), you'll have to apply that change to all notetags using it in the 1st way, while you just need to change the constant storing that RGSS3 string code in the 2nd way.

2. Better RGSS3 string code flexibility

In the 1st way, you always have to put complete RGSS3 string codes into notetags using them; In the 2nd way, you can break them into new constants storing their building blocks, and build the constants storing the complete RGSS3 code strings by combining those storing their building blocks. When many complete RGSS3 string codes share the same common part and you want to change that common part, you'll have to change that part in all those complete RGSS3 string codes, and apply the changes to all notetags using them in the 1st way, while you can break that common part as a constant storing it, make all constants storing those complete RGSS3 string codes use that constant storing that common part, and just change that constant storing that common part in the 2nd way.

For example, I need to use the below 3 complete RGSS3 string codes:

"code_part_1; code_part_2"
Code:
"code_part_2; code_part_3"
Code:
"code_part_3; code_part_1"
In the 1st way, you always have to input each of the above 3 codes completely into its corresponding notetags; In the 2nd way, you can break the above 3 codes into this:

CP1 = "code_part_1"CP2 = "code_part_2"CP3 = "code_part_3"CC1 = CP1 + "; " + CP2CC2 = CP2 + "; " + CP3CC3 = CP3 + "; " + CP1
Then just put CC1, CC2 and CC3 into their corresponding notetags.

In the 1st way, When I want to change the common part code_part_2, I'll have to change both the 1st and 2nd complete RGSS3 code strings using it and apply the changes to all notetags using those complete RGSS3 code strings:

"code_part_1; edited_code_part_2"
Code:
"edited_code_part_2; code_part_3"
In the 2nd way, I just have to change CP2:

CP2 = "edited_code_part_2"

What do you think about the aforementioned 2 ways? Do you have other ways to store the RGSS3 code strings in notetags? Again, I won't claim what I said as absolute truths, so feel free to correct me :)
 
Last edited by a moderator:

??????

Diabolical Codemaster
Veteran
Joined
May 11, 2012
Messages
6,513
Reaction score
3,202
First Language
Binary
Primarily Uses
RMMZ
I seen something somewhere about how using lambda with eval made for a much more efficient and less error prone eval-ing technique.  Cant remember its details exactly cause I tend not to eval things, but it was somewhat similar to this. 

eval( "lambda {#{code_string}}" )In the past though, I have used methods similar to this one...

class RPG::BastItem def scriptnote @scriptnote ||= note[/<code:(.*)>/i] ? $1 : '' end def to_eval_scriptnote eval(scriptnote) endendFairly self explanitory. :)
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
The problem of using lambda is its binding.

If the lambda is created and then immediately called at runtime, using it would be pointless and using simple eval would be better.

If the lambda is created when reading notetags and called at runtime, its binding would be the data(RPG::BaseItem or its subclasses) reading those notetags.

For instance, if the RGSS3 code string needs Game_BattleBase or its subclasses as the binding, error will occur upon trying to create that lambda as many Game_BattleBase(or its subclasses) stuffs don't exist in RPG::BaseItem or its subclasses.

While passing the battler to the lambda is easy(so the binding's problem's solved), each of the battler's instance variables will need a corresponding reader. While doing those in your script's easy, doing those in others' scripts can be painful like hell, and not doing so can raise lots of compatibility issues. Also, there are cases where tons of local variables need to be used and thus passed as well, and sometimes when the lambda's used in different contexts, different sets of local variables need to be passed. Of course it can still be done, but it can also easily become a mess :)

If the lambda is created when, say, initializing a battler, then each lambda would be needed for each notetag of each data. If there are many of such data, each using many of such notetags, the number of lambdas for each battler would be insane.

I tried to use RubyVM::InstructionSequence, but again, it seems to me that it always uses the top level binding(Main::object) and I failed to change it to use its runtime caller(e.g.: Scene_Battle) as the binding.

Also, If a method corresponding to a RGSS3 string code used by a notetag's created upon reading notetags, then if a data uses lots of notetags, it'll have lots of methods.

While I try to avoid using eval at runtime as much as I can, it seems to me that in this case, directly evaluating the RGSS3 code strings used by notetag is the best way I know so far(the most painful trouble for me when using lambda is the compatibility issues) lol
 
Last edited by a moderator:

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,846
First Language
English
While passing the battler to the lambda is easy(so the binding's problem's solved), each of the battler's instance variables will need a corresponding reader.
What do you mean by "corresponding reader"? Can you provide an example?
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
What do you mean by "corresponding reader"? Can you provide an example?
For example, Script A added a new instance variable @var_a under Game_Battler, and originally, calling battler.var_a(battler being a Game_Battler instance) will return an error as Script A doesn't make a reader for @var_a(like attr_reader :var_a or method var_a), meaning @var_a isn't accessible outside of its instance.

Suppose in Script B, there's a notetag storing RGSS3 string codes that can use Game_Battler instance variables.

Using simple eval to evaluate that RGSS3 string code at runtime could be like this:

class Game_Battler < Game_BattlerBase def script_b_run_notetag(item) eval(item.script_b_notetag) endend
Suppose script_b_notetag contains the below RGSS3 code string:

@var_a ? does_something : does_something_else
eval(item.script_b_notetag) will run script_b_notetag just fine.

Now, calling lambda to evaluate that RGSS3 string code at runtime could be like this:

class RPG::BaseItem def read_notetags @note.split(/[\r\n]+/).each { |line|      next unless line =~ Script_B::REGEXP      @script_b_notetag = eval("lambda { |battler| #{$1} }") return    } endendclass Game_Battler < Game_BattlerBase def script_b_run_notetag(item) item.script_b_notetag.call(self) endend
Suppose script_b_notetag contains the below RGSS3 code string:

battler.var_a ? does_something : does_something_else
item.script_b_notetag.call(self) will return no method error(undefined method var_a for battler), as there's no method var_a or attr_reader :var_a. As @var_a is a new instance variable in Script A, and script_b_notetag belongs to Script B, if Script B uses lambda to call script_b_notetag and wants to be compatible with Script A, it'll have to add method var_a or attr_reader :var_a to solve the compatibility issue between Script A and Script B.

Unless compatibility issues isn't an important concern for a given script, its developers will probably have a hard time solving even just a portion of them, considering the number of scripts each with the number of new instance variables that aren't accessible outside of that instance.
 
Last edited by a moderator:

Another Fen

Veteran
Veteran
Joined
Jan 23, 2013
Messages
564
Reaction score
275
First Language
German
Primarily Uses
You can also match multiple lines with a regular expression without having to split the note into lines first, which might be a bit more comfortable in some cases:

note = "<tag> thisis so long it does not fit in one line!</tag>" note[/<tag>(.*?)<\/tag>/im, 1] # => " this\nis so long it does \nnot fit in one line!\n"However, this obviously does not remove the newline characters which - considering the width of the notebox - might not be desireable in all cases.

I'd probably use the notebox for script codes when they are supposed to be not too long, tied to a specific item and probably have to be fine-tuned alongside with the item (like specific damage formulas).

After all, you can also mix those up, creating configurable methods or constants for general formulas in the script editor and refer to them within the notebox-formula if necessary.

About procs:
One can still use  instance_exec  to evaluate a proc in a different context:

block = proc { p @states }

battler.instance_exec(&block)  # => []

However, procs can't be saved using Marshal, so they must not be held by savestate relevant game objects, which can become an issue sometimes.
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
I think I've to articulately elaborate the lambda binding issue to clean somethings up :)

Situation

I want my scripts to be compatible with as many others' custom scripts as possible. One way is to proactively find all incompatible scripts and compatibility reports, and fix all those compatibility issues, another way is to make mine to have the best compatibility possible. Of course, both ways should be used, but given the number of others' custom scripts, I want to rely more on the latter way.
Scenario

Suppose a user wants to use DoubleX RMVXA State Triggers with YSA Battle System: Predicted Charge Turn Battle, which has a new Game_Battler instance variable @charging_pctb, which isn't accessible outside of its battler instance, as it doesn't have attr_accessor, attr_reader, attr_writer, def charging_pctb, def charging_pctb=, or something like those.

Now suppose that user wants to use the below notetag in state A:

<turn state trigger: STC1, STA1><turn state trigger: STC2, STA2>
Where STC1, STC2, STA1 and STA2 are:

STC1 = "@charging_pctb"STC2 = "!#{STC1}"STA1 = "does_something" # I don't care what it actually doesSTC2 = "does_something_else" # I don't care what it actually does as well

Problem

Everything works fine, except that the performance gets slightly hurt, when that notetag value's used by simple eval:

#----------------------------------------------------------------------------| # New method: load_notetags_state_triggers | # - Loads each <timing state trigger: STCX, STAX> notetag from its notebox | #----------------------------------------------------------------------------| def load_notetags_state_triggers # Stores all timing, STCX and STAX triples from matching lines sequentially @state_triggers = { :add => [], :turn => [], :remove => [] } st = "DoubleX_RMVXA::State_Triggers::" @note.split(/[\r\n]+/).each { |line| next unless line =~ /<(\w+) state trigger:\s*(\w+)\s*,\s*(\w+)\s*>/ @state_triggers[$1.to_sym].push([eval("#{st}#{$2}"), eval("#{st}#{$3}")]) } # end # load_notetags_state_triggers
Code:
  #----------------------------------------------------------------------------|  #  New method: eval_state_triggers                                           |  #  - Triggers each state action when each respective condition's met         |  #----------------------------------------------------------------------------|  # state_id: The id of the state triggering its actions  # timing: The timing of the state triggering its actions  def eval_state_triggers(state_id, timing)    # Evaluates each STCX to see if its corresponding STAX should be evaluated    $data_states[state_id].state_triggers[timing].each { |trigger|      eval(trigger[1]) if eval(trigger[0])    }    #  end # eval_state_triggers
Suppose I want to use lambda instead, however. But where to place the lambda? I've to place it in either Game_Battler(notetag user) or RPG::State(notetag owner), as placing it in any other place obviously makes no sense at all.

1. Game_Battler

Suppose I place it in Game_Battler. Then when should I initialize and remove that lambda?

Let's say I initialize it when a state's added to a battler and remove it when that state's removed from that battler:

#----------------------------------------------------------------------------| # Alias method: reset_state_counts | #----------------------------------------------------------------------------| alias reset_state_counts_state_triggers reset_state_counts def reset_state_counts(state_id) reset_state_counts_state_triggers(state_id) ## ADDED TO ILLUSTRATE THE LAMBDA BINDING PROBLEM if @states.include?(state_id) @state_triggers[state_id] = { :add => [], :turn => [], :remove => [] } @state_triggers[state_id].each { |timing, triggers| triggers.push( eval("-> { "#{$data_states[state_id].state_triggers[timing][0]}" }")) triggers.push(        eval("-> { "#{$data_states[state_id].state_triggers[timing][1]}" }")) } end ## # Added to trigger the add actions when the add conditions are met eval_state_triggers(state_id, :add) if @states.include?(state_id) # end # reset_state_counts
Code:
  #----------------------------------------------------------------------------|  #  Alias method: erase_state                                                 |  #----------------------------------------------------------------------------|  alias erase_state_triggers erase_state  def erase_state(state_id)    # Added to store the state existence flag right before it's erased    trigger = @states.include?(state_id)    #    erase_state_triggers(state_id)    # Added to trigger the remove actions when the remove conditions are met    eval_state_triggers(state_id, :remove) if trigger    #    ## ADDED TO ILLUSTRATE THE LAMBDA BINDING PROBLEM    @state_triggers.delete_key(state_id) if trigger    ##  end # erase_state
Code:
  #----------------------------------------------------------------------------|  #  Alias method: clear_states                                                |  #----------------------------------------------------------------------------|  alias clear_states_triggers clear_states  def clear_states    # Added to store the state array right before it's cleared    last_states = @states    #    clear_states_triggers    # Added to trigger the remove actions when the remove conditions are met    return unless last_states    last_states.each { |state_id| eval_state_triggers(state_id, :remove) }    #    ## ADDED TO ILLUSTRATE THE LAMBDA BINDING PROBLEM    @state_triggers = {}    ##  end # clear_states
Then I change eval_state_triggers to use those lambda instead:

#----------------------------------------------------------------------------| # New method: eval_state_triggers | # - Triggers each state action when each respective condition's met | #----------------------------------------------------------------------------| # state_id: The id of the state triggering its actions # timing: The timing of the state triggering its actions def eval_state_triggers(state_id, timing) ## REWRITTEN TO ILLUSTRATE THE LAMBDA BINDING PROBLEM @state_triggers[state_id][timing].each { |trigger| trigger[1].call if trigger[0].call } ## end # eval_state_triggers
Everything works fine, except one - If a battle has 12 battlers, each having 4 states, each using 3 state triggers(each having 2 lambdas), then there will be 12 * 4 * 3 * 2 = 288 extra lambdas stored in the memory just used for running those state triggers. The CPU's certainly ok about that, but the memory? Probably not so. You think that's extreme? Unfortunately, I've played few RMVXA games long time ago where such scenario isn't really rare(of course they don't use state triggers, but you should see my point).

2. RPG::State

Now suppose I place it in RPG::State. Then the timing's obvious - when reading the state trigger notetags:

#----------------------------------------------------------------------------| # New method: load_notetags_state_triggers | # - Loads each <timing state trigger: STCX, STAX> notetag from its notebox | #----------------------------------------------------------------------------| def load_notetags_state_triggers # Stores all timing, STCX and STAX triples from matching lines sequentially @state_triggers = { :add => [], :turn => [], :remove => [] } st = "DoubleX_RMVXA::State_Triggers::" @note.split(/[\r\n]+/).each { |line| next unless line =~ /<(\w+) state trigger:\s*(\w+)\s*,\s*(\w+)\s*>/ ## REWRITTEN TO ILLUSTRATE THE LAMBDA BINDING PROBLEM @state_triggers[$1.to_sym].push([ eval("-> battler { "#{eval("#{st}#{$2}")}" }"), eval("-> battler { "#{eval("#{st}#{$3}")}" }")]) ## } # end # load_notetags_state_triggers
Then I change eval_state_triggers to use those lambdas instead:

#----------------------------------------------------------------------------| # New method: eval_state_triggers | # - Triggers each state action when each respective condition's met | #----------------------------------------------------------------------------| # state_id: The id of the state triggering its actions # timing: The timing of the state triggering its actions def eval_state_triggers(state_id, timing) # Evaluates each STCX to see if its corresponding STAX should be evaluated $data_states[state_id].state_triggers[timing].each { |trigger| ## REWRITTEN TO ILLUSTRATE THE LAMBDA BINDING PROBLEM trigger[1].call(self) if trigger[0].call(self) ## } # end # eval_state_triggers
Clearly, initializing a lambda with STC1, which is "@charging_pctb", as the RGSS3 code, under RPG::State, will be problematic when that lambda's run, as now @charging_pctb will be interpreted as an instance variable of RPG::State, causing @charge_pctb in that lambda to always return nil.

Now suppose I change STC1 to this:

STC1 = "battler.charging_pctb"
While initializing a lambda with this modified STC1 is ok, calling that lambda will return no method error(undefined method charging_pctb for battler), as the battler has no def charging_pctb, attr_accessor :charging_pctb or attr_reader :charging_pctb.

This alone can be solved by adding either of them, but to me, YSA Battle System: Predicted Charge Turn Battle is other's custom script(Yami in this case), and it's just one of the others, which are extremely abundant. I either have to:

Add those instance variable getters and/or setters on a compatibility report basis,

Proactively hunt for as many such scripts and add those instance variable getters and/or setters for each of them, or

Make a separate script helping users to set those instance variable getters and/or setters they need themselves:

# Adds an attr_accessor, attr_reader or attr_writer to all instance variables needing them # It can't be chnaged once set # It must be a hash of strings with the attr type and instance variable name # Its keys must be the class and superclasses(if any) using the instance variable DEFS_ATTR = { # General Form: # [:method_class, :super_class] => [ # "attr_type :instance_variable" # ] [:Game_Battler, :Game_BattlerBase] => [ "attr_reader :charging_pctb" # Adds new instance variables here ] # Adds new classes here }Then I add the below abstract instance variable getter/setter creator:

# Adds getters and/or setters for instance variables in DEFS_ATTR DEFS_ATTR.each { |klass, vars| vars.each { |var| eval(%Q(class #{klass[0].id2name}#{klass[1] ? " < #{klass[1].id2name}" : ""} #{var}end )) } }
All the above 3 solutions has their own limitations:

Add instance variable getters and/or setters on a compatibility report basis - When some users find compatibility issues, they might just abandon the whole script altogether instead of reporting them to the developers, even if the latters are nice and active. Those wanting to target as many users as possible and/or wanting their scripts to be as compatible as possible might be unsatisfied in this case.

Proactively hunt for as many such scripts and add those instance variable getters and/or setters - Given the amount of custom scripts, it's almost impossible for any developer team(let alone individual) to hunt for even a portion of them. Those wanting their scripts to be as compatible as possible might be unsatisfied in this case.

Make a separate script helping users to set those instance variable getters and/or setters they need themselves - Although users using multiple scripts together in ways similar to be above scenario likely have at least some scripting proficiency and should have no problem setting those instance variable getters and/or setters they need themselves, some of them might still be unhappy about having to do those extra works themselves, as they probably also know they won't have to do so if the script uses simple eval instead of lambda. Those wanting to target as many users as possible might be unsatisfied in this case.


Summary

While the lambda binding problem can be solved, so far I don't find any solution that won't raise other problems yet. It alone doesn't mean none of the solution I know works - they can work well in many cases, I just think using lambda isn't always better to using simple eval in all possible cases. Instead, a thorough case by case analysis is probably needed when deciding whether simple eval or lambda should be used. After all, performance isn't always everything, so developers should try to find a working balance among all the programming aspects using a case by case appraoch.
P.S.: If I get what AnotherFen means, load_notetags_state_triggers should be changed into this:

#----------------------------------------------------------------------------| # New method: load_notetags_state_triggers | # - Loads each <timing state trigger: STCX, STAX> notetag from its notebox | #----------------------------------------------------------------------------| def load_notetags_state_triggers # Stores all timing, STCX and STAX triples from matching lines sequentially @state_triggers = { :add => [], :turn => [], :remove => [] } st = "DoubleX_RMVXA::State_Triggers::" @note.split(/[\r\n]+/).each { |line| next unless line =~ /<(\w+) state trigger:\s*(\w+)\s*,\s*(\w+)\s*>/ ## REWRITTEN TO ILLUSTRATE AnotherFen's SOLUTION @state_triggers[$1.to_sym].push([ eval("-> battler { battler.instance_exec { "#{eval("#{st}#{$2}")}" } }"), eval("-> battler { battler.instance_exec { "#{eval("#{st}#{$3}")}" } }")]) ## } # end # load_notetags_state_triggers
For example, the lambda storing STC1 will be like this:

-> battler { battler.instance_exec{ @charging_pctb } }
Then I'll have to benchmark and check its performance :D

P.P.S: My informal benchmark result's as follows:

class Test def initialize @test = "test" end def control block = -> x { eval("@test") } p("eval") 10_000_000.times(&block) p("eval") end # Roughly 66 secondsendtest = Test.newlambda_instance_exec = -> test { test.instance_exec{ @test } }block = -> x { lambda_instance_exec.call(test) }p("lambda instance_exec")100_000_000.times(&block)p("lambda instance_exec")# Roughly 81 seconds
So it seems to me that AnotherFen's way does work(I've checked that @test correctly returns "test" as well) :D
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
So after extensive testings, I decided to use AnotherFen's method. I used DoubleX RMVXA State Triggers as the lab rat:

#----------------------------------------------------------------------------| # New method: load_notetags_state_triggers | # - Loads each <timing state trigger: STCX, STAX> notetag from its notebox | #----------------------------------------------------------------------------| def load_notetags_state_triggers # Stores all timing, STCX and STAX triples from matching lines sequentially @state_triggers = { :add => [], :turn => [], :remove => [] } st = "DoubleX_RMVXA::State_Triggers::" @note.split(/[\r\n]+/).each { |line| next unless line =~ /<(\w+) state trigger:\s*(\w+)\s*,\s*(\w+)\s*>/ ## REWRITTEN TO ILLUSTRATE AnotherFen's SOLUTION @state_triggers[$1.to_sym].push([ eval("-> battler { battler.instance_exec { "#{eval("#{st}#{$2}")}" } }"), eval("-> battler { battler.instance_exec { "#{eval("#{st}#{$3}")}" } }")]) ## } # end # load_notetags_state_triggers
Code:
  #----------------------------------------------------------------------------|  #  New method: eval_state_triggers                                           |  #  - Triggers each state action when each respective condition's met         |  #----------------------------------------------------------------------------|  # state_id: The id of the state triggering its actions  # timing: The timing of the state triggering its actions  def eval_state_triggers(state_id, timing)    # Evaluates each STCX to see if its corresponding STAX should be evaluated    $data_states[state_id].state_triggers[timing].each { |trigger|      trigger[1].call(self) if trigger[0].call(self)    }    #  end # eval_state_triggers
In some really extreme test cases, the average fps increased by roughly 2(from roughly 81 to roughly 83) in my machine(i7-3820 + 2 * 2GB DDR3 1333 + GTX 550Ti), which is at least better than none.

Applying the above lambda instance exec trick to Yanfly Engine Ace - Skill Restriction could be something like this:

self.note.split(/[\r\n]+/).each { |line| case line #--- when YEA::REGEXP::SKILL::COOLDOWN @cooldown = $1.to_i when YEA::REGEXP::SKILL::WARMUP @warmup = $1.to_i when YEA::REGEXP::SKILL::LIMITED_USES @limited_uses = $1.to_i #--- when YEA::REGEXP::SKILL::CHANGE_COOLDOWN @change_cooldown[0] = $1.to_i when YEA::REGEXP::SKILL::STYPE_COOLDOWN @change_cooldown[$1.to_i] = $2.to_i when YEA::REGEXP::SKILL::SKILL_COOLDOWN @skill_cooldown[$1.to_i] = $2.to_i #--- when YEA::REGEXP::SKILL::RESTRICT_IF_SWITCH @restrict_any_switch.push($1.to_i) when YEA::REGEXP::SKILL::RESTRICT_ANY_SWITCH $1.scan(/\d+/).each { |num| @restrict_any_switch.push(num.to_i) if num.to_i > 0 } when YEA::REGEXP::SKILL::RESTRICT_ALL_SWITCH $1.scan(/\d+/).each { |num| @restrict_all_switch.push(num.to_i) if num.to_i > 0 } #--- when YEA::REGEXP::SKILL::RESTRICT_EVAL_ON @restrict_eval_on = true when YEA::REGEXP::SKILL::RESTRICT_EVAL_OFF @restrict_eval_off = true else @restrict_eval += line.to_s if @restrict_eval_on #--- end } # self.note.split ## ADDED TO USE LAMBDA WITH INSTANCE EXEC INSTEAD @restrict_eval = eval("-> battler { battler.instance_exec { "#{@restrict_eval}" } }") if @restrict_eval != "" ##
Code:
  #--------------------------------------------------------------------------  # new method: restrict_eval?  #--------------------------------------------------------------------------  def restrict_eval?(skill)    return false if skill.restrict_eval == ""    ## REWRITTEN TO USE LAMBDA WITH INSTANCE EXEC INSTEAD    return skill.restrict_eval.call(self)    ##  end
However, using the above trick in Shaz's State Add/Remove Commands would be pointless, as it only reads the notetags right before using them, implying that using the above trick there would mean creating a lambda right after reading but right before using the notetag value, and that lambda would be gone right after being called, effectively defeating the very purpose of using lambda - increasing the code performance.

Although I've no idea why notetag values would even be only read right before being used in the first place, in cases where this way's necessary or highly preferred for some solid reasons, the above trick won't be useful :)
 
Last edited by a moderator:

??????

Diabolical Codemaster
Veteran
Joined
May 11, 2012
Messages
6,513
Reaction score
3,202
First Language
Binary
Primarily Uses
RMMZ
I personally think that technique looks extremely messy. But its certainly good information to know ^_^
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
I personally think that technique looks extremely messy. But its certainly good information to know ^_^
Maybe it's just because my implementations of that technique are extremely messy lol

Anyway, the general form can be something like this:

@note_val = eval("-> user { user.instance_exec { "#{note_val}" } }")I hope it looks at least slightly less messy :)
 
Last edited by a moderator:

??????

Diabolical Codemaster
Veteran
Joined
May 11, 2012
Messages
6,513
Reaction score
3,202
First Language
Binary
Primarily Uses
RMMZ
Still seems a little clunky. Perhaps something like this..

def note_val @note_val ||= set_note_valenddef set_note_val eval(%Q[-> user { user.instance_exec {#{note_val}}}])endUses more code/lines, but feels neater imo. and considering the eval method would only ever be called once anyway, its not really any kinda performance hit. :p
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,268
Reaction score
1,422
First Language
English
Primarily Uses
RMXP
Consider going the path of preprocessing.

Try playing around with generating the code that will run before actually running the game.

Deployment does become somewhat more tedious, but maybe the gains offset the cost.

*hugs*
 

Galenmereth

Retired
Veteran
Joined
May 15, 2013
Messages
2,248
Reaction score
2,158
First Language
English
Primarily Uses
N/A
To be honest it just seems like bad practice in general to put so much logic into the notetag box. I see very few gains and a lot of problems. Even a statement as simple as "something ? do_first_thing : do_other_thing" is a very bad match for the tiny notetag box.

I'd prefer to use the notetag box for simple things like tags and labels, that sort of thing, which there are no other options for in the default form fields. Anything logic driven ought to be done in script files in the editor. I know some battle systems almost have to use this to configure all the different bits and bobs, but how about then going about it more modularly? As an example, let's say you want to put the character animations for a skill in the notetag field for a sideways battle system. Isn't this preferable to having tons of stuff in the notetag box? 

<animations: jump_to_target, attack, jump_back>Then the actual complicated actions "jump_to_target", "attack" and "jump_back" are defined in the script editor, where there is more room and code highlighting. This way the notetag remains perfectly readable, and you don't introduce all these potential syntax problems to the end user. Keep the notetag "dumb" and simple is my recommendation.
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
1 point to be added regarding only reading notetag values right before using them:

Right now I can think of 1 benefit of doing so - reducing the memory usage by not storing the notetag values at all. Although when talking about RMVXA used by modern computers, time performance's much, much more important than memory usage in most cases, when there are lots of such notetags(like 1000+) and all those notetag values are always rarely used(like once per battle), the reduction of memory usage can actually outweigh the increase of time performance.

On a side note, I don't think Shaz's State Add/Remove Commands is one such case lol
2 points to be added regarding using lambda:

1. Lambda isn't always better than direct eval

When using lambda, one's trading memory usage for time performance. Storing

eval("-> user { user.instance_exec { "#{note_val}" } }")or

eval("-> { "#{note_val}" }")clearly costs more memory than storing

note_valBesides, normally, the notetag values are stored thorough the whole game execution.

In general, the more frequent the use of the notetag values, the more justified the use of lambda over direct eval, and vice versa. If at least some notetag values are likely frequently used(like multiple times per frame), then using lambda is likely justified; If all notetag values are always rarely used(like once per battle), then using direct eval is likely justified.

Although when talking about RMVXA used by modern computers, time performance's much, much more important than memory usage in most cases, it doesn't mean we don't have to worry about the latter at all. When there are lots of notetags(like 1000+), and all notetags are always rarely used, the memory usage increase thorough the whole game execution can be nontrivial, but the time performance gain can be next to nothing, if lambda's used instead of direct eval.
2. instance_exec should only be used with solid reasons

My below informal benchmark in my machine might show a reason behind this:

class Test def initialize @test = "test" endendtest = Test.newblock1 = -> test { test.instance_exec{ @test } }block2 = -> x { block1.call(test) }p("lambda instance_exec")100_000_000.times(&block2)p("lambda instance_exec")# Roughly 81 secondstest = -> { "test" }block = -> x { test.call }p("lambda")100_000_000.times(&block)p("lambda")# Roughly 47 seconds
When a piece of code stored as a notetag value needs to be run under a specific instance context(like a Game_Battler instance), using instance_exec or similar tricks is likely necessary; When that piece of code can be run under any context(like using BattleManager, $game_party, $game_troop, $game_system, $game_temp, $game_switches, $game_variables, etc), using instance_exec or similar tricks would likely harm the code performance.



Consider going the path of preprocessing.

Try playing around with generating the code that will run before actually running the game.

Deployment does become somewhat more tedious, but maybe the gains offset the cost.

*hugs*
May you please elaborate this? Perhaps with some examples :)

I also want to know how to use preprocessing to run notetag values storing codes needed to be executed at runtime, and those executions depend on the current states(states at runtime) of the instances using those notetag values :D

To be honest it just seems like bad practice in general to put so much logic into the notetag box. I see very few gains and a lot of problems. Even a statement as simple as "something ? do_first_thing : do_other_thing" is a very bad match for the tiny notetag box.

I'd prefer to use the notetag box for simple things like tags and labels, that sort of thing, which there are no other options for in the default form fields. Anything logic driven ought to be done in script files in the editor. I know some battle systems almost have to use this to configure all the different bits and bobs, but how about then going about it more modularly? As an example, let's say you want to put the character animations for a skill in the notetag field for a sideways battle system. Isn't this preferable to having tons of stuff in the notetag box? 

<animations: jump_to_target, attack, jump_back>Then the actual complicated actions "jump_to_target", "attack" and "jump_back" are defined in the script editor, where there is more room and code highlighting. This way the notetag remains perfectly readable, and you don't introduce all these potential syntax problems to the end user. Keep the notetag "dumb" and simple is my recommendation.
While I personally mostly agree with you, scripters need to consider their user preferences as well. Some users are just used to putting everything in the noteboxes, and some of them might even refuse to adapt to the more modular way when they just want to use short simple codes, as it might seem to be less straightforward and more work for them.

Of course, in some cases, the more modular way is indeed necessary. One such example is Yami's Battle Engine Symphony, as the symphony tags are just way too complicated and long to be exposed to the noteboxes and most users lol
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
1. Regarding lambda vs direct eval

The below is my informal benchmark testing the memory usage of storng lambdas of code strings vs storing nothing vs storing code strings in my machine(i7-3820 + 2 * 2GB DDR3 1333):

test = []#~ block = -> x { nil } # Control#~ block = -> x { test << -> { nil } } # Lambda#~ block = -> x { test << "nil" } # String1_000_000.times(&block)p("block")block = -> x { nil }100_000_000.times(&block)p("block")# Control: Roughly 39MB Memory# Lambda: Roughly 207MB Memory# String: Roughly 62MB Memory
It shows that in my machine, storing the code string "nil" directly and using lambda costs roughly (62 - 39)MB / 1,000,000 = 23B and (207 - 39)MB / 1,000,000 =176B respectively. Their difference is roughly 176B - 23B = 153B.

I've also tried to use several different code string with varying length and complexities in my machine, and the memory usage difference between the former and the latter stays roughly the same.

This might suggest that regardless of the code string itself, that memory usage difference is roughly 153B in my machine.

While storing 1,000,000 lambdas are clearly unrealistic, both of the aforementioned memory performances should still be noted.

2. Multiple layers of notetag value wrappers

Using a skill's symphony tags in Yami's Battle Symphony as an example:

<whole action>immortal: targets, truemove user: forward, waitpose: user, caststance: user, castanimation 81: user, waithide nonfocusicon create: user, weapon</whole action><target action>pose: user, 2h swingstance: user, attackicon: user, weapon, swinganimation 65: targetani wait: 3skill effect: dmgwait for animation</target action><follow action>animation 81: user, waitpose: user, 2h swingstance: user, attackicon: user, weapon, swinganimation 68: targetsani wait: 2skill effect: wholeani wait: 2skill effect: wholeani wait: 2skill effect: wholeani wait: 2skill effect: wholeshow nonfocusimmortal: targets, false</follow action>
Each symphony tag(immortal, move, pose, stance, etc) is a command pointing to its corresponding action sequences, so symphony tags can be regarded as notetag value wrappers, as technically speaking, those action sequences themselves can be notetag values. If there were no symphony tags, all those corresponding action sequences would need to be put into the noteboxes, causing Battle Symphony to be nearly unusable to almost anyone.

However, some users might feel/think that the existing symphony tags still take too much space from the noteboxes. In this case, another type of notetag value wrappers(symphony tag wrappers in this case) can be used as well. For example:

THUNDER_BLADE = %Q(<whole action>immortal: targets, truemove user: forward, waitpose: user, caststance: user, castanimation 81: user, waithide nonfocusicon create: user, weapon</whole action><target action>pose: user, 2h swingstance: user, attackicon: user, weapon, swinganimation 65: targetani wait: 3skill effect: dmgwait for animation</target action><follow action>animation 81: user, waitpose: user, 2h swingstance: user, attackicon: user, weapon, swinganimation 68: targetsani wait: 2skill effect: wholeani wait: 2skill effect: wholeani wait: 2skill effect: wholeani wait: 2skill effect: wholeshow nonfocusimmortal: targets, false</follow action>)
Then just put <custom symphony act: THUNDER_BLADE> in Thunder Blade's notebox.

Reading <custom symphony act: tags> notetag can be something like this:

#-------------------------------------------------------------------------- # new method: battle_symphony_initialize #-------------------------------------------------------------------------- def battle_symphony_initialize ## ADDED TO READ <custom symphony act: tags> read_custom_symphony_act ## create_default_animation create_default_symphony create_tags_symphony end
Code:
  #--------------------------------------------------------------------------  # new method: read_custom_symphony_act  #--------------------------------------------------------------------------  def read_custom_symphony_act    @custom_symphony_act = ""    @note.split(/[\r\n]+/).each { |line|      case line      when /<custom symphony act:\s*(\w+)\s*>/        @custom_symphony_act += "#{eval("SYMPHONY::CUSTOM_SYMPHONY_ACT::#{$1}")}                                "      end    }  end
Code:
  #--------------------------------------------------------------------------  # new method: create_default_animation  #--------------------------------------------------------------------------  def create_default_animation    @atk_animation_id1 = SYMPHONY::Visual::ENEMY_ATTACK_ANIMATION    @atk_animation_id2 = 0    ## REWRITTEN TO READ CUSTOM SYMPHONY ACTIONS AS WELL    [@note, @custom_symphony_act].each { |lines|      lines.split(/[\r\n]+/).each { |line|        case line        when REGEXP::SYMPHONY::ATK_ANI1          @atk_animation_id1 = $1.to_i        when REGEXP::SYMPHONY::ATK_ANI2          @atk_animation_id2 = $1.to_i        end      }    }    ##  end
Code:
  #--------------------------------------------------------------------------  # new method: create_tags_symphony  #--------------------------------------------------------------------------  def create_tags_symphony    ## REWRITTEN TO READ CUSTOM SYMPHONY ACTIONS AS WELL    [@note, @custom_symphony_act].each { |lines|      lines.split(/[\r\n]+/).each { |line|        case line        when REGEXP::SYMPHONY::SETUP_ANI_ON          @symphony_tag = true          @setup_actions_list = []          @setup_action_flag = true        when REGEXP::SYMPHONY::SETUP_ANI_OFF          @symphony_tag = false          @setup_action_flag = false        when REGEXP::SYMPHONY::WHOLE_ANI_ON          @symphony_tag = true          @whole_actions_list = []          @whole_action_flag = true        when REGEXP::SYMPHONY::WHOLE_ANI_OFF          @symphony_tag = false          @whole_action_flag = false        when REGEXP::SYMPHONY::TARGET_ANI_ON          @symphony_tag = true          @target_actions_list = []          @target_action_flag = true        when REGEXP::SYMPHONY::TARGET_ANI_OFF          @symphony_tag = false          @target_action_flag = false        when REGEXP::SYMPHONY::FOLLOW_ANI_ON          @symphony_tag = true          @follow_actions_list = []          @follow_action_flag = true        when REGEXP::SYMPHONY::FOLLOW_ANI_OFF          @symphony_tag = false          @follow_action_flag = false        when REGEXP::SYMPHONY::FINISH_ANI_ON          @symphony_tag = true          @finish_actions_list = []          @finish_action_flag = true        when REGEXP::SYMPHONY::FINISH_ANI_OFF          @symphony_tag = false          @finish_action_flag = false       #---        else          next unless @symphony_tag          case line          when REGEXP::SYMPHONY::SYMPHONY_TAG_VALUES            action = $1            value = $2.scan(/[^, ]+[^,]*/i)          when REGEXP::SYMPHONY::SYMPHONY_TAG_NONE            action = $1            value = [nil]          else; next          end          array = [action, value]          if @setup_action_flag            @setup_actions_list.push(array)          elsif @whole_action_flag            @whole_actions_list.push(array)          elsif @target_action_flag            @target_actions_list.push(array)          elsif @follow_action_flag            @follow_actions_list.push(array)          elsif @finish_action_flag            @finish_actions_list.push(array)          end        end      }    }    ##  end
On a side note, the above symphony tags wrapper(custom symphony actions) isn't implemented yet :)
 
Last edited by a moderator:

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

Latest Threads

Latest Posts

Latest Profile Posts

How many parameters is 'too many'??
Yay, now back in action Happy Christmas time, coming back!






Back in action to develop the indie game that has been long overdue... Final Fallacy. A game that keeps on giving! The development never ends as the developer thinks to be the smart cookie by coming back and beginning by saying... "Oh bother, this indie game has been long overdue..." How could one resist such? No-one c
So I was playing with filters and this looked interesting...

Versus the normal look...

Kind of gives a very different feel. :LZSexcite:
To whom ever person or persons who re-did the DS/DS+ asset packs for MV (as in, they are all 48x48, and not just x2 the pixel scale) .... THANK-YOU!!!!!!!!! XwwwwX

Forum statistics

Threads
105,853
Messages
1,016,986
Members
137,561
Latest member
visploo100
Top