discussion on how to 'properly' code in RM MV

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,042
First Language
French
Primarily Uses
RMMV
so Hi guys!


recently I saw many people coding in MV in many way's! 


not that it's bother me not at all I love see manies way's people code even myself I code in a certain way's who are totally not the convention..but let's not speak JS convention...what I want to speak is more the 'coding' mindset in RM we currently have...


what I want to share is the fact that people tend to say "protect" your code from being edited by the other....and ect...what's could be good for Website or any other application ...Although this..not a good mindset for when you are coding in MV...why? 


Simply because we are not alone...we are not only one programmer one plugin....we all "interact" with each other and we have to deal with many possible incompatibility ect...


Although people tend to use the anonymous function who are good for LOCK the global variable declared inside the plugin this what the intention it's was done for and when you code it properly anonymous function is candy ect.


Although when I see this : 


(function(){
function SomeClass() {this.initialize.apply(this,arguments);}
SomeClass.prototype.constructor = SomeClass;

SomeClass.prototype.initialize = function() {
// material
};
})();

// the problem with this function is that it's make it private to the PLUGIN only you can't edit it externally or make any compattibility snipset plugin for it.

// the "good" way's for at least left programmer access this said function for edit or make any kind of extentions plugin ect... would be to do that.

function SomeClass(){this.initialize.apply(this,arguments);}
SomeClass.prototype.constructor = SomeClass;

(function(){
SomeClass.prototype.initialize = function(){
//material
};
})();

// with this said method you can left anyone edit the class for compatibility ALTHOUGH when declaring new class it's useless to input anonymous function
// anonymous in my opinion should be use for alias / parameters
// or in best resort use a object for store your parameters.






SO this not really concern people who use modular coding such yanfly since their code are open.


and I am saying we MUST do that I am just saying...please stop making your code "freezed" or impossible to be edited...it's just make your plugin so much incompatible and a pain to work with...and honestly we should not have this stupid mindset of "protecting" our code in MV when...we are supposed to be a community who help each other honestly...


I respect other people coding...but just when you are declaring a new function who affect thing's such "menu" or battle....at least declare the new class outside of the anonymous function so this will be far more easier!


SO I mean I am open to speak about the way's to code in MV


Honestly I think in my opinion anonymous function should be restrict to alias or parameter stuff honestly...


or I mean you can also use the modular way's for store your parameters or any other way's lol they have so manies way's to input stuff....


Anyway's hope you guys can understand...I am not searching fight I am just stating my opinion.
 

Kino

EIS Game Dev
Veteran
Joined
Nov 27, 2015
Messages
556
Reaction score
795
First Language
English
Primarily Uses
RMMV
It makes a lot more sense when you put it that way.



The way I write my plugins is with the idea of "hiding code" in mind, but I'm glad to hear your opinion on this.
So,  when it comes to adding new classes to the original code base; I usually do it inside of my anonymous function. That function is then return to my namespace. On one hand I get what you mean. It is rather redundant. 

I'll take a page from your book, and have object prototypes like that exposed in my namespace so they can still be accessed.


KR = KR || {};
KR.Plugins = KR.Plugins || {};

(function($) {
$.Plugins.PluginName = function() {
//Plugin Code
//New Window
function KR_Window_BattleStatus() {
this.initialize.apply(this,arguments);
}
//Add prototype to namespace
$.Window_BattleStatus = KR_Window_BattleStatus;
};
//Execute plugin
})(KR);


This will return the object and all it's prototypes to the editor. Something like this I assume? I think it does solve the problem, but keeps some information hidden, but the file would have to be above other files that you want to use the information in. My main reason for this approach is to retain privacy and prevent conflicts.
 
Last edited by a moderator:

Victor Sant

Veteran
Veteran
Joined
Mar 17, 2012
Messages
1,694
Reaction score
1,452
First Language
Portuguese
Primarily Uses
I had a major issue with declaring new classes on an anonymous function  (i found this out recently when somene had a issue with my plugins): Loading save data.


When i stored an object of that new class into a variable of an object that will be saved (for example, a Game_Actor), when loading the save file, the object will be NOT a instance of the said class, it will be simply an object with all the data, but when the maker tried to call a function for that object, it returned an 'undefined is not a function' error.


So, from now, i'm going with the second way, since I have some vxa scripts that created new classes to store data, and I don't plan to change the structure when converting them to MV.
 

Victor Sant

Veteran
Veteran
Joined
Mar 17, 2012
Messages
1,694
Reaction score
1,452
First Language
Portuguese
Primarily Uses
Yeah, JSON.parse doesn't recognize classes, that are not available in public space. That's why my Self Variables plugin ends with this line:



window.Game_SelfVariables = Game_SelfVariables;
Nice to know this... now i don't know if i Keep doing things as i've been doing, or use this approach...
 

Iavra

Veteran
Veteran
Joined
Apr 9, 2015
Messages
1,797
Reaction score
863
First Language
German
Primarily Uses
Another important thing: The local class (inside the anonymous function) must have the exact same name as the one you register in public space. That's because classes are resolved by their name only.
 

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,042
First Language
French
Primarily Uses
RMMV
actually yes I do find the use of anonymous...but I despise them a LOTS due the fact and yanfly explained me


you can't access the content without having a 'exporter'


can't be test in framework test...(don't quite remember the name ) 


but I don't mind using it for alias ect...I do recognize it's a way's to make private stuff with JS who are normally almost all public...


Although funny facts it's ...each time you declare a "global var" it's slow the process. I had a interesting conversation with Hudell on this...that's each time you call them it's add them to the global space and slow the process (even in anonymous it's just make them inaccessible....but are still here!)


so let's say...10 or 5 small global variable is fine....but imagine you have 100 global variable? 


IT'S HERE it's begin to get problematic...


so I honestly think we should avoid to abuse of global variable like we was doing in Ruby 
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,848
First Language
English
I define all new "classes" in global space near the beginning, since that's the order that I read code.


/* Main party manager */
function Game_Parties() {
this.initialize.apply(this, arguments);
};

(function ($) {
// stuff
}( TH.PartyManager );


I also like using static functions, which are functionally the same as global variables, but I don't know if there's the performance issue you mentioned.


eg:


function Party() {}; // then add things to it

var Party = {}; // then add things to it


My JS coding style is largely influenced by the default plugins and me liking to have lots and lots of methods for everything even if each method only does one thing and is very trivial.
 
Last edited by a moderator:

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,042
First Language
French
Primarily Uses
RMMV
well properly "everything's" add to memory usage


static function are considerate as a static so they are not readed until they are call contrary to global who are statics...BUT ....each time you call them it's go in the whole global list for know wich one to take...it's kinda wierd I think they read EACH place they got call....


I don't quite remember all the conversation I had with Hudell :c
 

Iavra

Veteran
Veteran
Joined
Apr 9, 2015
Messages
1,797
Reaction score
863
First Language
German
Primarily Uses
My plugins are structured in a similar way than Hime's. Since i started my particle engine, my standard looks like this:


(function($, undefined) {
"use strict";
// check for dependencies.

// read plugin parameters, if any.

// declare private functions, that are not available for public.

$.MODULE = { // declare public classes/functions.
Class: function() { this.initialize(); }
};

$.MODULE.Class.prototype = { // define classes

initialize: function() {

}

};

// extend existing classes, if any.

// do static initialization, that depends on classes.

})(this.IAVRA || (this.IAVRA = {})); // create namespace, if it doesn't already exist.


The "undefined" parameter isn't really needed, though in older browsers it was possible to overwrite the global undefined, which is preserved that way.


Speaking of Yanfly, personally i think there's a lot of bad practice in that plugins:


- No encapsulation, forcing them to store everything inside the namespace, like this:


for (Yanfly.i = 1; Yanfly.i < 21; Yanfly.i += 1)


- No strict mode, making eval dangerous (among others).


- No technical documentation (at all).


- Adding lots of functions to existing classes, that aren't prefixed or uniquely marked in any way, which in turn forces all other plugin writers to do so.


There are some minor issues, too, like suboptimal looping or leaving our optional blocks, like this:


if (command === 'LetterSoundReset') $gameSystem.initMessageSounds();




Don't misunderstand me, i have huge respect for all the work Yanfly is putting into making all of these plugins, i simply don't agree with their standards.
 

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,042
First Language
French
Primarily Uses
RMMV
I actually neither agreed with yanfly standart too (I do respect his work though)  I only use them when I don't have the use of  anonymous in this 


like I said I strictly use anonymous when I don't have the choice to alias tons of stuff without making new classes 


for myself I an not a fan of nameSpace stuff in JS because this actually not equal the C# level so...


And honestly I don't follow the "Prevent people to edit my stuff" material for myself it's RM I am not interested in people who complain that they can't edit a part of my plugin so I input everything's open to edit.


my coding personally is totally inconsistent lol...for myself I store all my parameters in a class 


even if this class is also extending other stuff such how parameter works ect


my coding is awkward I have to admit lol.....


//==============================================================================
// ■ Emoji_Engine
//------------------------------------------------------------------------------
// the class who handle all the base of my plugins. Can be access via $emoji.
//==============================================================================

// anonymous function
(function(){

//----------------------------------------------------------------------------
// ○ alias function: get_pluginname
//----------------------------------------------------------------------------
var NP01 = Emoji_Engine.prototype.get_pluginname;
Emoji_Engine.prototype.get_pluginname = function() {
NP01.call(this);
this.message_params = this.set_PluginID('EEMV::AdvanceMessageSystem');
};

//----------------------------------------------------------------------------
// ● alias function: get_params
//----------------------------------------------------------------------------
var NPN01 = Emoji_Engine.prototype.get_params;
Emoji_Engine.prototype.get_params = function() {
NPN01.call(this);
// Get Bust Parameters.
this.enableBust = this.setBoolean(this.message_params,'Enable Bust');
this.bustFolder = this.setString(this.message_params,'Bust Folder');
this.bustPos = this.setArray(this.message_params,'Bust Position');
this.bustPriority = this.setString(this.message_params,'Bust Priority');
// Get Panorama Parameters.
this.enablePanorama = this.setBoolean(this.message_params,'Enable Panorama');
this.panoramaFolder = this.setString(this.message_params,'Panorama Folder');
};
//===============================================================================
// => END : Emoji_Engine
//===============================================================================


thing's like setBoolean are my classes function who extend the thing's who lacks in MV plugin manager.


//----------------------------------------------------------------------------
// ○ new function: setBoolean
//----------------------------------------------------------------------------
// * By default the Boolean can't recognize string's correctly. Pure example,
// * if you input Boolean(param); it's will not work. Why? Because boolean()
// * auto recognize a string as true. it's not eval the string content.
// * and sadly Plugin parameters are strings. So this method will
// * consider your param as a boolean and return a true or a false.
//
Emoji_Engine.prototype.setBoolean = function(PluginVar,ParamName) {
var n = PluginVar[ParamName];
var s = undefined;
if(n === 'true' || n === 'false'){
if(n === 'true'){
s = true;
} else {
s = false;
}
} else {
throw new Error(ParamName + ' is a boolean please set it to true or false.');
}
return s;
};

//----------------------------------------------------------------------------
// ○ new function: setArray
//----------------------------------------------------------------------------
// * Convert a list of number in a Array. It's transform the string into a
// * number for after convert the chain into a Array.
// * it's look like this in the pluginManager :
// * Paramcommand = 1,2,3,4
// * in the system it's will return this [1,2,3,4];
// * so after you can access the array easily by doing this.varname[0];
//
Emoji_Engine.prototype.setArray = function (Plugin_var,ParamName) {
return Plugin_var[ParamName].split(',').map( function (i) { return Number(i || 0); } );
};


and I will be bluntly honest....I am a lazy programmer I hate to have to do stuff  like "=== "true" || false" ect so I created function who simplify my method


and also....ALL my plugin have to have to be documented I hate not having comment in plugin I get lost ect
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,789
Reaction score
941
First Language
Chinese
Primarily Uses
N/A
Let's drop my 2 cents:


I'm currently using 2 styles - 1 with plugins having no performance critical sections(i.e.: hotspots) at all, and another with those having at least 1 performance critical section(s).


Implementation info for both styles(DoubleX RMMV Item Triggers):


/*============================================================================
* ## Plugin Implementations
* You need not edit this part as it's about how this plugin works
*----------------------------------------------------------------------------
* # Plugin Support Info:
* 1. Prerequisites
* - Some Javascript coding proficiency to fully comprehend this
* plugin
* 2. Function documentation
* - The 1st part describes why this function's rewritten/extended for
* rewritten/extended functions or what the function does for new
* functions
* - The 2nd part describes what the arguments of the function are
* - The 3rd part informs which version rewritten, extended or created
* this function
* - The 4th part informs whether the function's rewritten or new
* - The 5th part informs whether the function's a real or potential
* hotspot
* - The 6th part describes how this function works for new functions
* only, and describes the parts added, removed or rewritten for
* rewritten or extended functions only
* Example:
* /*----------------------------------------------------------------------
* * Why rewrite/extended/What this function does
* *----------------------------------------------------------------------*/
/* // arguments: What these arguments are
* functionName = function(arguments) { // Version X+; Hotspot
* // Added/Removed/Rewritten to do something/How this function works
* functionContents
* //
* } // functionName
*----------------------------------------------------------------------------*/




For those belonging the former, the implementation style will be something like this(DoubleX RMMV Item Triggers):


(function(IT) {

IT.DataManager = {};
var DM = IT.DataManager;

DM.isDatabaseLoaded = DataManager.isDatabaseLoaded;
DataManager.isDatabaseLoaded = function() {
// Rewritten
return DM.isDatabaseLoaded.apply(this, arguments) && DM.loadAllNotes();
//
}; // DataManager.isDatabaseLoaded

DM.loadAllNotes = function() {
[$dataSkills, $dataItems].forEach(function(type) {
type.forEach(function(data) {
if (data) { DM.loadItemNotes(data); }
});
});
return true;
}; // DM.loadAllNotes

// data: The data to have its notetags read
DM.loadItemNotes = function(data) {
var regExp = /< *(\w+) +item +trigger *: *(\w+) *, *(\w+) *>/i;
var timing, triggers;
data.meta.itemTriggers = {};
triggers = data.meta.itemTriggers;
data.note.split(/[\r\n]+/).forEach(function(line) {
if (!line.match(regExp)) { return; }
timing = RegExp.$1;
triggers[timing] = triggers[timing] || [];
triggers[timing].push([RegExp.$2, RegExp.$3]);
});
}; // DM.loadItemNotes

IT.BattleManager = {};
var BM = IT.BattleManager;

BM.startAction = BattleManager.startAction;
BattleManager.startAction = function() {
// Added
var item = this._subject.currentAction().item();
GBB.execItemTriggers.call(this._subject, item, "preBattle");
//
BM.startAction.apply(this, arguments);
}; // BattleManager.startAction

BM.endAction = BattleManager.endAction;
BattleManager.endAction = function() {
BM.endAction.apply(this, arguments);
// Added
var item = this._action ? this._action.item() : null;
if (!item) { return; }
GBB.execItemTriggers.call(this._subject, item, "postBattle");
//
}; // BattleManager.endAction

IT.Game_BattlerBase = {};
var GBB = IT.Game_BattlerBase;

/*------------------------------------------------------------------------
* Triggers each item action when each respective condition's met
*------------------------------------------------------------------------*/
// timing: The timing of the item triggering its actions
GBB.execItemTriggers = function(item, timing) {
var triggers = item.meta.itemTriggers[timing];
if (!triggers) { return; }
// Calls each ITCX to see if its ITAX should be called as well
triggers.forEach(function(trigger) {
if (IT[trigger[0]].call(this)) { IT[trigger[1]].call(this); }
}, this);
//
}; // GBB.execItemTriggers

IT.Scene_ItemBase = {};
var SIB = IT.Scene_ItemBase;

SIB.useItem = Scene_ItemBase.prototype.useItem;
Scene_ItemBase.prototype.useItem = function() {
GBB.execItemTriggers.call(this.user(), this.item(), "preMap"); // Added
SIB.useItem.apply(this, arguments);
GBB.execItemTriggers.call(this.user(), this.item(), "postMap"); // Added
}; // Scene_ItemBase.prototype.useItem

})(DoubleX_RMMV.Item_Triggers);


Note that extended functions always use apply only, while new functions always use call only.


This style exhibits the below 3 traits:


1. Almost the whole plugin's implementation's(except declaring new classes which will be done before calling the anonymous function) wrapped by a single anonymous function


2. Every new function's wrapped by a single container - the plugin object(DoubleX_RMMV.Item_Triggers in this case)


3. Every new function declared and extended function in the anonymous function's accessible via the plugin object as the container(like DoubleX_RMMV.Item_Triggers.Scene_ItemBase.useItem.apply(sceneItemBasePrototype, arguments) for calling the extended function SIB.useItem and DoubleX_RMMV.Item_Triggers.Game_BattlerBase.execItemTriggers.call(battler, item, timing) for calling the new function GBB.execItemTriggers)


For those belonging the latter, my style will be something like this(DoubleX RMMV Reverse Input):


DataManager.isDatabaseLoadedReverseInput = DataManager.isDatabaseLoaded;
DataManager.isDatabaseLoaded = function() {
// Rewritten
if (!this.isDatabaseLoadedReverseInput.apply(this, arguments)) {
return false;
}
this.loadAllReverseInputNotes();
return true;
//
}; // DataManager.isDatabaseLoaded

DataManager.loadAllReverseInputNotes = function() { // New
var ts = [$dataActors, $dataClasses, $dataWeapons, $dataArmors, $dataStates];
ts.forEach(function(type) {
type.forEach(function(data) {
if (data) { this.loadReverseInputNotes(data); }
}, this);
}, this);
}; // DataManager.loadAllReverseInputNotes

// data: The data to have its notetags read
DataManager.loadReverseInputNotes = function(data) { // New
data.meta.reverseInput = {};
var rI = data.meta.reverseInput;
var regex =
/< *reverse +input *: *(\w+) *, *(\w+) *, *(\w+) *, *(\w+) *, *(\w+) *>/i;
data.note.split(/[\r\n]+/).forEach(function(line) {
if (!line.match(regex)) { return; }
rI.proto = "Window_" + RegExp.$1 + ".prototype";
rI.downUp = RegExp.$2 === "t";
rI.rightLeft = RegExp.$3 === "t";
rI.pagedownPageup = RegExp.$4 === "t";
rI.okCancel = RegExp.$5 === "t";
});
}; // DataManager.loadReverseInputNotes

/* window: The window used by the actor to check if the input's reversed
* command: The command to check if the correseponding input's reversed
*/
Game_Actor.prototype.isReverseInput = function(window, command) {
// New; Potential Hotspot
var equips = this.equips().filter(function(equip) { return equip; }), proto;
var types = [this.states(), equips, [this.currentClass()], [this.actor()]];
for (var index = 0, length = types.length; index < length; index++) {
for (var data = types[index], i = 0, l = data.length; i < l; i++) {
proto = eval(data.meta.reverseInput.proto);
if (!proto || !proto.isPrototypeOf(window)) { continue; }
return data.meta.reverseInput[command];
}
}
return false;
}; // Game_Actor.prototype.isReverseInput

Window_Selectable.prototype.cursorDownReverseInput =
Window_Selectable.prototype.cursorDown;
Window_Selectable.prototype.cursorUpReverseInput =
Window_Selectable.prototype.cursorUp;
Window_Selectable.prototype.cursorRightReverseInput =
Window_Selectable.prototype.cursorRight;
Window_Selectable.prototype.cursorLeftReverseInput =
Window_Selectable.prototype.cursorLeft;
Window_Selectable.prototype.cursorPagedownReverseInput =
Window_Selectable.prototype.cursorPagedown;
Window_Selectable.prototype.cursorPageupReverseInput =
Window_Selectable.prototype.cursorPageup;
Window_Selectable.prototype.processPagedownReverseInput =
Window_Selectable.prototype.processPagedown;
Window_Selectable.prototype.processPageupReverseInput =
Window_Selectable.prototype.processPageup;
Window_Selectable.prototype.processOkReverseInput =
Window_Selectable.prototype.processOk;
Window_Selectable.prototype.processCancelReverseInput =
Window_Selectable.prototype.processCancel;

Window_Selectable.prototype.cursorDown = function(wrap) { // Potential Hotspot
// Added
if (this._actor && this._actor.isReverseInput(this, "downUp")) {
return this.cursorUpReverseInput(wrap);
}
//
return this.cursorDownReverseInput(wrap);
}; // Window_Selectable.prototype.cursorDown

Window_Selectable.prototype.cursorUp = function(wrap) { // Potential Hotspot
// Added
if (this._actor && this._actor.isReverseInput(this, "downUp")) {
return this.cursorDownReverseInput(wrap);
}
//
return this.cursorUpReverseInput(wrap);
}; // Window_Selectable.prototype.cursorUp

Window_Selectable.prototype.cursorRight = function(wrap) { // Potential Hotspot
// Added
if (this._actor && this._actor.isReverseInput(this, "rightLeft")) {
return this.cursorLeftReverseInput(wrap);
}
//
return this.cursorRightReverseInput(wrap);
}; // Window_Selectable.prototype.cursorRight

Window_Selectable.prototype.cursorLeft = function(wrap) { // Potential Hotspot
// Added
if (this._actor && this._actor.isReverseInput(this, "rightLeft")) {
return this.cursorRightReverseInput(wrap);
}
//
return this.cursorLeftReverseInput(wrap);
}; // Window_Selectable.prototype.cursorLeft

Window_Selectable.prototype.cursorPagedown = function() {
// Added
if (this._actor && this._actor.isReverseInput(this, "pagedownPageup")) {
return this.cursorPageupReverseInput();
}
//
return this.cursorPagedownReverseInput();
}; // Window_Selectable.prototype.cursorPagedown

Window_Selectable.prototype.cursorPageup = function() {
// Added
if (this._actor && this._actor.isReverseInput(this, "pagedownPageup")) {
return this.cursorPagedownReverseInput();
}
//
return this.cursorPageupReverseInput();
}; // Window_Selectable.prototype.cursorPageup

Window_Selectable.prototype.processPageup = function() {
// Added
if (this._actor && this._actor.isReverseInput(this, "pagedownPageup")) {
return this.processPagedownReverseInput();
}
//
return this.processPageupReverseInput();
}; // Window_Selectable.prototype.processPageup

Window_Selectable.prototype.processPagedown = function() {
// Added
if (this._actor && this._actor.isReverseInput(this, "pagedownPageup")) {
return this.processPageupReverseInput();
}
//
return this.processPagedownReverseInput();
}; // Window_Selectable.prototype.processPagedown

Window_Selectable.prototype.processOk = function() {
// Added
if (this._actor && this._actor.isReverseInput(this, "okCancel")) {
return this.processCancelReverseInput();
}
//
return this.processOkReverseInput();
}; // Window_Selectable.prototype.processOk

Window_Selectable.prototype.processCancel = function() {
// Added
if (this._actor && this._actor.isReverseInput(this, "okCancel")) {
return this.processOkReverseInput();
}
//
return this.processCancelReverseInput();
}; // Window_Selectable.prototype.processCancel


And it seems to me that it's much closer to the RGSS3 style :)


I haven't benchmarked the performance difference between these 2 styles, but as using call to call a function is at least slightly less performant than directly calling it, and the 1st style made other plugins calling new or extended functions in mine much, much more painful(just compare DoubleX_RMMV.Item_Triggers.Scene_ItemBase.useItem.apply(this, arguments) with this.useItem() and DoubleX_RMMV.Item_Triggers.Game_BattlerBase.execItemTriggers.call(this, item, timing) with this.execItemTriggers(item, timing)), I'll stick to the 2nd style for plugins preferring more performant codes :D


Now it's time for you guys to criticize my styles as brutally and harshly as you can, as mine should be the best counterexamples on how to write RMMV plugins properly ;)
 
Last edited by a moderator:

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,042
First Language
French
Primarily Uses
RMMV
the only thing's I have to say...this totally not readable for me xD....
 

Milena

The woman of many questions
Veteran
Joined
Jan 26, 2014
Messages
1,281
Reaction score
106
First Language
Irish
Primarily Uses
N/A
If it is totally not readable to you, that means you don't know the basics of JavaScript yet then. While reading DoubleX's post, I got the idea right away and tried to subsequently code like it myself.
 

Iavra

Veteran
Veteran
Joined
Apr 9, 2015
Messages
1,797
Reaction score
863
First Language
German
Primarily Uses
The only downside i could see is that you are adding aliases to the prototype, like here:


Window_Selectable.prototype.cursorDownReverseInput = Window_Selectable.prototype.cursorDown;


This has both up- and downsides:


+ The old version is easily available, if someone wants to write compatibility patches.


+ You can directly call the functions, instead of using the slower call() or apply().


- The prototype might get a bit messy (harder to debug in console).


- There is a risk of picking function names, that are used by other plugins.


The last downside can be somewhat remedied by using prefixed method names, like "<scripter>_<plugin>_<aliased function>". In your example, that could be:


Window_Selectable.prototype.doubleX_reverseInput_cursorDown = Window_Selectable.prototype.cursorDown;


This makes it both easier to debug in console, since you can immediately see, which functions are aliases, and the risk of overwriting other plugin's methods get very slim, since you are using your own namespace. And it still comes very near to how aliases worked in Ruby.


I used this syntax myself for a while, but dropped it at some point after a small discussion with DarknessFalls. Though, i may use it again in the future, since it makes writing compatibility patches much easier.
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,789
Reaction score
941
First Language
Chinese
Primarily Uses
N/A
About anonymous function:


Back when I was learning how anonymous function works, many coders focused on offering an extra scope to enhance code protections and privacy, as they prefer blackboxing the implementations.


I, on the other hand, feel that anonymous functions can also be used to improve readability.


It's because back in RMVXA scripting, I frequently use DoubleX_RMVXA::Script_Name::CONFIG and/or DoubleX_RMVXA::Script_Name.config.


After realizing that anonymous functions can provide a shorthand for this(like IT for DoubleX_RMMV.Item_Triggers), it seems to me that my codes can be more readable by using such shorthand.


About strict mode:


Uptil now, I've used strict mode for all my plugins. I declare "use strict" at the beginning of those plugins.


About hotspot:


I've the habit to mark functions that are extremely frequentl called(like per frame) with hotspots, so I'll know I'll have to write more performant codes there :)


Also, to broaden our focus, let's talk about code qualities that are important in RMMV plugins, and our preferences in balancing and prioritizing them too.


We usually base our arguments about writing RMMV plugins properly on our insights of what code qualities are important in RMMV plugins and how we should balance and prioritize them, so sharing our views on this should be quite beneficial to this topic.


To clarify what I'm talking about, let's consider the below possibly hypothetical examples:


1. Some might think that time performance's always the most important code quality, as RMMV can already have serious lag and fps drop in less powerful machines(albeit still meeting the RMMV minimum requirements) even without using any plugin. Such plugin developers will probably be more inclined to claim that writing RMMV plugins properly means making the codes as performant as possible.


2. Some might think that compatibility's always the most important code quality, as no RMMV plugin is ever supposed to work in isolation. Such plugin developers will probably be more inclined to claim that writing RMMV plugins properly means making the codes as public and modular(via low coupling and high cohesion) as possible.


3. Some might think that readability's always the most important code quality, as our future selves and other plugin developers need to be able to thoroughly comprehend the plugins in order to maintain their lives. Such plugin developers will probably be more inclined to claim that writing RMMV plugins properly means making the codes as self-documenting and comments as descriptive as possible.


4. Some might think that balancing and prioritizing code qualities is purely personal preferences, as plugin users only care whether the plugins do work without nontrivial bugs. Such plugin developers will probably be more inclined to claim that there's no such thing called writing RMMV plugins properly.


5. Some might think that balancing and prioritizing code qualities is based on the specific situation, as different contexts give rise to different goals, conditions and constraints, all of which are fundamental in balancing and prioritizing code qualities. Such plugin developers will probably be more inclined to claim that writing RMMV plugins properly can mean drastically differently under different particular cases, implying actually analyzing the scenarios rather than automatically applying best practices is a prerequisite of writing RMMV plugins properly.


If we(all plugin developers, not just those having joined this topic already; same below) all share our opinions on this, we might realize why we've so difference stances on writing RMMV plugins properly, and perhaps why we're writing them so differently :D
 
Last edited by a moderator:

Iavra

Veteran
Veteran
Joined
Apr 9, 2015
Messages
1,797
Reaction score
863
First Language
German
Primarily Uses
My main preference is always to write my plugins as performant as possible, even if it's sometimes less readable (example: Binary operations like | or >> are much faster than Math.floor, and are exactly equal as long as you are only dealing with positive numbers).


I make an exception for initialization code, that only runs once, though. Example: Array.filter is a lot slower than filling an array with a normal for-loop, but i still use it to read plugin parameters, because it fits in one line and is only done once, anyway.


Apart from this, i made it a habit to use JSDoc to document every function, so it's hopefully easy enough to understand, what my plugins are doing. Here's an example:


/**
* Extends a given object with all properties from another given object. Mainly used to simplify subclassing.
* @param {Object} base - Base object to be extended.
* @param {Object} extend - Object containing the properties to be appended to the given base object.
* @returns {Object} The base object, after it has been extended.
*/
var _extend = function(base, extend) {
for(var key in extend) { base[key] = extend[key]; }
return base;
};


JSDoc is modelled after JavaDoc and it should be easy to understand, what the function does, which parameters it takes and what it returns. Of course this means my plugins end up a lot bigger, than they would be with code alone, but i'm willing to sacrifice some space for better readability (and performance, which i why i tend to cache stuff, if it makes my plugins faster).


Regarding "use strict", take note that it's scoped to the surrounding function and since the base scripts don't use strict mode, placing it at the start of your file won't do anything. You need to use anonymous function scope, if you want to use strict mode.


Also, another tip regarding scope: Loops, or anything that uses curly brackets, but is not a function, does NOT declare a scope. Any variable you declare inside a loop will be available afterwards. This is also the reason, why you can use "this" inside a loop, but not inside Array.forEach (which, btw, is slower than a for-loop).
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,848
First Language
English
I like to try to write code as performant as possible if I can think of a way to do it, but method calls are out of the question.


If I think it can be separated into different methods, it will be done.


I don't know how much overhead a couple extra method calls could be per frame, but I consider it a micro-optimization that is not necessary unless it's critical.
 
Last edited by a moderator:

Iavra

Veteran
Veteran
Joined
Apr 9, 2015
Messages
1,797
Reaction score
863
First Language
German
Primarily Uses
As long as it's direct method calls, i wouldn't bother about. Makes it way easier to modify your plugin from the outside, if there are more points to hook in, too.
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,789
Reaction score
941
First Language
Chinese
Primarily Uses
N/A
So maybe another way to code properly in RMMV is this: Avoid writing more performant code at the cost of degrading any other important code quality unless the performance benchmarks suggest otherwise.


For example, in DoubleX RMMV Popularized ATB Core, there are many functions which are hotspots(from being called per frame to being called per battler per frame).


Before I know the RMMV FPS meter can show the ms spent per frame(much more useful to plugin developers imo) instead of the plain fps(much more useful to RMMV players imo), I tried to apply micro optimizations to make the codes in those hotspots as performant as possible, like using for loops instead of Array.prototype.forEach. It's because the FPS is 60 for both cases with and without micro optimizations, I can't tell whether those micro optimizations actually make the codes more performant, so I decided to play safe.


Later when I use the ms spent mode of the FPS meter to benchmark both the codes with micro optimizations and those without them, I failed to detect any noticeable ms spent difference at all even after I repeated the test for 10+ times.


While my machine is i7-3820 + 2 * 2GB DDR3-1333 + Asus GTX550Ti, which surpasses the RMMV minimum requirements, I still don't think the performance difference between those 2 cases will suddenly become much more significant for those barely meeting the RMMV minimum requirements.


Besides, without those micro optimizations, I feel that my codes become more readable, so in this case I decided to get rid of all those micro optimizations.


On a side note: For both cases, the base load's(without any plugins) about 6ms per frame and the added load from that plugin alone with full ATB mode, with nothing else happening, is about 2ms per frame + 1ms per battler refilling ATB per frame, leading to the final load possibly being about 20ms per frame(4 party members + 8 troop members in the default RMMV setting), which is about 50 FPS(10 FPS drop from 60 FPS).


On the other hand, redrawing the whole status window per frame should be avoided at nearly all costs.


In the same environment, redrawing the whole status window per frame led to about 17ms added load per frame in my machine. As the final load of that plugin roughly ranges from 8ms to 20ms without redrawing the whole status window per frame, the final load with that roughly ranges from 25 to 37ms, which is about 40 to 27 FPS(20 to 33 FPS frop from 60 FPS). For machines barely meeting the RMMV requirements, the final FPS can only be lower.


In this case, even if I'll have to add a change notification flag with several reasons to raise that flag to notify when to redraw the whole status window and when to just redraw all actor's ATB bars(which are optimized to be redrawn only when the actor ATB fill percent can change), thus making the codes more complicated and convoluted, I'll still go for it, as such a FPS drop's simply unacceptable to me.


In short, regarding performance, how solid the reason to write more performant codes is mainly determined by performance benchmarks.
 
Last edited by a moderator:

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

Latest Threads

Latest Posts

Latest Profile Posts

This is relevant so much I can't even!
Frostorm wrote on Featherbrain's profile.
Hey, so what species are your raptors? Any of these?
... so here's my main characters running around inside "Headspace", a place people use as a safe place away from anxious/panic related thinking.
Stream will be live shortly! I will be doing some music tonight! Feel free to drop by!
Made transition effects for going inside or outside using zoom, pixi filter, and a shutter effect

Forum statistics

Threads
105,999
Messages
1,018,219
Members
137,777
Latest member
Bripah
Top