Home » Community » Forums » » Creating a Scripting System in C++ Part III: Dynamic Loading
  Intel sponsors gamedev.net search:   
[Control Panel] [Register] [Bookmarks] [Who's Online] [Active Topics] [Stats] [FAQ] [Search]

Add Forum to Favorites |  Send Topic To a Friend | View Forum FAQ | Track this topic


 Last Thread Next Thread 
 Creating a Scripting System in C++ Part III: Dynamic Loading
Post Reply 
Great article,
I, however, still have a question in which direction you are going to take the VM and especially the compiler.
So here is the question:
Will the OPCODE be
1)very basic stack based commands (CALL,ADD,POP...)
or
2)game engine specific commands (DISPLAY MESSAGE, PLAY ANIMATION...)

If it is the second choice than I would like to know such a compiler would look like?

The think is I totally agree with using bigger OPCODES don't understand yet how to write a compiler to make them.

Thanks
Markus

BTW: I do think your articles are one of the best in the game community on this subject.


 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

I will be introducing opcodes which are more stack-based in the next article. However, realize that nothing I do should be taken as law written in stone as to how things should be done.

For your own projects, you may be best off using very specific opcodes as they relate to your game engine. For those who need more complexity, I am introducing possibilities for achieving it in my writing.

"Don't be afraid to dream, for out of such fragile things come miracles."

 User Rating: 1079   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Hey, great article, I'm not writing a scripting engine *yet*, but it let's me look at how not to make mistakes when I do it .

-------
Happy Coding!
Homepage: www.ussshrike.com/pcwebvia/

 User Rating: 1014   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link


Hi,

Thank you for a very educational tutorial. I find
this information very useful in my daily job, where
I have a instant need for simple changes/updates to applications. I'm used to take advantage of custom
.ini files, but they seems to not satisfy more than
about 10% of neccesary enhancments.

Scripting seems to me like the best solution for my
instantly growing problem.

Thank you again!



 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

    
void operator=(const Buffer& buf)
    {
        delete[] _data;
        _size = buf._size;
        _data = new char[_size];
        memcpy(_data, buf._data, _size);
    }
   


Be sure to check for self-assignment if operator= deals with heap data. Imagine "this" and buf were the same, then you would actually delete the heap-data of buf and then perform a memcpy on an empty data block. Bad idea...


Bye, VizOne

[edited by - VizOne on March 17, 2002 8:12:12 AM]

 User Rating: 1322   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

void operator=(const Buffer& buf)
{
    if (&buf == this)
        return;
    delete[] _data;
    _size = buf._size;
    _data = new char[_size];
    memcpy(_data, buf._data, _size);
} 


"Don't be afraid to dream, for out of such fragile things come miracles."

 User Rating: 1079   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

quote:
Original post by Redleaf
void operator=(const Buffer& buf)
{
    if (&buf == this)
        return;
    delete[] _data;
    _size = buf._size;
    _data = new char[_size];
    memcpy(_data, buf._data, _size);
}    


"Don't be afraid to dream, for out of such fragile things come miracles."


Well, better, but a operator = should return a self-reference (to make operations chainable), shouldn't it?

Buffer & operator=(const Buffer& buf)
{
    if (&buf != this)
    {
      delete[] _data;
      _size = buf._size;
      _data = new char[_size];
      memcpy(_data, buf._data, _size);
    }
    return *this;
}    


I guess in the context you use Buffer, it is not neccessary to implement that. Nevertheless it's more consequent to do it.

Bye, VizOne

[edited by - VizOne on March 18, 2002 8:58:26 AM]

 User Rating: 1322   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

I've just read your articles and they good overall. Well done on completing them.

However, I do have some queries about your coding. You might feel you should be allowed to code how you want, but because this is an article for people to learn from, you should try to show the best example you can.

In the "Cleaning Up" section, your Buffer object is just a reimplementation of part of std::vector<char>. I could understand you wanting to limit the interface but you shouldn't be reimplementing it.

Also, in the "Basic File Handling" section, you then go on to create file objects which just reimplement part of std::ifstream and std::ofstream. It looks like your just creating work for yourself.

You also get some input from the keyboard using a fixed sized buffer (after implementating a dynamic sized buffer). This is much more robust:

  
std::string input

std::cin >> input;
  


I am just very confused why you use C++ features, such as std::cin, std::vector, and then mix them up with C features, such as handling your own container memory and FILE, when they are much less robust and more error prone. I would prefer if you would use the solutions that are already in the standard library: it is better practice, your code would be more concise and you could progress quicker.

Also, you are using a leading underscore for member variables, which doesn't conform to standard C++ as these names are reserved for the compiler.

 User Rating: 1015    Report this Post to a Moderator | Link

I suggest you read that part of the standard again:
[lib.global.names] 17.4.3.1.2 Global names

Certain sets of names and function signatures are always reserved to the implementation:

— Each name that contains a double underscore (__) or begins with an underscore followed by an uppercase letter (2.11) is reserved to the implementation for any use.

— Each name that begins with an underscore is reserved to the implementation for use as a name in the global namespace.

