Cocos2d-xには実行中のゲームのリソースを書き換えるHotUpdatingという機能があり、Cocos Code IDEでも利用されています。3.1でこれらの機能をつかっていたのですが、
3.2からデータを転送するフォーマットが一部Protocol Buffersを利用したものに置き換えられ、以前のフォーマットを期待して書かれていたツール等が動かなくなったため、3.2でも動くようにします。
Protocol Buffers
Protocol Buffers(プロトコルバッファー)はインタフェース定義言語(IDL)で構造を定義する通信や永続化での利用を目的としたシリアライズフォーマットであり、Googleにより開発されている。
だそうです。HotUpdatingではファイルサイズや更新時間など、ファイルのメタデータをProtocol Buffersで転送しています。以下は定義の一部です。
class FileSendProtos : public ::google::protobuf::MessageLite {
...
// required uint64 content_size = 4;
inline bool has_content_size() const;
inline void clear_content_size();
static const int kContentSizeFieldNumber = 4;
inline ::google::protobuf::uint64 content_size() const;
inline void set_content_size(::google::protobuf::uint64 value);
...
// optional uint64 modified_time = 6;
inline bool has_modified_time() const;
inline void clear_modified_time();
static const int kModifiedTimeFieldNumber = 6;
inline ::google::protobuf::uint64 modified_time() const;
inline void set_modified_time(::google::protobuf::uint64 value);
...
}
本来言語に依存しない*.protoファイルがあるはずですが、現状ではリポジトリには含まれていないようなので、このコードをそのまま使ってエンコードの処理をC++で書きます。
送信するためのデータを作る
ファイルの情報を受け取って送信するデータを出力するコードは次のようになります。
# include<iostream>
# include "Protos.pb.h"
using namespace std;
int main(int argc, char* argv[])
{
char* fileName = argv[1];
int contentSize = atoi(argv[2]);
int modifiedTime = atoi(argv[3]);
runtime::FileSendProtos fileProto;
fileProto.set_file_name(fileName);
fileProto.set_package_seq(1);
fileProto.set_package_sum(1);
fileProto.set_content_size(contentSize);
fileProto.set_compress_type(runtime::FileSendProtos_CompressType_CompressType_MIN);
fileProto.set_modified_time(modifiedTime);
string requestString;
fileProto.SerializeToString(&requestString);
char dataBuf[1024] = {0};
struct RequestStruct
{
char startFlag[12];
unsigned short protoNum;
unsigned short protoBufLen;
};
RequestStruct requestData;
strcpy(requestData.startFlag, "RuntimeSend:");
requestData.protoNum = 1; // FileServer::PROTONUM::FILEPROTO
requestData.protoBufLen = (unsigned short)requestString.size();
memcpy(dataBuf, &requestData, sizeof(requestData));
memcpy(dataBuf + sizeof(requestData), requestString.c_str(), requestString.size());
cout.write(dataBuf, sizeof(requestData) + requestString.size());
return 0;
}
runtime::FileSendProtos
がProtocol Buffersで記述されている箇所です。必要な情報をセットしてシリアライズします。package_seq
とpackage_sum
はデータを分割して送信する場合に順序と総数を指定します。requiredなフィールドなので、1回で送る場合も明示的に1を指定します。FileSendProtos
には他にもoptionalなバリデーション用のフィールドがいくつかありますが、Runtimeの中では特に使われていないようだったので省略できます。
Runtimeは他にもメタデータ自体の長さなども期待しているので、それらも合わせて結合したデータを出力します。SerializeToString
以降がその処理です。
ローカルファイルの更新にフックして転送する
先に作ったツールを組み合わせると以下のようになります。
import os
import traceback
from socket import *
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
import subprocess
CMD_PORT = 6010
FILE_PORT = 6020
BUFFER = 1024
HOST = '0.0.0.0' # change if other device
BASE_DIR = os.path.abspath(os.path.dirname(__file__)) + '/..'
RESOURCE_DIR = BASE_DIR + '/res'
cmdSocket = None
fileSocket = None
class ResourceHandler(FileSystemEventHandler):
def on_created(self, event):
print('created: ' + event.src_path)
on_resource(event, event.src_path)
def on_modified(self, event):
print('modified: ' + event.src_path)
on_resource(event, event.src_path)
def on_moved(self, event):
print('moved: ' + event.src_path)
on_resource(event, event.dest_path)
def on_resource(event, path):
if event.is_directory or ('tmp' in path):
return
if upload(path):
cmdSocket.send('sendrequest {"cmd":"reload","modulefiles":["src/main.lua"]}\n')
def upload(src_path):
print("upload: " + src_path)
success = False
if not os.path.exists(src_path):
print("not found: " + src_path)
return
filename = os.path.relpath(src_path, BASE_DIR)
mtime = os.path.getmtime(src_path)
size = os.path.getsize(src_path)
metaData = None
try:
metaData = subprocess.check_output(["./encode", filename, str(size), str(mtime)])
except Exception, e:
traceback.print_exc()
return False
try:
fileSocket.send(metaData)
fp = open(src_path, 'rb')
data = fp.read(BUFFER)
while (data):
fileSocket.send(data)
data = fp.read(BUFFER)
fp.close()
success = True
except Exception, e:
traceback.print_exc()
return success
if __name__ == "__main__":
HOST = raw_input("type IP shown your device (like 127.0.0.1) >>> ")
obs = Observer()
obs.schedule(ResourceHandler(), RESOURCE_DIR, recursive=True)
obs.start()
cmdSocket = socket(AF_INET, SOCK_STREAM)
cmdSocket.connect((HOST, CMD_PORT))
cmdSocket.send('debugmsg on\n')
fileSocket = socket(AF_INET, SOCK_STREAM)
fileSocket.connect((HOST, FILE_PORT))
try:
while True:
print cmdSocket.recv(BUFFER)
except KeyboardInterrupt:
obs.stop()
obs.join()
cmdSocket.send('exit\n')
cmdSocket.close()
fileSocket.close()