Jump to content

Immutability and encapsulation in mod design


Recommended Posts

On 11/18/2019 at 5:18 PM, subtledoctor said:

Third: for the sake of reducing clutter, is there any reason the .debug logs shouldn't be shunted away into the weidu_external folder?  My MacOS micro mod manager already sequesters them into /logs, but I'm thinking about changing that to /weidu_external/debug_logs...

There is a weidu feature: create folder called 'debugs' and all installation .debug files will be created there. Other than that, I don't see how you could change .debug files location without using mod manager or extra bat/command files.

Edited by AL|EN
Link to post

 

@DavidW

So, I went the extra mile and started using SFO (so as to complete your lecture about functional programming).

I (hopefully!) managed to get the hang of it, but unfortunately some features are poorly documented / rarely used in SCS, so I'd like to ask you a couple of questions:

  • As far as make_item and make_spell are concerned, how can I target a specific header using add_effect_inline? I was hoping for the array eff_fields to have an additional parameter (i.e., header or something like that), but that's not the case.... I mean, I don't think you're forced to do something like this, right?

    EDIT: Just to clarify: I'd like to match headers based on their order, i.e.: I'm looking for something like header in ordinary ADD_ITEM_EFFECT / ADD_SPELL_EFFECT ('SET header to number of extended header (starting from 1) the effect should be added to (by default the effect is added to every header'). I tried with 

    add_effect_inline => "match=>~ability=1~ opcode=>18 parameter2=>1"	// Add this effect to the first extended header only!

    but it's not working (i.e., the effect is not added....)

  • Is it possible to collect something like this
    add_effect_global_inline'0=>~opcode=>0~
    add_effect_global_inline'1=>~opcode=>1~
    add_effect_global_inline'2=>~opcode=>2~
    add_effect_global_inline'3=>~opcode=>3~
    add_effect_global_inline'4=>~opcode=>4~
    add_effect_global_inline'5=>~opcode=>5~

    into an auxiliary array (like you would do when adding a brand new ability / effect, i.e.: add_ability / add_effect => auxiliary_array).

Edited by Luke
Link to post
6 hours ago, AL|EN said:

Do all of the encapsulation goals can be achieved simply by putting one mod component into the one tp2 file?

Retyped the question and then I'll answer it...

Can one components all encaptulation goals be archived by simply putting it's code into a .tpx file ? YES ! And then just include the .tpx file in the primary .tp2 file.

This of course has a few things to remembered, the actual .tp2 has to have the backup file etc things set as said in this post, and the following ones. Of course you don't have to undestand all of the code in those post, just the portions that you utilize, say setting the backup folder, custom "workshop" folder and the include function for basic functionality, when you don't have translation etc.

Edited by Jarno Mikkola
Link to post
1 hour ago, Jarno Mikkola said:

Can one components all encaptulation goals be archived by simply putting it's code into a .tpx file ? YES !

Awesome.  That's easy enough.  Thanks!

Link to post
19 hours ago, Jarno Mikkola said:

Can one components all encaptulation goals be archived by simply putting it's code into a .tpx file ? YES ! And then just include the .tpx file in the primary .tp2 file.

My takeaway from this thread is that the answer is NO.  Put the component into a .tpx file, sure, but also put it inside a function - essentially, just put

DEFINE_ACTION_FUNCTION ~this_component~ BEGIN

at the beginning of the tpx file, and put

END

at the end.  Then in the main .tp2 file, you would put:

INCLUDE ~mymod/components/this_component.tpx~
LAF ~this_component~ END

That way, all variables and arrays etc. used in that component will be cleared before moving on to the next component.  Just INCLUDE'ing a .tpx file doesn't do that.

This has been my recent practice, at any rate.

Edited by subtledoctor
Link to post
43 minutes ago, qwerty1234567 said:

A lot of jumping through the hoops... How about


ALWAYS
  CLEAR_EVERYTHING

?

This example works aka it clear all tp2 global variables which were initialized by other components:

 BACKUP ~ImmutableA/backup~
SUPPORT ~http://www.ImmutableA.com/forums~
VERSION ~0.1.0~