Member variables are certainly not part of the global namespace.

As to buffer being a reimplementation of vector, it's certainly not. It's quite a bit more simplistic.

quote:
std::string input
std::cin >> input;

That's not going to grab an entire line if there are spaces.

I think I explained my choice as to files.

"Don't be afraid to dream, for out of such fragile things come miracles."

 User Rating: 1079   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Just to add to the war of "right" coding

Not that I'm in any way am coding to any standard, I use whatever is right in the situation I'm standing at. This might mean I use fullblown OOP model, or totally shift over to asm and do it there (Doing it fast, not "right").

The point here for me is that Redleaf is trying to show ppl who to create a scripting engine, not teach them how to program, it's expected that the reader can do that.

As to quote him from the first article
quote:

This series is intended to give the reader the information necessary to create a scripting system of his/her own from the ground up.


He also states the this will NOT be an OO engine, but only use C++ classes for the reason that they are there, quote:
quote:

This example will be object-based, but not necessarily object-oriented; the classes can therefore easily be replaced by structures for those dealing with a pure C mentality.


I guess that's the reason Redleaf leans towards C and not so much C++.

Looking from a newbies perspective I would guess that implementing your own file handling rutine will help you understand the i/ostream parts.

As I started with this is a script engine learning, not coding. I for that matter, do not use much of the code from here directly but do my own implementation of it, this implementation is pure OO and does use all the new nice features of C++ and VS.NET, but this certainly doesn't mean that my code is better, it's just different

So give the man some slack and let's focus on the scripting engine and if you want to worry about coding standard do that in your own code.

BTW. Nice set of articles

---------------------------------------------------
Life after death? No thanks, I want to live NOW
--- Sturm 2001

 User Rating: 1016   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

quote:
Original post by Redleaf
I suggest you read that part of the standard again:
[lib.global.names] 17.4.3.1.2 Global names

Certain sets of names and function signatures are always reserved to the implementation:

— Each name that contains a double underscore (__) or begins with an underscore followed by an uppercase letter (2.11) is reserved to the implementation for any use.

— Each name that begins with an underscore is reserved to the implementation for use as a name in the global namespace.

Member variables are certainly not part of the global namespace.



I don't know where you got that quote because there are additional things that you have missed out such as them also being reserved in ::std. In all coding standards I have ever read, they say you should not use underscores like this. There is also an issue with compilers that also reserve many identifiers starting with an underscore regardless of scope. The practise is simply frown upon.

quote:

As to buffer being a reimplementation of vector, it's certainly not. It's quite a bit more simplistic.



There is no reason to expose yourself to creating and deleting memory, buffer overruns and destructors, assignment operators and copy constructors. How can you possibly say it is more simplistic than this?

  
class Buffer
{
public:
    Buffer() { }
    Buffer(const char* d) : data(d) { }
    const char* Data() const { return data.c_str(); }
private:
    std::string data;
};
  


If you complain about data.c_str() being slow or something, you should really be using iterators. It just seems like you've made a redudant class that has no purpose.

quote:

That's not going to grab an entire line if there are spaces.



Use this then:
  
std::string string;
std::getline(std::cin, string, '\n');
  


quote:

I think I explained my choice as to files.



I really don't understand why you made that choice. You are learning C++ which has OO file streams already. Instead of learning and using them, you take the longer option of wrapping up the C ones in a class. You also name your class with methods similar to the C++ ones, which makes me think you know about them anyway. The C++ ones are much more elegant and safe.

I wouldn't be making these comments if you looked like a useless programmer. You seem to have a good knowledge of C++ practice (e.g. inheritence, correct casting, STL containers), I just really don't understand why you mix it up with C code that is much less robust. Also, you say these things make your article more simple but that doesn't stop them taking up about half of it.

You should be striving to improve these things and not making excuses. It would improve the quality and educational value of the article vastly.

 User Rating: 1015    Report this Post to a Moderator | Link

AP,

You do realise that there are incompatible implementations of the STL, right? Some compilers can't even handle templates properly. There will always be a use for custom classes, even if they appear to reimplement what has already been done.

I do agree with you about the leading underscore. I used to do that and ran into problems on a project. Now I use trailing underscores:

_somedata;
somedata_; // better

Still, I don't see the point on picking his code apart. He is attempting to convey a relatively complex topic.


Dire Wolf
www.digitalfiends.com

 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Your points are heard, AP, and noted. I'll keep you in mind as I write the next article.

As to the reference regarding underscores, that comes from the C++ Standard. Bartosz Milewski from Relisoft pointed that out to me when I asked him about the same thing, in case you were wondering.

"Don't be afraid to dream, for out of such fragile things come miracles."

[edited by - Redleaf on March 19, 2002 7:27:16 PM]

 User Rating: 1079   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Good article. The topic of loading your scripts is definally important. I'm looking forward to see how you have handled the stack. In the VM I've been designing/writing each variable has:
BeginRead,EndRead,BeginWrite,EndWrite
This allows be to directly access the internal structure of the varaible (supports all the standard primative datatypes), and allows the stackpointer to be used as a peudo-variable. IE when you read from it, it pop's an item off the stack, when you write to it, it pushes the result onto the stack.

Also this would allow multipule scripts to run co-currently, as each instruction is atomic to the script. IE c := a+b, 1 op


Since reading the article I'm planning to allow my Instruction classes to read asm code, as well as bytecode. Since I plan to allow the compiler to parse asm, it would be simplistic to move the asm parsing code into the loading code for an Instruction

I'm very interested in these articles, as for the debate on naming, I'm writting my compiler and VM in Object pascal for Delphi5. Thus as long as I can follow the concepts I can translate them into my language of choice.

 User Rating: 1044   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

As of yet you haven't handled control structures. Given that this is a high level script, are you going to resort to using goto? or biuld control constructs directly into the scripting bytecode.
Are you going to use flat bytecode (intel,java, etc) or a tree-like structure for your instructions?

  
IE 
if (condition)
  {
  (* Statements*)
  }
else
  {
  (* Statements*)
  }

Translates into :
if
  - condition
  - true path statements
  - false path statements

were "-" indicates a child instruction.
  


I personally feel that the tree-like structure is ideal for scripts. This makes the implemenation of break,exit,continue and control construct simplistic. As all they have to do is walk the tree of parent instruction to find the place to jump to. Procedures would also be simlified, as you can create a special instruction (ie OP_EntryPoint) which owns its code, and could contain useful debug information.

On the note of debugging, scripting are normally sadly lacking in this regard. Dispite the fact it is trivial to include information into the bytecode (and it would be handled at load-time and not run-time). I think it would be a good idea of implementing some debugging tools for the VM.



 User Rating: 1044   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

The types of instructions I will be providing as example in the next article will not really be too high level at all. For all intents and purposes, higher level control structures will be handled through patterns of simplistic jump codes. That doesn't mean control flow can't be handled the way you present it, but I feel a flat structure is easier to model for the sake of this series.

As for debugging, I do intend to include additional facilities for this as they become apparent. Please feel free to share any ideas regarding this, as I could always use some more.

"Don't be afraid to dream, for out of such fragile things come miracles."

 User Rating: 1079   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

I read all three articles in the series back to back. Oh boy it gave me a brain aneurism. :-) I've been working on my scripting engine and I read your article for ideas. I know people have been extremly critical of your programming style so far, so I apologize up front.

I'm not much of a C++ person, but it seems to me that all your code snippets are classes. Please correct me if I'm wrong but I thought classes where the domain of C++ and not C? Anyways, my implimentation is in visual basic (ok, laugh all you want), and I started off with the lexical analyzer and was about to procede to creating a parser for the script, and then I realized I had no clue which method of design for a virtual machine would suit my needs. So today I wrote and tested 3 different implimentations of a virtual machine, and each one hardly had any code at all in it. My point is that I don't really understand why you have all the code that you do. Perhaps some pseudo code would be better so people can understand concepts instead of worrying about a silly underscore. I get stumped on lines like
code Code _code;
that's some scary stuff.

 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Well, I really don't mean to be rude or anything, so keep that in mind as you read my suggestions.

First of all, if you read the title of the article series, it says "Creating a Scripting System in C++." I never said I was using straight C.

The line that stumped you I'm guessing is found in the Instruction class. "opcode _code;" This is simple variable declaration of a type declared by enumeration. For the sake of clarity, it is basically a typedef of an int. If something like this stumps you, you really should be reading more introductory material on the programming language first, before reading any more specific articles, like this one. This series isn't meant to teach you a language.

I do try to explain the concepts as I go along with the source code snippets. Perhaps you'd like to share with me a specific example where it doesn't seem I explain well enough? Then it would be much easier for me to clear up the misunderstanding.

"Don't be afraid to dream, for out of such fragile things come miracles."

 User Rating: 1079   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link


I agree with the other Anonymous Poster that there's too
much "reinventing the wheels" here. The Buffer is
just a vector<char> and the File classes just wraps the
C I/O library into C++... which the iostream library
already does. It's as if the author has nothing better
to do than to rewrite rigorously tested code better
known as the Standard Template Library.


 User Rating: 1015    Report this Post to a Moderator | Link

Well no, actually. The fstream classes do not implement typesafe manipulation of binary files. A class would have to be created to deal with that whether or not I chose C or C++ file IO. If you would like to work with the typeless read() and write() of the fstream, be my guest.

And the buffer is not always intended to simply be a character array. Explicitly creating a class to represent the buffer forces the reader to understand exactly how the data is internally represented so that they can knowingly make use of it to store any relevent data necessary for whatever instruction types they decide to create. Creating the buffer was a matter of illustration, and I gladly welcome anybody to replace it with whatever they choose.

"Don't be afraid to dream, for out of such fragile things come miracles."

 User Rating: 1079   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

All times are ET (US)

Post Reply
 Last Thread Next Thread 
Forum Rules:
You may not post new threads
You may post replies
You may not edit your posts
You may not use HTML in your posts
Jump To:
Administrative Options: