<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          保姆級教程: c++游戲服務(wù)器嵌入v8 js引擎

          共 7001字,需瀏覽 15分鐘

           ·

          2021-08-19 08:24


          導(dǎo)語 | 本文將介紹在c++游戲服務(wù)器上嵌入v8 js引擎的詳細(xì)教程,關(guān)鍵步驟都會附帶完整的可運(yùn)行代碼。并在文末為您附上github倉庫鏈接。


          逐漸有些原生語言項(xiàng)目因?yàn)橄M胁煌C(jī)更新的能力而引入腳本。而且由于大多數(shù)項(xiàng)目已經(jīng)有現(xiàn)成的c++服務(wù)器框架,他們往往選擇把腳本作為庫嵌入到c++程序的做法。


          服務(wù)器選用一個庫,最看重的莫過于穩(wěn)定性和性能了,在眾多腳本引擎中,v8這兩方面可謂佼佼者:穩(wěn)定性源自長時間各種方式的折騰,v8引擎每天那么多的實(shí)例跑在各種各樣的機(jī)器、環(huán)境下,跑著各種各樣的代碼,一天跑的代碼量比很多小眾的腳本引擎一輩子的代碼量還多,而且nodejs的應(yīng)用也驗(yàn)證了v8跑在服務(wù)器環(huán)境是沒問題的。


          性能這塊,在jit的加持下,雖說比不上原生語言,但在腳本中肯定是第一檔的存在。


          對于c++程序猿,v8還有個很誘人的地方:支持wasm,c++編譯成wasm在v8上跑,性能比js還能高一個臺階,而且還能熱更新。


          v8引擎看上去很合適服務(wù)器使用,目前卻很少項(xiàng)目應(yīng)用到游戲服務(wù)器上,一些項(xiàng)目交流說有過這樣的想法,但不知道怎么做v8嵌入。于是有了本文,本文會循序漸進(jìn)的介紹怎么在linux c++程序里頭嵌入v8:


          • HelloWorld級別的示例;

          • c++類封裝到j(luò)s;

          • 把v8改為嵌入式nodejs;


          上述三步都會附帶完整的可運(yùn)行代碼,最后會附上github倉庫鏈接。



          一、HelloWorld


          直接上王道:


          //...各種include

          // -------------------------begin 1-----------------------------
          static void Print(const v8::FunctionCallbackInfo<v8::Value>& info) {
          v8::Isolate* isolate = info.GetIsolate();
          v8::Local<v8::Context> context = isolate->GetCurrentContext();

          std::string msg = *(v8::String::Utf8Value(isolate, info[0]->ToString(context).ToLocalChecked()));
          std::cout << msg << std::endl;
          }
          // -------------------------end 1-----------------------------
          int main(int argc, char* argv[]) {
          // -------------------------begin 2-----------------------------
          // Initialize V8.
          v8::StartupData SnapshotBlob;
          SnapshotBlob.data = (const char *)SnapshotBlobCode;
          SnapshotBlob.raw_size = sizeof(SnapshotBlobCode);
          v8::V8::SetSnapshotDataBlob(&SnapshotBlob);

          std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
          v8::V8::InitializePlatform(platform.get());
          v8::V8::Initialize();

          // Create a new Isolate and make it the current one.
          v8::Isolate::CreateParams create_params;
          create_params.array_buffer_allocator =
          v8::ArrayBuffer::Allocator::NewDefaultAllocator();
          v8::Isolate* isolate = v8::Isolate::New(create_params);
          // -------------------------end 2-----------------------------
          {
          // -------------------------begin 3-----------------------------
          v8::Isolate::Scope isolate_scope(isolate);

          // Create a stack-allocated handle scope.
          v8::HandleScope handle_scope(isolate);

          // Create a new context.
          v8::Local<v8::Context> context = v8::Context::New(isolate);

          // Enter the context for compiling and running the hello world script.
          v8::Context::Scope context_scope(context);

          // -------------------------end 3-----------------------------
          // -------------------------begin 4-----------------------------
          context->Global()->Set(context, v8::String::NewFromUtf8(isolate, "Print").ToLocalChecked(),
          v8::FunctionTemplate::New(isolate, Print)->GetFunction(context).ToLocalChecked())
          .Check();

          // -------------------------end 4-----------------------------
          {
          // -------------------------begin 5-----------------------------
          const char* csource = R"(
          Print('hello world');
          )";

          // Create a string containing the JavaScript source code.
          v8::Local<v8::String> source =
          v8::String::NewFromUtf8(isolate, csource)
          .ToLocalChecked();

          // Compile the source code.
          v8::Local<v8::Script> script =
          v8::Script::Compile(context, source).ToLocalChecked();

          // Run the script
          auto _unused = script->Run(context);
          }
          // -------------------------end 5-----------------------------
          }
          // -------------------------begin 6-----------------------------
          // Dispose the isolate and tear down V8.
          isolate->Dispose();
          v8::V8::Dispose();
          v8::V8::ShutdownPlatform();
          delete create_params.array_buffer_allocator;
          return 0;
          // -------------------------end 6-----------------------------
          }

           

          以上一大堆代碼最終運(yùn)行效果只是打印了個“hello world”,沒接觸過的童靴是不是有點(diǎn)暈菜,別急,有我。


          上述代碼我用分割線分成了6塊,其中:


          • 第2塊是v8的啟動,第6塊是v8的關(guān)閉,除非你要定制啟動參數(shù),啟動多虛擬機(jī)啥的,否則這兩部分都是固定的;


          • 第1塊有個Print函數(shù),和這函數(shù)同聲明的c++函數(shù),都可以注冊到j(luò)s環(huán)境里頭被js調(diào)用,函數(shù)只是簡單的把參數(shù)取出通過std::cout輸出;


          • 第4塊把前面的Print函數(shù)注冊到j(luò)s的全局變量,名字也叫Print;


          • 第5塊執(zhí)行了一段js代碼,調(diào)用了Print函數(shù)。


          上述例子演示了怎么去啟動一個腳本,以及怎么從腳本調(diào)用原生。在Print只是簡單的取一個參數(shù)進(jìn)行打印,如果有更多個數(shù)及種類的參數(shù)呢?更復(fù)雜的是一個c++類有構(gòu)造函數(shù),成員變量,有成員函數(shù),靜態(tài)函數(shù),還有繼承,重載等等,c++類如果需要封裝不是十分麻煩?


          這就輪到puerts出場了,為服務(wù)器童鞋科普下:puerts最初是Unreal Engine、Unity游戲引擎下的typescript編程解決方案,但游戲引擎以外的環(huán)境也逐步在支持,其中任意C#環(huán)境早已支持,而c++ 11以上環(huán)境,最近也加入支持之列。通過puerts,我們僅僅只需對c++進(jìn)行些聲明操作,即可被js使用,甚至可以根據(jù)c++ api生成.d.ts文件。


          二、
          Powered by Puerts


          用個比較簡單又有一定代表性的c++類為例:


          class TestClass
          {
          public:
          TestClass(int p) {
          std::cout << "TestClass(" << p << ")" << std::endl;
          X = p;
          }

          static void Print(std::string msg) {
          std::cout << msg << std::endl;
          }

          int Add(int a, int b)
          {
          std::cout << "Add(" << a << "," << b << ")" << std::endl;
          return a + b;
          }

          int X;
          };


          對上述類,只需要在c++里頭做如下聲明:


          UsingCppType(TestClass);

          int main(int argc, char* argv[]) {
          //other...

          //注冊
          puerts::DefineClass<TestClass>()
          .Constructor<int>()
          .Function("Print", MakeFunction(&TestClass::Print))
          .Property("X", MakeProperty(&TestClass::X))
          .Method("Add", MakeFunction(&TestClass::Add))
          .Register();
          //other...
          }


          然后就能在js里頭使用(ps,puerts還支持對上述類生成typescript類型定義):


          const TestClass = loadCppType('TestClass');
          TestClass.Print('hello world');
          let obj = new TestClass(123);

          TestClass.Print(obj.X);
          obj.X = 99;
          TestClass.Print(obj.X);

          TestClass.Print('ret = ' + obj.Add(1, 3));


          當(dāng)然,要支持這些,還需要對puerts做一定的初始化操作,在這就不再贅述,各位可于文后鏈接獲取代碼,對比第一版Helloworld即可得知用法。


          至此,我們能在c++程序里執(zhí)行js代碼, js能調(diào)用到c++代碼。這對一些項(xiàng)目已經(jīng)足夠了。


          不過我們嵌入的v8引擎,只實(shí)現(xiàn)了es規(guī)范語法以及api,像setTimeout這種耳熟能詳?shù)腶pi,都不是es規(guī)范的內(nèi)容,其次有的項(xiàng)目組希望能對接npm上豐富的組件,那有沒可能往c++程序嵌入一個nodejs呢?請看下一章節(jié)。


          三、Powered by embedding Nodejs


          第一步我們要編譯libnode.so,下載或者clone node源碼,進(jìn)入源碼目錄,執(zhí)行如下命令:


          ./configure --shared
          make -j4


          漫長的編譯完成后,會在out/Release/下找到libnode.so.95文件,這就是動態(tài)庫版本的node,接下來編譯官方的嵌入式例子:


          cd test/embedding
          c++ -I../../src -I../../deps/v8/include -I../../deps/uv/include embedtest.cc -c embedtest.cc
          c++ embedtest.o -Wl,-rpath,../../out/Release ../../out/Release/libnode.so.95


          跑一下:


          ./a.out "console.log('hello world')"


          跟著,我們把上一章節(jié)的TestClass,Puerts加入到這程序,然后在js里試試看?


          const TestClass = loadCppType('TestClass');
          TestClass.Print('hello world');
          let obj = new TestClass(123);

          TestClass.Print(obj.X);
          obj.X = 99;
          TestClass.Print(obj.X);

          TestClass.Print('ret = ' + obj.Add(1, 3));

          const fs = require('fs');
          let info = fs.readdirSync('.');
          console.log(info);


          除了之前的c++類調(diào)用之外,還加了nodejs api的調(diào)用,以證明這確實(shí)是個完整的nodejs環(huán)境。


          nodejs的嵌入可能要了解的情況更多,它內(nèi)部有一套事件循環(huán)處理邏輯,也會啟動些線程,要注意這些是否和原來的服務(wù)器框架有沖突。相比之下,上一章節(jié)的純v8環(huán)境只是一個庫,它跑不跑取決于你是否調(diào)用,會簡單得多。


          附上完整的實(shí)例代碼以及編譯配置,按readme操作就可以運(yùn)行:

          https://github.com/chexiongsheng/v8_embedding_test。



           作者簡介


          車雄生(johnche)

          騰訊游戲開發(fā)工程師

          騰訊游戲開發(fā)工程師,從事游戲開發(fā)工作多年,目前于騰訊游戲中臺部門負(fù)責(zé)公共組件開發(fā),是三個騰訊開源組件:xLua、InjectFix、Puerts的作者。


          推薦閱讀


          程序員如何把你關(guān)注的內(nèi)容推送到你眼前?揭秘信息流推薦背后的系統(tǒng)設(shè)計(jì)

          在Exception的影響下,如何才能寫出更高質(zhì)量的C++代碼?

          自動的內(nèi)存管理系統(tǒng)實(shí)操手冊——Golang垃圾回收篇

          自動的內(nèi)存管理系統(tǒng)實(shí)操手冊——Java和Golang對比篇

          自動的內(nèi)存管理系統(tǒng)實(shí)操手冊——Java垃圾回收篇



          瀏覽 107
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  直插美女逼 | 欧美伊人网 | 色逼资源站 | 小處女末发育嫩苞电影 | 丁香五月色情 |