问题
I had an idea of a program I want to write, but which language would be best is my problem.
If I have a car racing game and I want to allow users to submit code for new interactive 3D race tracks (think of tracks such as found in the Speed Racer movie), vehicles and for their autonomous vehicles, so, they would create the AI for their car that will enable the car to determine how to handle hazards.
So, I need a language that will run fast, and as part of a world map that the server has of all the possible races available, and their various states.
I am curious if this would be a good reason to look at creating a DSL in Scala, for example?
I don't want to have to restart an application to load new dlls or jar files so many compiled languages would be a problem.
I am open to Linux or Windows, and for the languages, most scripting languages, F#, Scala, Erlang or most OOP I can program in.
The user will be able to monitor how their vehicle is doing, and if they have more than one AI uploaded for that car, when it gets to certain obstacles they should be able to swap one AI program for another on demand.
Update: So far the solutions are javascript, using V8, and Lua.
I am curious if this may be a good use for a DSL, actually 3 separate ones. 1 for creating a racetrack, another for controlling a racecar and the third for creating new cars.
If so, would Haskell, F# or Scala be good choices for this?
Update: Would it make sense to have different parts end up in different languages? For example, if Erlang was used for the controlling of the car and Lua for the car itself, and also for the animated racetrack?
回答1:
Your situation sounds like a good candidate for Lua.
- You need sandboxing: This is easy to do in Lua. You simply initialize the users' environment by overwriting or deleting the
os.execute
command, for instance, and there is no way for the user to access that function anymore. - You want fast: Check out some of the Lua benchmarks against other languages.
- Assumably you need to interoperate with another language. Lua is very easy (IMO) to embed in C or C++, at least. I haven't used LuaInterface, but that's the C# binding.
- Lua has first-order functions, so it should be easy to swap functions on-the-fly.
- Lua supports OOP to some extent with metatables.
- Lua's primary data structure is the table (associative array) which is well-suited to sparse data structures like integrating with a world map.
- Lua has a very regular syntax. There are no funny tricks with semicolons or indentation, so that's one less thing for your users to learn when they are picking up your language -- not to mention, using a well-documented language takes away some of the work you have to do in terms of documenting it yourself.
Also, as @elviejo points out in a comment, Lua is already used as a scripting language in many games. If nothing else, there's certainly some precedent for using Lua in the way you've described. And, as @gmonc mentions, there is a chance that your users have already used Lua in another game.
As far as how to integrate with Lua: generally, your users should simply need to upload a Lua script file. To grossly oversimplify, you might provide the users with available functions such as
TurnLeft
, TurnRight
, Go
, and Stop
. Then, the users would upload a script like
Actions = {} -- empty table, but you might want to provide default functions
function Actions.Cone()
TurnLeft()
end
function Actions.Wall()
Stop()
TurnRight()
TurnRight()
Go()
end
Then server-side, you would might start them off with a Go()
. Then, when their car reaches a cone, you call their Actions.Cone()
function; a wall leads to the Actions.Wall()
function, etc. At this point, you've (hopefully) already sandboxed the Lua environment, so you can simply execute their script without even much regard for error checking -- if their script results in an error, no reason you can't pass the error on directly to the user. And if there aren't any errors, the lua_State
in your server's code should contain the final state of their car.
Better example
Here's a standalone C file that takes a Lua script from stdin and runs it like I explained above. The game is that you'll encounter Ground, a Fence, or a Branch, and you have to respectively Run, Jump, or Duck to pass. You input a Lua script via stdin to decide how to react. The source is a little long, but hopefully it's easy to understand (besides the Lua API which takes a while to get used to). This is my original creation over the past 30 minutes, hope it helps:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#define FAIL 0
#define SUCCESS 1
/* Possible states for the player */
enum STATE {
RUNNING,
JUMPING,
DUCKING
};
/* Possible obstacles */
enum OBSTACLE {
GROUND,
FENCE,
BRANCH
};
/* Using global vars here for brevity */
enum STATE playerstate = RUNNING;
enum OBSTACLE currentobstacle = GROUND;
/* Functions to be bound to Lua */
int Duck(lua_State *L)
{
playerstate = DUCKING;
return 0; /* no return values to Lua */
}
int Run(lua_State *L)
{
playerstate = RUNNING;
return 0;
}
int Jump(lua_State *L)
{
playerstate = JUMPING;
return 0;
}
/* Check if player can pass obstacle, offer feedback */
int CanPassObstacle()
{
if ( (playerstate == RUNNING && currentobstacle == GROUND) )
{
printf("Successful run!\n");
return SUCCESS;
}
if (playerstate == JUMPING && currentobstacle == FENCE)
{
printf("Successful jump!\n");
return SUCCESS;
}
if (playerstate == DUCKING && currentobstacle == BRANCH)
{
printf("Successful duck!\n");
return SUCCESS;
}
printf("Wrong move!\n");
return FAIL;
}
/* Pick a random obstacle */
enum OBSTACLE GetNewObstacle()
{
int i = rand() % 3;
if (i == 0) { return GROUND; }
if (i == 1) { return FENCE; }
else { return BRANCH; }
}
/* Execute appropriate function defined in Lua for the next obstacle */
int HandleObstacle(lua_State *L)
{
/* Get the table named Actions */
lua_getglobal(L, "Actions");
if (!lua_istable(L, -1)) {return FAIL;}
currentobstacle = GetNewObstacle();
/* Decide which user function to call */
if (currentobstacle == GROUND)
{
lua_getfield(L, -1, "Ground");
}
else if (currentobstacle == FENCE)
{
lua_getfield(L, -1, "Fence");
}
else if (currentobstacle == BRANCH)
{
lua_getfield(L, -1, "Branch");
}
if (lua_isfunction(L, -1))
{
lua_call(L, 0, 0); /* 0 args, 0 results */
return CanPassObstacle();
}
return FAIL;
}
int main()
{
int i, res;
srand(time(NULL));
lua_State *L = lua_open();
/* Bind the C functions to Lua functions */
lua_pushcfunction(L, &Duck);
lua_setglobal(L, "Duck");
lua_pushcfunction(L, &Run);
lua_setglobal(L, "Run");
lua_pushcfunction(L, &Jump);
lua_setglobal(L, "Jump");
/* execute script from stdin */
res = luaL_dofile(L, NULL);
if (res)
{
printf("Lua script error: %s\n", lua_tostring(L, -1));
return 1;
}
for (i = 0 ; i < 5 ; i++)
{
if (HandleObstacle(L) == FAIL)
{
printf("You failed!\n");
return 0;
}
}
printf("You passed!\n");
return 0;
}
Build the above on GCC with gcc runner.c -o runner -llua5.1 -I/usr/include/lua5.1
.
And pretty much the only Lua script that will pass successfully every time is:
Actions = {}
function Actions.Ground() Run() end
function Actions.Fence() Jump() end
function Actions.Branch() Duck() end
which could also be written as
Actions = {}
Actions.Ground = Run
Actions.Fence = Jump
Actions.Branch = Duck
With the good script, you'll see output like:
Successful duck! Successful run! Successful jump! Successful jump! Successful duck! You passed!
If the user tries something malicious, the program will simply provide an error:
$ echo "Actions = {} function Actions.Ground() os.execute('rm -rf /') end" | ./runner PANIC: unprotected error in call to Lua API (stdin:1: attempt to index global 'os' (a nil value))
With an incorrect move script, the user will see that he performed the wrong move:
$ echo "Actions = {} Actions.Ground = Jump; Actions.Fence = Duck; Actions.Branch = Run" | ./runner Wrong move! You failed!
回答2:
Why not JavaScript or EcmaScript? Google's V8 is a really nice sandboxed way to do this. I remember it being really really easy. Of course, you will have to write some bindings for it.
回答3:
I would recommend Dot Net for several reasons:
Players can choose which language they implement their solutions in: C#, IronPython, VB.NET, Boo, etc. but your runtime wouldn't care - it is just dynamically loading dot net assemblies into its sandbox. But this gives your players a choice of their own favorite language. This encourages players to enjoy the experience, rather than some players deciding not to participate because they simply don't like the single language that you chose. Your overall framework would probably be in C#, but players' code could be in any Dot Net language.
Sandboxing and dynamically loading are very mature in Dot Net. You could load the players' assemblies into your own sandboxed AppDomains that are running with Partial Trust. You would not have to restart the container process to load and unload these player AppDomains.
Players are encouraged to "play" this game because the language (whichever Dot Net language they choose) is not only useful for game scripting, but can lead to a real career in the industry. Doing a job search for "C#" gives a lot more hits than for "Lua" or "Haskell", for example. Therefore, the game is not only fun, but especially for younger players, is actually helping them to learn genuinely useful, marketable skills that can earn them money later. That is big encouragement to participate in this game.
Execution speed is blazing. Unlike some alternatives like Lua, this is compiled code that is well known for excellent performance, even in real-time games. (See Unity3d, for example).
Players can use MonoDevelop on Mac and Linux or they can use Visual Studio Express for free from Microsoft, or they can use good ol' notepad and the command line. The difference from the alternatives here is that mature, modern IDE's are available if players should choose to use them.
A DSL doesn't seem like a good idea for the AI parts of the problem simply because implementing AI for the vehicles is going to require a lot of creative problem solving on the part of the players. With a DSL, you are locking them into only the way that you defined the problem when you thought about it. Smart players with a complete platform like Dot Net (or some of the other choices mentioned) might have radically new and innovative solutions to some of the AI problems that you never foresaw. With the right tools, these players could implement crazy learning programs or small neural networks or who knows what in order to implement their AI. But if you lock them into a simplified DSL, there might not be much variety in different players' AI implementations (because their set of available expressions of ideas is so much smaller).
For the other parts of the problem such as defining the tracks, a DSL might be fine. Again, though, I would lean toward one of the simpler Dot Net languages like Boo simply so that you can have a unified tech stack for your entire project.
回答4:
I had done in MMO before, you know, NPC response scripts were using python, while it is in a framework of C++, say any NPC related action will trigger the framework to run a python script (a C-python interface of course, not a shell call such as "python /script/run.py"). The scripts are replaceable runtime, though need the player or game admin to issue a command to do a refresh, but anyway the game server program is not required to restart.
Actually I forgot that whether "do a refresh by issuing a command" was required or not for a new script runtime..2 years before...but I think it suitable for you.
回答5:
Consider Erlang:
You need sandboxing / DSL: you can write "parser generators" to scrub access to critical/vulnerable system calls. The stock compiler can be "easily" enhanced with this functionality.
You need fine-grained scheduling : you have some control over this also provided you run each "user" in separate emulators. Maybe you can do better but I'd have to dig more. Remember the scheduling is O(1) :-)
You need resource partitioning between your "players" ( I guess if I understood correctly): Erlang has no shared-state so this helps from the on-start. You can easily craft some supervisors that watch resource consumption of the players etc. See also link on above point (lots of knobs to control the emulator).
You need code hot-swapping: Erlang was designed for this from the on-start
You need scaling: Erlang scales with SMP nicely and since it is based on message passing with seamless inter-machine communication, you can scale horizontally
You can optimize the critical paths using C drivers
Integrated "supervisor" functionality for restarting gracefully "users"
Ulf Wiger on Concurrency
来源:https://stackoverflow.com/questions/1583786/looking-for-good-server-side-language-that-will-allow-players-to-upload-code-tha