May 13, 2024, 08:06:48

Author Topic: Event-Driven Programming  (Read 7369 times)

Offline Mularac

  • Lieutenant
  • ***
  • Posts: 531
  • Karma: 11
    • View Profile
Event-Driven Programming
« on: September 22, 2010, 04:00:51 »
I'm still rather new to the programming world, but the little I know makes it clear that Nexus' Black Ruler scripting language belongs to the Event-Driven paradigm (although not purely).

I was just wondering if anyone here knew of a paper, manual or whatever that I can read in order to improve my style while programming in Nexus.


EDIT: Idea: we could write one together and learn from each other and therefore improve the quality of our scripts in Nexus (well... I think it'll end up in us learning from Arparso :P) and then post it the Wiki.

Offline Arparso

  • Administrator
  • Lieutenant
  • *****
  • Posts: 558
  • Karma: 14
  • I can see you...
    • View Profile
    • http://arparso.de/nexus
Re:
« Reply #1 on: September 22, 2010, 12:25:03 »
Ah, don't worry too much about it. If you understand the concept of events and rules, the rest should come naturally with practice.

Some important basics, although I'm sure most Nexus scripters already know most if not all of that:

1. Events and rules

1.1 Introduction to events
An event is very much like a message, signalling that something important has happened. When a ship is exploding, Nexus fires an "Exploding" event... if a ship has been dedected by enemy sensors, the game fires an "Detected" event. Like those two, there is a wide range of default messages Nexus fires completely automatic, no matter what you do in your mission or campaign script. It's the scripters responsibility to "listen" to those events he cares about and to write proper rules for them.

1.2. Introduction to rules
A rule in Nexus' terms is at the most basic level just a piece of code. Each rule is bound to a certain event and when that event fires, the rule's code will be promptly executed. Rules and events are linked together by their names: a rule written for the "Exploding" event would begin with "RULE event Exploding". A rule can only be linked to one event, but it'll be executed each time such an event fires and you can have as many rules for the same event as you like. For example it might make sense to split player- and AI-related logic reacting to the same event into two (or more) different rules to keep things nice and clean and easy to understand. The typical syntax for a rule looks like that:

Code: [Select]
RULE event <event-name>
:action
    <code>
:end
END

1.3 Events and parameters
An event doesn't only have a name like "Exploding", but it often also carries important information in the form of parameters. For example, the "Exploding" event would be rather pointless, if we wouldn't know what ship is actually exploding. For that purpose, the game fires the event with an additional parameter, called "E.ship" in this case. It's a regular variable available to all rules linked to that event, containing a reference to the exploding ship.

Please be cautios with these parameters: all rules for a certain event will be executed sequentially one after another - if you change the value of a parameter variable in one of your rules, it'll be changed for all subsequently executed rules as well! Because you can't easily anticipate the order of execution for your rules, you can't use this "feature" reliably to transport information from one rule to another. That's an easy way to break your whole script. So in an "Exploding" rule, don't do something like this:

>> E.ship := "My Fair Lady";

1.4 Custom events
You don't have to restrict yourself to the game's default events (which are documented quite well in the official modding manual), but can use your own events instead. In order to define a new event, you don't have to do anything at all. The moment you write a rule for a non-default event, that event will be registered for you automatically. Of course, the game doesn't yet know when to fire the new event, so you have to do that manually. There is a bunch of commands available for that, but we'll use "LocalEvent(event, params)" for now. That command fires the specified event with the specified parameters. You can also omit the parameters, if you don't need any.

Short example: let's say you wan't to fire a "NosePicking" event once all enemy forces have been destroyed. For that purpose you've created a rule for the "Exploding" event so each time a ship is exploding, you check if there are any more enemy ships left. If there are none left, you fire your own event:

>> LocalEvent(NosePicking, E.duration := 5; E.depth := 2);

Notice how to assign parameters to an event:
- each parameter name uses the E-prefix, because it's an event-variable
- you use the assignment operator := to assign a value to the parameter
- split several parameter assignments by semicolons

Now every rule written for the "NosePicking" event will get executed and receive those parameter values.

1.5 Conditional execution of rules
You'll probably run into instances, where you're creating a rule for an event, that you only want to execute in certain special cases and not all the time. Let's say, one "Exploding" rule only cares, if one of the player's ships explodes - it doesn't care about any other ship. You could do that distinction in the rules' code by using the "If(condition, when, else)" command or something similar. It does achieve the wanted result, but makes the rule's code just a tad harder to read and understand. However, rules already have a handy mechanism for conditional execution letting us avoid ugly If-statements:

Code: [Select]
RULE event <event-name>
:condition
    <condition>
:end
:action
    <code>
:end
END
or

Code: [Select]
RULE event <event-name>
condition <condition>
:action
    <code>
:end
END

A rule will only be executed, if its <condition> will not evaluate to 0 or false. You can put almost anything in there. For our example we might want to evaluate the exploding ship's race, so let's just do that:

>> condition E.ship:race=#race_player

Now this rule will only be executed, if the exploding ship belongs to the player. It's that easy. Please be careful with the shorter syntax, though: don't use any spaces in your <condition> or else it will crash. if you want to be on the safe side, use the longer ":condition-:end" syntax for your rule conditions, as Nexus is much less anal about those.
« Last Edit: December 02, 2010, 17:22:15 by Arparso »

Offline Mularac

  • Lieutenant
  • ***
  • Posts: 531
  • Karma: 11
    • View Profile
Scope of variables
« Reply #2 on: September 22, 2010, 16:36:47 »
Yes, of course. What I meant was something a bit more advanced in the line of the scop of variables, the max ammount of rules per state and all that.

I've also done a lot of testing and I have come up with a way of reliable (or at least semi reliable) way of replacing good-old functions, by carefully taking advantage of the Scope of the variables and parameters you'll be using.


And while we're talking about the scope of variables, why don't add my small knowledge about it?

1 Scope of Variables

1.1 Introduction to variables

A variable is an expression in a programming language that allows you to store information. Different languages have different methods and implementations for this (some don't even have them).

In nexus, variables are weakly typed. What does that mean, you may ask yourself?
Well, it means that variables can store anything. The "how" is unknown to us, it could be dynamically typed like python or just not typed at all, like smalltak where they're just pointers.
But that's not what's important. What is important, though, is that one variable can store anything, like numbers, objects, lists, etc.

So a variable you were using for, let's say, store a ship object
Code: [Select]
ship := GetSceneObj("My Ship");

can be then used to store something else, like a string. (although that's a horrible style of coding)

Code: [Select]
ship := "Hello World";

If you have had experience in other languages, you'll notice than in Black Ruler you don't have to declare the variables anywhere, you just invoke them. The name of the variable can be anything and it won't matter, except for the scope of said variable.

1.1 Scope of Variables

The "scope" of a variable means from where in the script that variable can be accessed.

1.1.a "e." variables

These variables are vital to any code, and you'll probrably be using them a lot, so pay attention. They mark the lowest scope of all, and can only be reached from within the block they were first invoked, and when that block's finished executing they will disappear.

Examples:

a)
Code: [Select]
rule event DoSomething
:action
e.string:="Hello World";
Debug(e.string);
LocalEvent(DoSomethingElse);
:end
END

Rule event DoSomethingElse
:action
debug(e.string);
:end
END

in this case, in the debug screen it'll appear written "Hello World" and below it "0", as the two "e.string" variables are not the same.

b)

Code: [Select]
Rule event DoSomething
:action
e.string:="Hello World";
Delay(0,1,
Debug(e.string);
,0);
:end
END

What would it appear on the debug screen? The answer is "0". We don't exactly know how, but the delay command generates a new block where all the variables present in that block are independant from the block they where called from.

c)
Code: [Select]
rule event DoSomething
:action
e.string:="Hello World";
Debug(e.string);
LocalEvent(DoSomethingElse, e.string:="Hello again");
:end
END

Rule event DoSomethingElse
:action
debug(e.string);
:end
END

In this case, however, the debug screen will show "Hello world" and below it "Hello again". Why? because the string "Hello again" was passed as an argument to the DoSomethingElse rule and stored in that rule's "e.string" variable.

1.1.b "p." variables

Those variables access the variables stored in the previous event. This always means the directly previous valid E. variables.
It's used mainly, if not always, to pass parametres from one event to an other or from an event to a delay function.

Example:
a) Going back to the variables of the rule the delay function is called

Code: [Select]
Rule event DoSomething
:action
e.string:="Hello World";
Delay(0,1,
Debug(e.string);
,e.string:=p.string);
:end
END

This time in the debug screen it'll appear"Hello World", because the delay's "e.string" variable was declared with the content of the "DoSomething" rule's "e.string" variable (note that the name doesn't have to be the same, but it's good practice to keep it that way).

b)
Code: [Select]
rule event DoSomething
:action
e.string:="Hello World";
Debug(e.string);
LocalEvent(DoSomethingElse, e.string:=p.string);
:end
END

Rule event DoSomethingElse
:action
debug(e.string);
:end
END

Same as above. The "DoSomethingElse" Rule's e.string variable was declared with the "DoSomething" rule "e.string"'s content.
Note that ALWAYS when passing arguments with functions like "LocalEvent", "MEvent", "ChangeState", "Delay", etc, you have to use the p prefix to access the variables of the rule this functions were called from, and that p prefix is only used to access the ones with the E prefix.

1.1.c variables withouth a prefix

Those variables are what we could call "semi-global". Their scope extends within the machine they were first declared, so the contents of that variable can be accessed and changed from any rule in any state of that machine, and it can, of course, be passed as an argument to an other state in an other machine (in the form of a E. variable or any other).
You're probably feeling excited about this type of variables, as you don't have to deal with passing parametres and e. or p. variables and all that. Well, don't. Global or semi-global variables are a bad deal in any programming language, and you must avoid them whenever it's possible, because they can break your code if you're not cautius or at least make it unreadable by someone on the outside, as keeping track of those variables in a complex code is a pain.

1.1.d "M." variables

In all sense and purpose, these are global variables. Their scope extends towards the entire mission, meaning that any rule being executed in your mission can access it and change it.
Same recommendation as before: Be careful with it. My recommendation is that use them only for declaring mission "constants" (like ships) or in the worst case some very specific boolean flag, nothing more.

1.1.e "S." variables

These variables only make sense inside a selection, like the Execlist or SelectEx command. They only live through one iteration of said commands, so declaring them out of the blue doesn't make a lot of sense.

What's important about them, though, is that in those selections the selected object can accessed via the "s.this" variable you all head about. And as any s. variable it will change at the end of the cicle's iteration.




There are other prefixes available for variables, but to be honest, I don't quite understand or use them.

Which begs the question, does anyone know how and when to use variables with the D or T prefix?
« Last Edit: October 03, 2011, 00:30:19 by Mularac »