Game Development Community

OOP In Torque Script 3.0 (for T2D 1.0.2 specifically, and probably TGE and TSE)

by Bryan Edds · 07/24/2005 (3:17 pm) · 36 comments

Download Code File

Included in the attached .zip is the instructions for applying this resource to your project. Follow the instructions in the OOInTorqueScriptInstructions.txt file to apply the resource to the engine.

After applying the resource, it is critical that you read and study the other files that are included in the .zip file. The first one to study is the tsoo.csl file. This is a script file that can be executed from the console which shows and explains many of the features of OO in Torque script in an example. You should read that file first. The remaining files are EXTREMELY USEFUL. They are different types of class templates that allow you to do a couple of "search and replaces" to create your own class files. Using these are not only instructive, but will also save you lots of time :)

New notes in the 3.0 version -

Mutliple inheritance - In this resource, you are allowed to inherit an unlimited amount of classes into any subclass or datablock. This makes the language very much like C++.

Using Multiple Inheritance - Using multiple inheritance is pretty easy. Simply put a SPC between each class your'e inheriting from. For example, assume ConcreteClass is a concrete class and InterfaceClassA and InterfaceClassB are both interface classes (and ALL of them use SimObject as their engine base). To inherit them into a class called ConcreteSubClass, do like so -

// class
   link( SubClass, 
         ConcreteClass1 SPC ConcreteClass2 SPC InterfaceClass,
         SimObject);

The first class in the inheritance list will become the Parent class, and is referred to by the Parent:: scope resolver. If you inherit other classes whose methods are named differently than the methods of the first class, they too will be referred to by the Parent:: scope resolver. But, if you inherit two classes which have functions of the same name (say, they both have a protInit function), then you refer to the first class in the inheritance list by Parent::protInit and the second class by it's actual name (in the above case, it'd be ConcreteClass2::protInit. If you need to call both in a function, you simply call them in order like this -
function SubClass::protInit(%this)
{
   Parent::protInit(%this);
   ConcreteClass2::protInit(%this);

   /// some code...
}
If you only need to call one of them, just leave out the call to the one you don't need like this -
function SubClass::someFunc(%this);
{
   Parent::someFunc(%this);

   /// some code
}

Old notes from the 2.0 version -

SimObject is now the main engine base class - In this resource, the main engine base class to derive from to create a new script base class is SimObject, not ScriptObject. You can still use ScriptObject instead of SimObject, but it's quite unnecessary and rather confusing at best (for an explanation of the difference between engine base classes and script base classes, see the included tsoo.csl file).

Cast function - Have a bad design you don't have time to refactor into a good one? Is the engine losing type information (like in onCollision) giving you parameter objects whose actual type is unknown? Use this super-cool cast function to make sure the object you have is of a certain class type or derived from a certain class. It's basically the Torque Script version of C++'s dynamic_cast, but it's not slow like C++ version. Use it all you want without worrying about performance (but use this facility only when you have to since down-casting is the sign of bad OO design).

Override xxx::onRemove(), NOT xxx::delete() - I have made it so SimObjects (the object from which ALL objects in script derive) makes the proper call to xxx::onRemove() when an instance is deleted. So when you need to execute some code when your object is deleted, make sure to override the class's ::onRemove() function, NOT the ::delete() function.

Singletons use reference counting for garbage collection - In this singleton template's implementation, singletons are created when they are first referenced, and deleted when all the objects referring to them unregister their references to the singleton.

Old notes from the 1.0 version -

Initially, all script inheritance functionality was put into the ScriptObject, and unfortunately, it only allowed one layer of inheritance. While it was handy for very specialized tasks, I could not help but wonder what I could do with it. Harold and I discussed the current capabilities, and he quickly saw that with just the smallest bit of tinkering, multi-layered inheritance was possible. So, like the insane genius he is, he went in, did a bit of C++ coding, and made multi-layered inheritance work... well, almost. Unfortunately, there was another task that remained which would be needed to be done in order to help the ConsoleEval object find the methods of multi-layered inheritance when the method was not defined at all levels of inheritance. So with a bit of direction from Ben, I went into ConsoleEval, and updated it to handle the new capabilites in just a couple hours. No problem.

And I figured I might just stop there... but my curiousity drove me forward. I asked myself, "Why can't I put this feature into ALL objects that can be accessed in script?" So I tried it a couple different ways, and found one that worked expertly! I just moved all the inheritance functionality implemented in ScriptObject backwards into SimObject. Good stuff.

So, with the code extension considered by themselves, the only thing you got here is some fixed inheritance features that Torque was probably meant to have in the first place. The great thing about this is that you can use these features only if you want to - if you're not a big fan of inheritance, you just ignore it. Problem solved. It just all depends on what you want to do and how you do it. For OO maniacs like me, it's better to have the feature available than not, so long as it doesn't get in anyone else's way. Which brings up another good thing, this code extension is %100 backwards compatible with all your old script code. All you have to do is copy the zipped "engine" folder into you Torque directory, do a rebuild, and you're done.

So, that, in a nutshell, is what has been done in the extension. But I told you that story so I could tell you this one -

When I had arrived seriously on the Torque scene after developing some GUI stuff on the Q game engine, I took a very hard look at what capabilities Torque script offered its programmers. Although I did not, at the time, know that inheritance allowed only one layer, I knew that with a bit of methdological standardization, a form of object-oriented programming in script was possible. This all, of course, is before I met Harold. So, I pounded out the necessary disciplines that I thought would be a great way to achieve OO in script. Only after that, I had someone explain to me that Torque's implementation of scripting inheritance, while it would not stop me from using this methodology, would severely limit how useful the methodology would be. And that's when I met Harold... and ya'll know the rest :)

So download the zip, follow the readme.txt file, and take a look at the sample tsoo.cs file to see the new OO methodology in use. I've tried to include all the explanation I could, but if anyone has anyone questions, feel free to contact me here, or even privately. I'll be happy to answer any questions or concerns as to the hows, whys, or wtfs :D

Also, I've tested this code quite a bit, BUT, that does not mean it will work for you. Since this code has only been narrowly tested, it is highly advised that make sure to read and follow the code rollback instructions in case you encounter a big problem with the code. I cannot guarantee any amount of success with this since I haven't tested it out on your personal machine with your personal project :D

Thanks again to Harold for the help - it was his initial code example that springboarded this entire project. Thanks also to Ben, definitely for the encouragement to do it my way and go allll the way with it. And BIG thanks to the community for all the help for getting my newbie-arse up to speed on the inner workings of Torque.

Final Note: If anyone has any problems or I have gotten any line numbers wrong, please don't hesitate to tell me by e-mailing me at the e-mail address specified in my profile.

Good luck, and thanks for using object-oriented programming in Torque Script!
Page «Previous 1 2
#1
07/12/2005 (8:46 am)
I think finally I will have to cave and implement this :)
#2
07/12/2005 (11:57 am)
Great Matt! Can't wait to hear what you think :)

And... it could always use some more nice tutorials too ;)
#3
07/13/2005 (1:17 am)
Sounds great. Except that I don't understand why TorqueScript needs Java-style interface inheritance, which is unnecessary for dynamically-typed languages. Documentation is the only benefit I can think of. I haven't used TorqueScript much, so I may be missing something. I'm reminded of the April Fool's joke, Generic Programming In Smalltalk [gotta love geek humour ;-)].
#4
07/13/2005 (4:44 am)
Intrefaces are important in general for reasons which are much more abstract than those you mention. Interfaces don't just define a standard set of function signatures that make it easy for method 'x' to be found and executed by the VM or whatever. On an abstract level, interfaces are much more important than this. Interfaces define standard behaviors and responsibilities for the objects that inherit them, as well as define relationships between objects which may not even be implemented. They make a guarantee (even if it is implicit) to the other program objects that interact with the interface subclasses that those subclasses conform to the behoviors and responsibilities and relationships defined by the interface.

