寻找良好的服务器端语言,允许玩家上传可执行的代码

时间:2009-10-18 01:59:21

标签: dsl architecture

我知道我想写的程序,但哪种语言最好是我的问题。

如果我有一个赛车游戏,我想让用户提交新的交互式3D赛道的代码(想想Speed Racer电影中的曲目),车辆和他们的自动驾驶汽车,那么,他们会为他们的汽车创建AI,使汽车能够确定如何处理危险。

所以,我需要一种能够快速运行的语言,并且作为服务器具有所有可用种族的世界地图的一部分,以及它们的各种状态。

我很好奇,例如,这是否是在Scala中创建DSL的一个很好的理由?

我不想重新启动一个应用程序来加载新的dll或jar文件,因此很多编译语言会出问题。

我对Linux或Windows开放,对于语言,大多数脚本语言,F#,Scala,Erlang或大多数OOP我都可以编程。

用户将能够监控他们的车辆如何运行,以及他们是否为该车上传了多个AI,当遇到某些障碍时,他们应该能够按需交换一个AI程序。 / p>

更新:到目前为止,解决方案是javascript,使用V8和Lua。

我很好奇这是否适用于DSL,实际上是3个独立的DSL。 1用于创建赛道,另一个用于控制赛车,第三个用于创建新车。

如果是这样,那么Haskell,F#或Scala会选择这个吗?

更新:让不同的部分以不同语言结尾是否有意义?例如,如果Erlang用于控制汽车,Lua用于汽车本身,还用于动画赛道?

5 个答案:

答案 0 :(得分:31)

你的情况听起来像是Lua的好候选人。

  • 您需要沙盒:这是easy to do in Lua。例如,您只需通过覆盖或删除os.execute命令来初始化用户的环境,用户无法再访问该功能。
  • 您想要快速:查看部分Lua benchmarks against other languages
  • 可能你需要与另一种语言互操作。 Lua非常容易(IMO)嵌入C或C ++,至少。我没有使用LuaInterface,但这是C#绑定。
  • Lua具有一阶函数,因此在运行中交换函数应该很容易。
  • Lua supports OOP to some extent与metatables。
  • Lua的主要数据结构是table(关联数组),它非常适合稀疏数据结构,例如与世界地图集成。
  • Lua有一个非常规则的语法。没有带有分号或缩进的有趣技巧,所以当用户拿起你的语言时,用户学习的东西就少了 - 更不用说,使用记录良好的语言会带走你必须做的一些工作。自己记录的条款。

另外,正如@elviejo在评论中指出的那样,Lua已经在许多游戏中被用作脚本语言。如果不出意外,以你所描述的方式使用Lua肯定有一些先例。并且,正如@gmonc所提到的,您的用户可能已经在另一个游戏中使用过Lua。

<小时/> 至于如何与Lua集成:通常,您的用户应该只需要上传Lua脚本文件。要严重过度简化,您可以向用户提供TurnLeftTurnRightGoStop等可用功能。然后,用户将上传像

这样的脚本
Actions = {} -- empty table, but you might want to provide default functions 
function Actions.Cone()
    TurnLeft()
end

function Actions.Wall()
    Stop()
    TurnRight()
    TurnRight()
    Go()
end

然后在服务器端,您可以使用Go()启动它们。然后,当他们的汽车到达一个圆锥时,你会调用他们的Actions.Cone()功能;墙壁通向Actions.Wall()函数等。此时,您(希望)已经沙箱化了Lua环境,因此您可以简单地执行其脚本而不必过多考虑错误检查 - 如果它们的脚本结果在一个错误中,没有理由你不能直接将错误传递给用户。如果没有任何错误,服务器代码中的lua_State应该包含他们汽车的最终状态。

<小时/>

更好的例子

这是一个独立的C文件,它从stdin获取Lua脚本并像我上面解释的那样运行它。游戏是你会遇到地面,栅栏或树枝,你必须分别跑,跳或鸭子通过。您通过stdin输入Lua脚本来决定如何做出反应。源代码有点长,但希望它很容易理解(除了需要一段时间才能习惯的Lua API)。这是我过去30分钟的原创作品,希望有所帮助:

#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;
}

使用gcc runner.c -o runner -llua5.1 -I/usr/include/lua5.1在GCC上构建上述内容。

几乎每次成功通过的唯一Lua脚本是:

Actions = {}

function Actions.Ground() Run() end
function Actions.Fence() Jump() end
function Actions.Branch() Duck() end

也可以写成

Actions = {}
Actions.Ground = Run
Actions.Fence = Jump
Actions.Branch = Duck

使用好的脚本,您将看到如下输出:

