can c++ be used as a scripting language

Started by
10 comments, last by Brain 7 years, 9 months ago

I know different scripting languages have their benefits, i was just wondering if it was possible to use c++ as a scripting language, like having your game engine code and gameplay code both using c++ but still be separated enough that the gameplay code wont directly affect the engine code. What would be the benefits of doing this? What would be the disadvantages?

Advertisement

Yes, I do this for my personal code. Here's my take on my 1-3 man projects I do at home, and some limited experience in a studio that used run-time compiled C++:

Benefits

  • Fast iteration time (near instant recompile-reload of code)
  • Can use code as an "editor" in most cases. Modifying UI offsets, animation/gameplay offsets, tweaking behaviors, ai routines, hitboxes, etc. In many cases a real editor would just let the user input text into little text boxes, and C/C++ can do this just fine. More onus is put on the engineer to write more functional styled code, but that's a plus in my opinion.
  • Forces a clear definition of where memory is, what code is linked where, and forces compile times to stay super fast. Requiring fast compile times can be a good learning exercise to gain a practical understanding of what types of C++ styles negatively and positively affect compilation times.
  • Run-time debugging. This one might require one to learn new ideas or tricks, but if code is written in a certain way it can be visually debugged and modified at run-time. An example where I did this was for implementing 3D quick hull. After each quick hull iteration, as long as all data structures are in-tact, a debug-drawing function can be called. The debug drawing, as well as quickhull itself can be modified at run-time to diagnose or fix bugs. The reason this helps is the programmer can now try to focus on details they care most about, without maintaining superfluous info in their head. For example, say I forget if during hull expansion I need to hook up this face to that one, or, did I need to just delete the face entirely? I can just try it out and see what happens quickly since iteration time is faster than me making on-paper drawings and derivations. This really lets the programmer clear his thought space and put more focus on whatever is most important at that moment, and forget a lot of other details that would otherwise be necessary to remember.

Some drawbacks

  • Can end up relying heavily on the idea of code as an "editor". For larger teams tools can suffer making individuals not great at C/C++ unable to perform efficiently
  • Restrictions on what kind of code can be written. There are some styles, ranging from pretty simple to very complicated. I personally go the very simple route which has restrictions like: no heap-memory function pointers, no heap-memory vtables, pretty much no heap memory used within hot-loadable code, and the list goes on.
  • Most integral team members will probably have to work in C or C-styled C++, unless work is put into a complicated system to support more C++ features. This includes game designers. So on teams that use this kind of run-time compiled code, designers best be able to write some effective code. In my limited experience finding designers who can write good C/C++ is like searching for a blue unicorn -- however the insane iteration time helps engineers to refine their design abilities quickly.

Overall I think the iteration time is the big point. The ability to modify code and algorithms (and stack variables, or the process bss/data segments, etc.) is just too good to pass up. At work it can take 5 or so minutes to change a line of code, recompile, and see it in-game. At home this can take less than a second. I've had experience with this kind of hotloadable code at a major studio in the past and can say it makes me 10x more productive as an engineer.

As an example of style differences, take a look at a more traditional C++ styled function:


void Player::Update( float dt )
{
	if ( right_button )
	{
		m_position.x += m_runspeed * dt;
	}
}

A regular function call is executed as a member function on some player class. Member variables for position and run_speed are used. With hotloadable code it suddenly becomes more important to place constants and tweakables in the process memory directly (or on the stack), so when code is compiled and reloaded at run-time new data can be used. So we can modify the above function to something more like:


void Player::Update( float dt )
{
	float run_speed = 10.0f;

	if ( right_button )
	{
		m_position.x += run_speed * dt;
	}
}

Alternatively a #define can be used for run_speed.

Since this style places focus on algorithms instead of abstractions (like function pointers, callbacks, heap allocated objects, etc.) it becomes clearly beneficial to use things like in-code state machines, and POD style data types that can easily be moved around in memory. For virtual dispatch enums or switch statements become obvious and simple choices over the virtual keyword, since they can be hotloaded along with the rest of the code without additional complexity or memory-patching routines.

Thanks for the reply, I think I'm gonna try this out, My team for now is just two guys both of us are pretty good at c++, Also if you have any tips, or anything of the sort that you think should know please don't refrain from replying.

Ive done this employing C's macro mechanism to simplify what 'Script' has to be written (used for behavioral control of simple intelligent objects) .

It is also useful for structured scripts (like the start/end/else state constructs of Finite State machines) ... not just individual function calls (several of the AI Game Programming Wisdom books had several articles about macros doing that including, hierarchical finite state machines)

Advantage is that a routine/repetitive pattern of code (which sometimes are rather bulky) can be reduced to much simpler 'script form' and an assumption of the script features being used in a systematic way (eliminating nesting/spaghetti code hell). The macro Script restricts what variables and calls can be accessed through the 'Script'

Another is that specialize code can be created/customized by just adding another 'macro' to your 'language' (and if when needed you can still insert actual NATIVE code in (hopefully few) trouble spots to get done exactly what you want/need)

The C preprocessor then converts your simpler macro Script into native code now subject to the compilers optimizing abilities and can run directly without interpretor overhead (including eliminating subroutine calls)

Disadvantage : is some extra difficulty debugging where the 'script' produced code is mutated

-

Some people may say 'why bother optimize' , but when you are running thousands of active/reactive objects of this level of complexity EVERY Turn, the optimization can spell a great difference in the size of the players game environment

---

One thing that added a little difficulty was : that my 'nice' grouping of Script Code created for each different object typewere (in my usage) run in different sections of the program, even though the 'Script' has their chunks defined right next to each other (instead of breaking them up onto separate files, and the bother/confusion/disorganization that entails, and you seek to eliminate).

So its good learning how to use #define #ifdef etc... to modalized the script text so that by using multiple #include of the same file (each employing a different #define 'mode' for a different place in the program)

example (a LOCKSTEP behavior processing for active/reactive object on a grid map which use finite state machines)

Situation detection phase - all objects Scan and detect a number of stimulus to potentially react to (from their current local situation) and filter/prioritize them according to the objects mode (with potential interrupts for 'tactics' already in progress)

Solution Classification phase - all objects digest their 'stimulus' set, decided ONE best action/tactic initiation (avoiding conflicting actions with other objects already busy interacting - which could change since previous phase)

Action Processing - carry out all decided actions (including animations) and generate results (resolve conflicts), and adjust the game situation

-

The above runs a simulation in a lockstep manner, so the separate phase chunks of code for each object type (even though grouped together in the script file) get placed in the corresponding 'phase' section of the program whose basic organization was 'the Big Switch' (nested) (My program used three phases, but the chunk split-up still happens if there are only two phases required by a lockstep simulation)

--------------------------------------------[size="1"]Ratings are Opinion, not Fact

Ive done this employing C's macro mechanism to simplify what 'Script'  has to be written (used for behavioral control of simple intelligent objects) .

[...]

 

I don't really use #define, #ifdef that much but seeing as it could be useful I guess I could start looking into that, THANKS for the answer it was very informative and helpful.
?Also quick question is there a difference between a finite state machine and a state machine or do they both mean the same thing

It is certainly possible, and being done (I do it myself). There is a nice in-depth description of yet someone else using C++ for scripting on the molecular web site.

The obvious problem about using C++ as a scripting language is that it is not a scripting language.

Scripting languages suck because they are so slow, blah blah. But they also have some very noticeable advantages (some of these are more obvious, some are less obvious but nonetheless important). For example, a scripting language exposes to the script writer exactly what you want it to expose, and nothing else. It enforces that you do exactly as allowed, nothing else. An invalid script should never result in an application crash or undefined state, either.

C++ does not do things like bounds checking, and it allows you to dereference any valid or invalid pointer, reading from or writing to any address. You can in principle call any OS function, including one that modifies files on the harddisk, opens a process handle, or... a network socket.

Which means that user-modifiable scripts are always in some way troublesome. A badly written user script may cause a crash (which you can catch in a handler, see the molecular article), but it might also mess up some random state in a non-obvious way without causing a crash. It might deliberately change state, allowing a player to cheat. It might allow someone to publish a "mod" that has a somewhat nicer window layout and uses your game to host a botnet. It might let someone write a ransomware mod on top of your game. Imagine the PR nightmare linked to "your game encrypted my harddisk and demands money". You will never recover from that one.

If scripts reside on a user-controlled computer, it is a valid consideration to remove the feature before shipping, and instead compile all scripts to native code right into the application (or into signed binary modules). Of course that approach isn't without problems either. Among other things, it means that what user and developer have in their hands is no longer 100% identical... It is thus unlikely but possible that the user experiences a bug that the developer cannot reproduce.

I'm only using C++ as server-side scripting language. User-side, no thank you, I do not like to open Pandora's box. Never know what comes out of it. On a server, however, it gives me fast iteration, the ability to modify behaviour at runtime, and native speed.

I know different scripting languages have their benefits, i was just wondering if it was possible to use c++ as a scripting language, like having your game engine code and gameplay code both using c++ but still be separated enough that the gameplay code wont directly affect the engine code. What would be the benefits of doing this? What would be the disadvantages?

Generally you could take every coding language into a scripting language when providing the runtime for it to be interpreted on. Scripting languages are different from coding languages in that way that they dont run on hardware directly but inside of some kind of VM that has its own processor interpreting the specific bytecode generated from source.

The benefits of scripting over hardcoded assemblies is simply the exchangability and stop n go you would getting hard to work in compiled platform specific programs.

But I would not recommend to use C/C++ or C# as scripting language because of the strict data model where in LUA or JavaScript for example data types were less strictly managed. Also the memory model of C/C++ is to different for a real scripting language and if you dismiss well established procedures like new/delete or malloc/free then you have something that may look like if it were C/C++ but didnt handle as that

Ok from what ive gathered so far it is possible to use any language as a scripting language, some easier than others, but the issue with using system programming languages like c++ is that, because of it's low level access users could use c++ to create different forms of malware, hacks etc. Also I have come to understand that if you want your game to be moddable use of traditional scripting languages might be a good idea ..... One question couldn't one script in c++ then expose the necessary functions and variables to a scripting language in case someone wants to mod thegame or would that be too much work for nothing

One question couldn't one script in c++ then expose the necessary functions and variables to a scripting language in case someone wants to mod thegame or would that be too much work for nothing

This is how scripting languages in games work. You have your LUA runtime for example and expose C++ functions to it when initializing the runtime so LUA could interact with your game. Ohterwise it wouldnt be possible because the LUA runtime (as I mentioned scripting languages are executed in there own runtime) wouldnt know about what functions it may call in your C++ game.

Same is true when you take a look at Unity. They have an own C# assembly called UnityEngine that contains callbacks into the C++ engine side. Your "compiled" game then is running inside the Unity Player application in its own mono runtime

If scripts reside on a user-controlled computer, it is a valid consideration to remove the feature before shipping, and instead compile all scripts to native code right into the application (or into signed binary modules). Of course that approach isn't without problems either. Among other things, it means that what user and developer have in their hands is no longer 100% identical... It is thus unlikely but possible that the user experiences a bug that the developer cannot reproduce.

In Molecule, the scripts are always compiled to native code.

In a development build, each script is compiled into a single shared library for hot-reloading, fast iteration times, etc.

In a retail build, all scripts are compiled into the game executable, so the builds that go through QA are also what ends up in the user's hands - 100% identical.

There recently was a similar question on the Handmade Hero forums where my engine was also mentioned, and I provided a bit more info over there for anybody who's interested.

This topic is closed to new replies.

Advertisement