If you are not seeing the benefit, then you may be looking at it on the wrong level. Try not to think of interfaces in terms of what is absolutely necessary on the concrete level to have an equally functional program. Think of it as what is extremely helpful on the design level, a level which is more abstract. Even though the high-level software design is far away from the immediate concerns of concrete program logic, this level is where many of the most critical and important decisions in software engineering take place. Anything that aides that decision making process is extremely helpful in general, regardless of its relevance to the low-level concerns of the actual program implementation. Software design is HARD. This makes it MUCH MUCH easier.

From Wikipedia -
"The basic idea of typing is to give some semantic meaning to what is ultimately just mere bits. Types are usually associated either with values in memory or with objects such as variables. Because any value is simply a set of bits for computers, there is no distinction in hardware even among memory addresses, instruction code, characters, integers and floating-point numbers. Types tell you how you should treat those mere bits."

Read more.

Bringing semantic meaning to the concrete bits and instructions is necessary for productive programming. Interface typing is just an extension of the semanticization process. Semanticization is not absolutely necessary on a purely technical level. But it is necessary on any non-arbitrary engineering level. This level can make the difference between a reusable code libary and an unusable big ball of mud. I agree that type definitions are not necessary in a non-typed language. But if you say they are not EXTREMELY useful for important yet more abstract reasons, then I must ask we agree to disagree :)
#5
07/13/2005 (5:58 am)
Actually, I realize I might be starting a holy war...

Let me put it this way -

I personally like typed langauges. I personally use Toque script as if it were a strongly typed langauge (an implicitly typed langauge, as one might call it). This is because I believe enforcing typing on myself allows me to better engineer my programs. I recognize my ability to step away from that model if I absolutely need that flexibility in order to do something. But I never really have to because there's always been a static-type-oriented solution so far.

BUT - this does not mean that using Torque Script as if it were a strongly typed language is the best possible way. It simply means that I personally believe that using script as if it were strongly typed is the best possible way. If you are not convinced of the extreme usefulness of implicit typing, then what I said in my previous post is flawed at its core, and would probably be best considered then ignored.

So, that's how I see it, and that's why I consider Java-style MI extremely helpful to me and those who prefer implicit typing like me. But I can absolutely see the case for why non-typers have no use for it at all. It comes down to a simple matter of preferences. I prefer toma'to.
#6
07/13/2005 (12:27 pm)
Whoa. We seem to be using very different definitions. First, I distinguish between (1) dynamic versus static typing and (2) strong versus weak typing.

(1) In a statically typed language, the types must be known at compile-time (Java, C/C++, C#); the variable declarations have explicit types. In a dynamically typed language, the declarations are untyped, but each value/object knows its type (Smalltalk, Ruby, Python, TorqueScript).

(2) In a strongly typed language, you cannot send a message to an object that it doesn't understand without triggering an error; it is unimportant whether this error occurs at compile-time or run-time. A weakly typed language, on the other hand, may happily corrupt data and allow the program to continue running normally. Strongly typed languages include Java, C#, Smalltalk, Ruby, Python (I think TorqueScript can be added to the list, but I'm not sure). C is a weakly typed language; C++ is more strongly typed than C.

In a dynamically typed language, interfaces are typically implicit. A humorous synonym for dynamic typing is "duck typing" because "if it walks like a duck, and talks like a duck, then it is a duck." In a static typed language, polymorphic interfaces are implemented via inheritance, but polymorphism does not depend on inheritance in a dynamically typed language. In other words (using Smalltalk terminology): if you send a message to an object, the only thing that matters is whether the object understands that message. This is (I believe) how TorqueScript works; implicit interfaces are already implemented. Your extensions support *explicit* interfaces, which add no new functionality, but may be useful as documentation.
#7
07/19/2005 (2:39 am)
Useful as documentation and as a self-imposed methodological engineering constraint. Apologies for my loose usage of the terms though - I hadn't realized there was a difference :)

Edited for typo.
#8
07/19/2005 (1:58 pm)
To be honest, most people who aren't dynamic typing advocates don't distinguish between static/dynamic and strong/weak typing. As a result, programmers with a C/C++ background tend to exaggerate the danger of deferring type errors to run-time -- because run-time errors in a weakly typed language like C can silently corrupt the program in subtle ways.

I didn't mean to belittle your work. I'm all for having the option to formalize interfaces; though strictly unnecessary, it can make the code more intention-revealing. Also, explicit interfaces can be exploited by tools; e.g., scoped searches and automated refactorings, auto-completion, warnings for unimplemented methods. Though I tend to defer the creation of explicit interfaces until the core design has crystalized.
#9
07/23/2005 (4:23 am)
Quote:Though I tend to defer the creation of explicit interfaces until the core design has crystalized.

An interesting engineering method... there are definitely some foreseeable advantages....

Hmm... I must admit discomfort with this approach because it can ambiguate the meaning of an object signature... I wonder... In actuality, does semantic ambiguity become a problem very often with the approach? I mean, is it something that you have to worry about in practice?
#10
07/24/2005 (10:51 pm)
Is there a danger of accidentally mixing types because implicit interfaces allow LegalAgreement::contract() to be substituted for FishingNet::contract()? That's a common question, and no, it is rarely a problem in practice. For starters, you're unlikely to accidentally use an object with a wildly inappropriate type. Also, you're likely to invoke other methods on the same object, so if it has the wrong type, an error will probably occur eventually. Finally, automated tests will catch type errors along with everything else.
#11
07/29/2005 (9:41 pm)
Yay, thanks for helping out guys :)
#12
08/01/2005 (6:23 am)
Why this resource rating isn't a 5. Some people like to (ab)use the resource system as a means to make personal attacks. It happens. As you can see, the people who gave it a rating of 1 left no comment as to how I could improve the resource for them. What's even weirder is that 2 ratings of 1 occured in very quick succession AFTER the resource's rating had stabilized for a while. Before this, the resource was rated a 5.0, and has been rated at 5.0 by everyone else since then. So I think what we have here is a symptom of haterism.

So really, the bad ratings seem to be of little merit and probably nothing more than an attemp to make a personal attack against me. The sad thing is that this only really hurts the community since someone who might have otherwise benefitted from this resource might turn away because of a low rating.

So, in other words, it's just another case of some person or people drinking way too much Haterade :) It's even sadder that they don't have the courage to show their face or explain why.

Such is the way of the world.
#13
08/01/2005 (6:32 am)
Amazing work!
#14
08/01/2005 (7:19 am)
Perhaps anonymous ratings should be disallowed.

EDIT: Forget I said that. You already have to be logged in, and a little anonymity can be a good thing.
#15
08/01/2005 (11:39 am)
meh, i was seriously tempted to give you a 1 rating just cuz you were complaining about it so much.

but no, i did not so dont worry.
#16
08/01/2005 (9:19 pm)
Jason, thanks for refraining from using the rating system as a means of making personal attacks - and you even had the courage to admit thinking about it. You're okay in my book!

Now let's turn the topic back to the resource itself :D

Many types of multiple concrete inheritance are fully functional in this resource. Having played around with some things, I've found that the more simple forms of multiple concrete inheritance work perfectly. The key is to remember that calling Parent::func(%this) will not always do everything you want. For example, if you inherit from two concrete classes, you'll have to do more than just call the Parent::protInit to initialize the parent classes. Let's say you inherit SubClass from two concrete classes named ClassA and ClassB. The call to Parent::whateverFunction will always check the parent classes in order from left to right in the list (first ClassA then ClassB, and so on if there are more parent classes) for ::whateverFunction. Once the first one is found, it will be executed and that will be the end of that. This means it stops short of executing ::whateverFunction in ClassB if it's found in ClassA. Of course, if it's not found in ClassA, then it will be found in ClassB and there will be nothing to worry about. But to continue with the protInit example, if there is a protInit function in ClassA, ClassB won't be initialized unless a call to ClassB::protInit is added after the call to Parent::protInit. Same goes for other functions in the class where both the parent classes have different implementations. Calling the Parent::function by itself may not cut it... but then again, if you wish to skip ClassB's implementation, it actually may. So it all really depends. It's not hard at all to manage, and it generally workd out for most sensible mutliple concrete class inheritance forms.
#17
08/02/2005 (10:44 am)
Looks good. "What I mean" information built into code is always handy. I'm a C++er myself, but I've been known to sling some Java on occasion.

I have two things keeping me from rating this a 5 (feel free to fill me in, I'd be happy to change my rating):
1) TGE applicability. If someone could check out the feasibility of a TGE/TSE port for this, it's broader usage would easily rate a 5 (IMNSHO ;)
2) What happens when an object is missing one or more interface functions?

Another thing: Your files all have hard tabs. PLEASE convert them to spaces when making them available to a variety of people using a variety of editors. On my machine, I have editors (that I use) that interpret tabs:
1) As 2 spaces
2) as 3 spaces
3) As 4 spaces
4) As 8 spaces
5) as an advance to the next tab stop (wherever that might be)

So you can see why I'd much prefer spaces over tabs. I'm an extreme example, but probably not the worst case you'll find. And you can use an RNG to determine what any individual might have on their machine.

Spaces please. I'll reffer to a "general programming thread" where this particular horse was beaten to death, resurrected, flogged, tortured, beaten to death, beaten to a paste, spread that paste on toast, eaten, and then beat the eater's of said toast to death too, just to be sure: www.garagegames.com/mg/forums/result.thread.php?qt=29763

Oh, and this resource doesn't include a "make my game for me" button, and is therefore useless. ;)

--Mark
#18
08/02/2005 (1:37 pm)
Ctrl-H
"/t"
" "
#19
08/02/2005 (9:42 pm)
Quote:1) TGE applicability. If someone could check out the feasibility of a TGE/TSE port for this, it's broader usage would easily rate a 5 (IMNSHO ;)
Well, an older version of this resource has been successfully applied to an older version of TGE 1.3 following the exact instructions changes used to apply it to T2D... the changes are applied to code that is shared between all the Torque platforms so it doesn't really need any porting in that sense I don't believe. Of course, I could be wrong, but even if there were some slight adjustments needed, the instrtuctions should hopefully be detailed enough to help you navigate any differences between the platforms.

Really, the best thing to do is to just try it yourself. It shouldn't be hard at all I don't think.

As for tabs and spaces and such things... I don't know. I'll think about it.
Quote:2) What happens when an object is missing one or more interface functions?
I'm not really sure how you mean this? Do you mean like if a non-existant method is called on an object? If that happens, the console simply spits out a warning at run-time and proceeds to the next line of code. If you mean what happens if a concrete class has an abstract method called which is not defined... don't worry about that either. There is no such things as an abstract method in Torque Script. All interface functions DO technically have an implementation, it's just that the implementation is always empty. So you could call an interface function that hasn't been defined in a subclass and it will run the function without doing anything at all. There won't be any warnings or anything in the console, though that could be possible with the usage of some interface class naming standards.

Hopefully that answers your questions a little.
#20
09/18/2005 (8:06 am)
hmmm... after playing around with Torque for some [admittedly short] time now, it seems to me that its biggest deficiency is the inability to subclass purely in the script. I might have downloaded and played around with your zip files, except for one thing that you said:

'Interface class can NOT have ANY data members of any kind! For both practical and OO purity reasons, interface classes can no longer have any members. Public, protected, and private members are no longer valid for interface classes because they cannot easily be instantiated with MI and because overriding data member behavior is currently impossible.

Keep your interfaces pure like the should be - functions only!'

It seems to me that inheritance is just about useless if you can't inherit data members - that's the whole point about keeping interfaces "pure" and restricted to "functions only": inherit both MEMBERS and interfaces by default, and override when needed. If a child class can't have its own data members, what's the point of it being another class?
Page «Previous 1 2