ALWAYS
    CLEAR_EVERYTHING
END

BEGIN "ImmutableA 1" NO_LOG_RECORD
    OUTER_TEXT_SPRINT test error
    PRINT "%test%"

BEGIN "ImmutableA 2" NO_LOG_RECORD
    PRINT "%test%"

Even if first component set 'test' to 'error', the second component will print 

%test%

because variable being not initialized. So I guess it works?

Edited by AL|EN
Link to post

 Observation despite CLEAR_EVERYTHING doing it's job for modder variables:

If I replace 'test' with weidu automatic variable "MOD_FOLDER", it won't be cleaned. According to the documentation of CLEAR_EVERYTHING -> CLEAR_MEMORY:

Quote

removes all variables from the memory, then reloads the automatic ones (TP2_AUTHOR, TP2_FILE_NAME, TP2_BASE_NAME, MOD_FOLDER, LANGUAGE, WEIDU_ARCH, WEIDU_OS, COMPONENT_NUMBER, all numeric constants such as NAME1, the soundsets, or SCRIPT_OVERRIDE). INTERACTIVE is automatically set when you call the next component. Do not use this feature without a real reason.

 it should be re-initialized with default value. Looks like a bug to me. @Wisp can you elaborate?

Edited by AL|EN
Link to post
44 minutes ago, qwerty1234567 said:

A lot of jumping through the hoops..

Two.  Two hoops.  That's a lot?

Actually putting components into separate .tpa files is entirely unnecessary, and has nothing to do with encapsulation.  So really it just comes down to "make each component code inside a function."  So, just one hoop.

CLEAR_EVERYTHING in an always block will wipe everything... but what if you actually want certain variables to be set when you call a component function?  What if some subroutine in a component behaves differently depending on what was done in some earlier component?  CLEAR_EVERYTHING is a massive "nothing survives, no exceptions!" rule.  Putting things in functions works just as well (AFAIK - I'm not really an expert) but preserves flexibility.

Edited by subtledoctor
Link to post

@subtledoctor  According to the definition, what you describe is again principals of encapsulation, isn't it? Although the definition itself is very strict. If any component change it's behavior based on previous one, I'm sure there is wa way to re-implement the same 'check' which set some variable into the current component, even if it means code duplication.

Do you have any example? I would really like to see valid user-case in order to evaluate the possible ratio of flexibility vs benefits.

Link to post

Flexibility vs. what benefits? I haven’t seen any mentioned. Apart from not wanting to copy and paste “DEFINE_ACTION_FUNCTION” a handful of times? 

All I see is some flexibility vs. no flexibility. We’re talking in exceedingly general terms here, so  at this level, I’ll take flexibility.

 

Link to post
6 hours ago, subtledoctor said:

Flexibility vs. what benefits? I haven’t seen any mentioned. Apart from not wanting to copy and paste “DEFINE_ACTION_FUNCTION” a handful of times?

Simplicity and just working. Effortlessly.

  1. Look at this thread. 4 pages of discusion (also, I looked back and actually CLEAR_EVERYTHING was already proposed). Even people who are not novices getting confused and disagreeing. How do you want beginners to undestand this? Or would you prefer them to copy the code without understanding what it does and even why?
  2. You keep your flexibility. No one forces you to put it in the ALWAYS block. However, if you're worried about encapsulation at all, breaking it should be explicit, not the other way around.
    Like, if you configure a firewall, a good practice is to block everything by default, then open each needed port explicitly. If you open everything and then try to block each unneeded port one by one (yes, just copying a config line a handful of times), you will slip up at some point, because you're human.
    And the very reason to employ encapsulation is remove human error element (well, from that particular angle).

Following the best practice policy should be effortless, and breaking it should take effort. That's the best practice for following best practices.

So if I was asked for an advice, I would say "Just always CLEAR_EVERYTHING. But if for some reason you really need to pass variables between components, be aware that it breaks encapsulation. And if you're ok with that, then go LAF route."

Link to post

@AL|EN

Can you expand a bit on observed behaviour versus expected? The CLEAR code stores the current values for MOD_FOLDER et al., and uses these values when restoring the variables. Since the variables are mutable, it is possible the value could remain different from what it first was, even after CLEAR has run.

Link to post

Sigh.  Not really looking for a debate.  If CLEAR_EVERYTHING works for you, that's great.  Suggesting that there is only one way to skin this cat, or that your lazy simplistic simple way ( :p ) is somehow objectively better than another given way, seems ridiculous to me.  But what the heck, I'll engage, if only for the sake of clarifying things for later readers of the thread.

7 hours ago, qwerty1234567 said:

How do you want beginners to undestand this? Or would you prefer them to copy the code without understanding what it does and even why?

I would suggest that this is more of a danger for your preferred method.  If a newbie asks you "what is cleared when I write CLEAR_EVERYTHING as you instructed me to?"  Your answer will probably be something like "a lot of important stuff, but don't worry about the details," in which case it hangs out there as a kind of big scary unknown thing they are putting in their code (and the Weidu docs say something like "don't use this without good reason").  Or you might explain everything that actually gets cleared, and they likely won't understand half of it, and then it becomes kind of scarier because they won't understand how it affects their code.

Whereas, telling a newbie "putting your code inside a "function" and then calling that function runs your code exactly as your wrote it; it just protects against rare edge-case problems that might possibly crop up.  Those problems may never crop up, but protecting against them is free and easy, so why not?"  This is just my opinion as someone not very far away from being that newbie, but the latter seems a lot less intimidating and easier to wrap my head around.  (And therefore I'm more likely to actually do it.

 

8 hours ago, qwerty1234567 said:

also, I looked back and actually CLEAR_EVERYTHING was already proposed

Yes.  And there were two fine answers which I guess bear repeating because you either didn't read them or didn't understand them or you found some other reason to ignore them:

1) "At a more conceptual level, I'd like my encapsulation to be built in to the way the code is structured, not forced explicitly. One of the main points of FUNCTIONs is to deliver this; why not use it?"

2) A disadvantage with CLEAR_EVERYTHING is that "you can't read in data in your ALWAYS that's to be used in any component."

7 hours ago, qwerty1234567 said:

 I would say "Just always CLEAR_EVERYTHING. But if for some reason you really need to pass variables between components, be aware that it breaks encapsulation. And if you're ok with that, then go LAF route."

But, you're wrong.  Functions don't break the idea of encapsulation.  They were specifically proposed as a way to achieve it.  Moreover, the point of encapsulation is that things can't get out of your component sandboxes; it's mot mean to keep anything from getting in.

So @AL|EN here's your example: the list of arcane spells, and their filenames, and the scroll files that allow you to learn them, are highly dynamic.  They can change in any given install depending on whether the user installed IWDification, SCS IWD spells, Divine Remix spells, Spell Revisions, SpellPack, Faiths & Powers, Deities of Faerun... I could go on and on.  Tome and Blood often needs to patch arcane spells, and consequently patch the scrolls associated with those spells.  So at the very outset, the first time you install anything in the mod, it generates an array of arcane spells and their associated scrolls (and also their schools).  Later, various components read from this array in order to correctly patch the spells and scrolls.  We feed that data into each component for use by the component; if we did CLEAR_EVERYTHING every time, we would have to dynamically generate that data every time.  I guess that wouldn't be the end of the world, but... as I say, I haven't seen any valid arguments for why clearing the data and re-generating the array multiple times would be any better.

That circumstance is admittedly pretty rare.  If CLEAR_EVERYTHING works for your mod, go ahead and use it.  If a new modder who's not very knowledgeable about Weidu or programming in general asks you what they should do... well, I guess I would explain both and let them decide which one they understand and are more comfortable with.  If you tell them "thou shalt put CLEAR_EVERYTHING in your ALWAYS block" and later they decide to implement something like what Tome & Blood does, it won't work for them and they might not understand why.  Dropping things into functions (again, adding a single line of text at the beginning of each component) precludes the possibility of that difficulty, for exceedingly low effort.  Seems better to me.  But again that's just MHO.

Link to post
Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...