Browse Source

Adding development web server

Josh Engebretson 10 years ago
parent
commit
ab80da29cd
4 changed files with 165 additions and 10 deletions
  1. 7 0
      CLI/cli.js
  2. 145 0
      CLI/index.js
  3. 10 9
      Source/ToolCore/Build/BuildWeb.cpp
  4. 3 1
      Source/ToolCore/Command/BuildCmd.cpp

+ 7 - 0
CLI/cli.js

@@ -82,6 +82,13 @@ cmd.setDefaults({action: function (args) {
     });
 }});
 
+var cmd = commands.addParser("serve", {help: "Start a development server.",
+    description: "Starts a development web server for testing.",
+    aliases: ["server"]});
+cmd.setDefaults({action: function (args) {
+            var server = new cli.Server();
+            server.start();
+}});
 
 // GO!
 if (process.argv.length > 2) {

+ 145 - 0
CLI/index.js

@@ -12,6 +12,9 @@ exports.DATA_DIR = DATA_DIR;
 
 var ATOMIC_TOOL_BIN = __dirname + "/data/bin/Mac/AtomicTool";
 
+var HTTP_PORT = 4000;
+var SOCKET_PORT = HTTP_PORT+1;
+
 exports.VERSION = JSON.parse(fs.readFileSync(__dirname + "/package.json")).version;
 
 var exec = function (command, flags, opts) {
@@ -63,3 +66,145 @@ exports.build = function (platform) {
 exports.addPlatform = function (platform) {
   return atomictool(["platform-add", platform], {output:true});
 };
+
+// Web Server (from flambe: https://raw.githubusercontent.com/aduros/flambe/master/command/index.js)
+var Server = function () {
+};
+exports.Server = Server;
+
+Server.prototype.start = function () {
+    var self = this;
+    var connect = require("connect");
+    var url = require("url");
+    var websocket = require("websocket");
+
+    // Fire up a Haxe compiler server, ignoring all output. It's fine if this command fails, the
+    // build will fallback to not using a compiler server
+    // spawn("haxe", ["--wait", HAXE_COMPILER_PORT], {stdio: "ignore"});
+
+    // Start a static HTTP server
+    var host = "0.0.0.0";
+    var staticServer = connect()
+        .use(function (req, res, next) {
+            var parsed = url.parse(req.url, true);
+            if (parsed.pathname == "/_api") {
+                // Handle API requests
+                req.setEncoding("utf8");
+                req.on("data", function (chunk) {
+                    self._onAPIMessage(chunk)
+                    .then(function (result) {
+                        res.end(JSON.stringify({result: result}));
+                    })
+                    .catch(function (error) {
+                        res.end(JSON.stringify({error: error}));
+                    });
+                });
+
+            } else {
+                if (parsed.query.v) {
+                    // Forever-cache assets
+                    var expires = new Date(Date.now() + 1000*60*60*24*365*25);
+                    res.setHeader("Expires", expires.toUTCString());
+                    res.setHeader("Cache-Control", "max-age=315360000");
+                }
+                next();
+            }
+        })
+        .use(connect.logger("tiny"))
+        .use(connect.compress())
+        .use(connect.static("Build/Web-Build"))
+        .listen(HTTP_PORT, host);
+    console.log("Serving on http://localhost:%s", HTTP_PORT);
+
+    this._wsServer = new websocket.server({
+        httpServer: staticServer,
+        autoAcceptConnections: true,
+    });
+
+    this._wsServer.on("connect", function (connection) {
+        connection.on("message", function (message) {
+            if (message.type == "utf8") {
+                self._onMessage(message.utf8Data);
+            }
+        });
+    });
+
+    var net = require("net");
+    this._connections = [];
+    this._socketServer = net.createServer(function (connection) {
+        self._connections.push(connection);
+        connection.on("end", function () {
+            self._connections.splice(self._connections.indexOf(connection, 1));
+        });
+        connection.on("data", function (data) {
+            data = data.toString();
+            self._onMessage(data);
+        });
+    });
+    this._socketServer.listen(SOCKET_PORT, host);
+
+    var watch = require("watch");
+    var crypto = require("crypto");
+    watch.createMonitor("Resources", {interval: 200}, function (monitor) {
+        monitor.on("changed", function (file) {
+            console.log("Asset changed: " + file);
+            var output = "build/web/"+file;
+            if (fs.existsSync(output)) {
+                var contents = fs.readFileSync(file);
+                fs.writeFileSync(output, contents);
+                self.broadcast("file_changed", {
+                    name: path.relative("Resources", file),
+                    md5: crypto.createHash("md5").update(contents).digest("hex"),
+                });
+            }
+        });
+    });
+};
+
+/** Broadcast an event to all clients. */
+Server.prototype.broadcast = function (type, params) {
+    var event = {type: type};
+    if (params) {
+        for (var k in params) {
+            event[k] = params[k];
+        }
+    }
+    var message = JSON.stringify(event);
+    this._wsServer.broadcast(message);
+    this._connections.forEach(function (connection) {
+        connection.write(message);
+    });
+};
+
+/** Handle messages from connected game clients. */
+Server.prototype._onMessage = function (message) {
+    try {
+        var event = JSON.parse(message);
+        // switch (event.type) {
+        // case "restart":
+        //     this.broadcast("restart");
+        // }
+    } catch (error) {
+        console.warn("Received badly formed message", error);
+    }
+};
+
+/** Handle web API messages. */
+Server.prototype._onAPIMessage = function (message) {
+    try {
+        message = JSON.parse(message);
+    } catch (error) {
+        return Q.reject("Badly formed JSON");
+    }
+
+    switch (message.method) {
+    case "restart":
+        this.broadcast("restart");
+        return Q.resolve({
+            htmlClients: this._wsServer.connections.length,
+            flashClients: this._connections.length,
+        });
+    default:
+        return Q.reject("Unknown method: " + message.method);
+    }
+};

+ 10 - 9
Source/ToolCore/Build/BuildWeb.cpp

@@ -25,14 +25,12 @@ BuildWeb::~BuildWeb()
 
 void BuildWeb::Initialize()
 {
-    ToolSystem* tools = GetSubsystem<ToolSystem>();
-    Project* project = tools->GetProject();
-
-    FileSystem* fileSystem = GetSubsystem<FileSystem>();
-    String bundleResources = fileSystem->GetAppBundleResourceFolder();
+    ToolSystem* tsystem = GetSubsystem<ToolSystem>();
+    Project* project = tsystem->GetProject();
 
+    String dataPath = tsystem->GetDataPath();
     String projectResources = project->GetResourcePath();
-    String coreDataFolder = bundleResources + "CoreData/";
+    String coreDataFolder = dataPath + "Atomic/Resources/CoreData/";
 
     AddResourceDir(coreDataFolder);
     AddResourceDir(projectResources);
@@ -41,7 +39,9 @@ void BuildWeb::Initialize()
 }
 void BuildWeb::Build(const String& buildPath)
 {
-    buildPath_ = buildPath + "/Web-Build";
+    ToolSystem* tsystem = GetSubsystem<ToolSystem>();
+
+    buildPath_ = AddTrailingSlash(buildPath) + GetBuildSubfolder();
 
     Initialize();
 
@@ -49,8 +49,9 @@ void BuildWeb::Build(const String& buildPath)
     if (fileSystem->DirExists(buildPath_))
         fileSystem->RemoveDir(buildPath_, true);
 
-    String buildSourceDir = fileSystem->GetAppBundleResourceFolder();
-    buildSourceDir += "Deployment/Web";
+    String dataPath = tsystem->GetDataPath();
+
+    String buildSourceDir  = dataPath + "Atomic/Deployment/Web";
 
     fileSystem->CreateDir(buildPath_);
 

+ 3 - 1
Source/ToolCore/Command/BuildCmd.cpp

@@ -66,7 +66,9 @@ void BuildCmd::Run()
     Platform* platform = NULL;
 
     if (buildPlatform_ == "mac")
-        platform = tsystem->GetPlatformByID(PLATFORMID_MAC);
+        platform = tsystem->GetPlatformByID(PLATFORMID_MAC);    
+    else if (buildPlatform_ == "web")
+        platform = tsystem->GetPlatformByID(PLATFORMID_WEB);
 
     if (!platform)
     {