Successful duck!
Successful run!
Successful jump!
Successful jump!
Successful duck!
You passed!

如果用户尝试恶意攻击,程序只会提供错误:

$ 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))

如果移动脚本不正确,用户将看到他执行了错误的操作:

$ echo "Actions = {} Actions.Ground = Jump; Actions.Fence = Duck; Actions.Branch = Run" | ./runner 
Wrong move!
You failed!

答案 1 :(得分:10)

为什么不使用JavaScript或EcmaScript?谷歌的V8是一个非常好的沙盒方式。我记得它真的很容易。当然,你必须为它写一些绑定。

答案 2 :(得分:2)

我推荐Dot Net有几个原因:

玩家可以选择使用哪种语言来实现他们的解决方案:C#,IronPython,VB.NET,Boo等,但运行时并不关心 - 它只是动态地将点网组件加载到其沙箱中。但这会让您的玩家选择自己喜欢的语言。这鼓励玩家享受体验,而不是一些决定不参与的玩家,因为他们根本不喜欢你选择的单一语言。你的整体框架可能在C#中,但玩家的代码可能是任何Dot Net语言。

沙盒和动态加载在Dot Net中非常成熟。您可以将玩家的程序集加载到与Partial Trust一起运行的沙盒AppDomains中。您不必重新启动容器进程来加载和卸载这些播放器AppDomains。

鼓励玩家“玩”这个游戏,因为语言(他们选择的Dot Net语言)不仅对游戏脚本有用,而且可以带来真正的行业生涯。例如,搜索“C#”可以获得比“Lua”或“Haskell”更多的批次命中。因此,游戏不仅有趣,而且特别是对于年轻玩家来说,实际上是帮助他们学习真正有用的,适销对路的技能,以后可以赚钱。这是参与这场比赛的巨大鼓励。

执行速度很快。与Lua这样的替代品不同,这是一种编译代码,即使在实时游戏中也能以出色的性能而闻名。 (例如,参见Unity3d)。

玩家可以在Mac和Linux上使用MonoDevelop,也可以从Microsoft免费使用Visual Studio Express,或者他们可以使用好的记事本和命令行。与此处的替代方案的不同之处在于,如果玩家应该选择使用它们,则可以使用成熟的现代IDE。

对于问题的AI部分而言,DSL似乎不是一个好主意,因为为车辆实施AI将需要玩家的很多创造性问题解决。使用DSL,您只能将它们锁定在您考虑问题时定义问题的方式。拥有完整平台(如Dot Net(或其他一些选项))的智能播放器可能会为您从未预见到的一些AI问题提供全新的创新解决方案。使用正确的工具,这些玩家可以实施疯狂的学习计划或小型神经网络,或者谁知道为了实现他们的AI。但是如果你将它们锁定在一个简化的DSL中,那么不同玩家的AI实现可能没有多少变化(因为它们可用的思想表达式集合要小得多)。

对于问题的其他部分,例如定义轨道,DSL可能没问题。尽管如此,我还是倾向于像Boo这样简单的Dot Net语言之一,这样你就可以为整个项目建立一个统一的技术堆栈。

答案 3 :(得分:1)

我之前在MMO中​​做过,你知道,NPC响应脚本都在使用python,而它是在C ++框架中,比如任何与NPC相关的动作都会触发框架运行python脚本(C-python接口)当然,不是shell调用,如“python /script/run.py”)。脚本是可替换的运行时,虽然需要播放器或游戏管理员发出命令来进行刷新,但无论如何游戏服务器程序都不需要重新启动。

实际上我忘记了是否需要“通过发出命令进行刷新”是否需要新的脚本运行时... 2年前......但我觉得它适合你。

答案 4 :(得分:1)

考虑Erlang:

  • 您需要沙盒/ DSL:您可以编写“解析器生成器”来擦除对关键/易受攻击的系统调用的访问。使用此功能可以“轻松”增强库存编译器。

  • 您需要细粒度的调度:如果您在单独的模拟器中运行每个“用户”,则还需要some control。也许你可以做得更好,但我必须挖掘更多。记住安排是O(1): - )

  • 您需要在“玩家”之间进行资源分区(我想如果我理解正确的话):Erlang没有共享状态,所以这有助于从开始。您可以轻松制作一些监视玩家资源消耗的主管等。另请参阅上面的链接(控制模拟器的许多旋钮)。

  • 您需要代码热交换:Erlang是从开始设计的

  • 你需要扩展:Erlang很好地扩展了SMP,因为它基于消息传递和无缝的机器间通信,你可以水平扩展

  • 您可以使用C驱动程序优化关键路径

  • 集成“管理员”功能,可以优雅地重新启动“用户”

Ulf Wiger on Concurrency