mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 20:55:50 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cafb547894 | ||
|
|
35543c2790 | ||
|
|
88efdb5ba4 | ||
|
|
eeff7cfd92 | ||
|
|
f66cbe0049 | ||
|
|
d0c6f2a470 | ||
|
|
34d5de9f2c | ||
|
|
16676ebfa1 | ||
|
|
bf9be6ccae | ||
|
|
974ebc33c2 | ||
|
|
4b03a79cfe | ||
|
|
43cc350b7a | ||
|
|
5685619c3a | ||
|
|
f3483ee2e0 | ||
|
|
60a9135452 | ||
|
|
c3d6dc0e2c | ||
|
|
2681972976 | ||
|
|
5e64773784 | ||
|
|
c7171ef513 | ||
|
|
63b21b90ff | ||
|
|
7f3aaf4680 |
12
.gitmodules
vendored
Normal file
12
.gitmodules
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[submodule "modules/Roact"]
|
||||||
|
path = modules/Roact
|
||||||
|
url = https://github.com/Roblox/Roact.git
|
||||||
|
[submodule "modules/Rodux"]
|
||||||
|
path = modules/Rodux
|
||||||
|
url = https://github.com/Roblox/Rodux.git
|
||||||
|
[submodule "modules/RoactRodux"]
|
||||||
|
path = modules/RoactRodux
|
||||||
|
url = https://github.com/Roblox/RoactRodux.git
|
||||||
|
[submodule "modules/TestEZ"]
|
||||||
|
path = modules/TestEZ
|
||||||
|
url = https://github.com/Roblox/TestEZ.git
|
||||||
17
CHANGES.md
17
CHANGES.md
@@ -3,5 +3,22 @@
|
|||||||
## Current Master
|
## Current Master
|
||||||
* *No changes*
|
* *No changes*
|
||||||
|
|
||||||
|
## 0.2.3
|
||||||
|
* Plugin only release
|
||||||
|
* Tightened `init` file rules to only match script files
|
||||||
|
* Previously, Rojo would sometimes pick up the wrong file when syncing
|
||||||
|
|
||||||
|
## 0.2.2
|
||||||
|
* Plugin only release
|
||||||
|
* Fixed broken reconciliation behavior with `init` files
|
||||||
|
|
||||||
|
## 0.2.1
|
||||||
|
* Plugin only release
|
||||||
|
* Changes default port to 8000
|
||||||
|
|
||||||
|
## 0.2.0
|
||||||
|
* Support for `init.lua` like rbxfs and rbxpacker
|
||||||
|
* More robust syncing with a new reconciler
|
||||||
|
|
||||||
## 0.1.0
|
## 0.1.0
|
||||||
* Initial release, functionally very similar to [rbxfs](https://github.com/LPGhatguy/rbxfs)
|
* Initial release, functionally very similar to [rbxfs](https://github.com/LPGhatguy/rbxfs)
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1,6 +1,6 @@
|
|||||||
[root]
|
[root]
|
||||||
name = "rojo"
|
name = "rojo"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap 2.28.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.28.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rojo"
|
name = "rojo"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||||
description = "A tool to create robust Roblox projects"
|
description = "A tool to create robust Roblox projects"
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://github.com/LPGhatguy/rojo"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "rojo"
|
name = "rojo"
|
||||||
|
|||||||
124
README.md
124
README.md
@@ -3,53 +3,37 @@
|
|||||||
<a href="https://travis-ci.org/LPGhatguy/Rojo">
|
<a href="https://travis-ci.org/LPGhatguy/Rojo">
|
||||||
<img src="https://api.travis-ci.org/LPGhatguy/Rojo.svg?branch=master" alt="Travis-CI Build Status" />
|
<img src="https://api.travis-ci.org/LPGhatguy/Rojo.svg?branch=master" alt="Travis-CI Build Status" />
|
||||||
</a>
|
</a>
|
||||||
<a href="#">
|
|
||||||
<img src="https://img.shields.io/badge/docs-soon-red.svg" alt="Documentation" />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div> </div>
|
<div> </div>
|
||||||
|
|
||||||
**EARLY DEVELOPMENT, USE WITH CARE**
|
Rojo is a flexible multi-tool designed for creating robust Roblox projects. It's in early development, but is still useful for many projects.
|
||||||
|
|
||||||
Rojo is a flexible multi-tool designed for creating robust Roblox projects.
|
It's designed for power users who want to use the **best tools available** for building games, libraries, and plugins.
|
||||||
|
|
||||||
It's designed for power users who want to use the best tools available for building games, libraries, and plugins.
|
## Features
|
||||||
|
|
||||||
It has a number of desirable features *right now*:
|
Rojo has a number of desirable features *right now*:
|
||||||
|
|
||||||
* Work from the filesystem, in your favorite editor
|
* Work on scripts from the filesystem, in your favorite editor
|
||||||
* Version your place, library, or plugin using Git or another VCS
|
* Version your place, library, or plugin using Git or another VCS
|
||||||
|
|
||||||
Soon, Rojo will be able to:
|
Soon, Rojo will be able to:
|
||||||
|
|
||||||
|
* Sync Roblox objects (including models) bi-directionally between the filesystem and Roblox Studio
|
||||||
* Create installation scripts for libraries to be used in standalone places
|
* Create installation scripts for libraries to be used in standalone places
|
||||||
* Similar to [rbxpacker](https://github.com/LPGhatguy/rbxpacker), another one of my projects
|
* Similar to [rbxpacker](https://github.com/LPGhatguy/rbxpacker), another one of my projects
|
||||||
* Add strongly-versioned dependencies to your project
|
* Add strongly-versioned dependencies to your project
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
Rojo has two components:
|
Rojo has two components:
|
||||||
* The binary, written in Rust
|
* The command line tool, written in Rust
|
||||||
* The [Roblox Studio plugin](https://www.roblox.com/library/1211549683/Rojo-v0-0-0), written in Lua
|
* The [Roblox Studio plugin](https://www.roblox.com/library/1211549683/Rojo-v0-0-0), written in Lua
|
||||||
|
|
||||||
To install the binary, there are two options:
|
To install the command line tool, there are two options:
|
||||||
* Cargo, which requires you to have Rust installed
|
* Cargo, if you have Rust installed
|
||||||
* Pre-built binaries from the [the GitHub releases page](https://github.com/LPGhatguy/rojo/releases)
|
* Use `cargo install rojo` -- Rojo will be available with the `rojo` command
|
||||||
|
* Download a pre-built Windows binary from [the GitHub releases page](https://github.com/LPGhatguy/rojo/releases)
|
||||||
### Cargo (Recommended)
|
|
||||||
Make sure you have [Rust 1.21 or newer](https://www.rust-lang.org/) installed.
|
|
||||||
|
|
||||||
Install Rojo using:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo install rojo
|
|
||||||
|
|
||||||
# Installed!
|
|
||||||
rojo help
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pre-Built (Windows only)
|
|
||||||
Download the latest binary from [the GitHub releases page](https://github.com/LPGhatguy/rojo/releases). Put it somewhere you can access it from a terminal!
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
For more help, use `rojo help`.
|
For more help, use `rojo help`.
|
||||||
@@ -66,8 +50,92 @@ rojo init
|
|||||||
|
|
||||||
Rojo will create an empty project in the directory.
|
Rojo will create an empty project in the directory.
|
||||||
|
|
||||||
|
The default project looks like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "my-new-project",
|
||||||
|
"servePort": 8000,
|
||||||
|
"partitions": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start Dev Server
|
||||||
|
To create a server that allows the Rojo Dev Plugin to access your project, use:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rojo serve
|
||||||
|
```
|
||||||
|
|
||||||
|
The tool will tell you whether it found an existing project. You should then be able to connect and use the project from within Roblox Studio!
|
||||||
|
|
||||||
### Migrating an Existing Roblox Project
|
### Migrating an Existing Roblox Project
|
||||||
Coming soon!
|
**Coming soon!**
|
||||||
|
|
||||||
|
### Syncing into Roblox
|
||||||
|
In order to sync code into Roblox, you'll need to add one or more "partitions" to your configuration. A partition tells Rojo how to map directories to Roblox objects.
|
||||||
|
|
||||||
|
Each entry in the partitions table has a unique name, a filesystem path, and the full name of the Roblox object to sync into.
|
||||||
|
|
||||||
|
For example, if you want to map your `src` directory to an object named `My Cool Game` in `ReplicatedStorage`, you could use this configuration:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "rojo",
|
||||||
|
"servePort": 8000,
|
||||||
|
"partitions": {
|
||||||
|
"game": {
|
||||||
|
"path": "src",
|
||||||
|
"target": "ReplicatedStorage.My Cool Game"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `path` parameter is relative to the project file.
|
||||||
|
|
||||||
|
The `target` starts at `game` and crawls down the tree. If any objects don't exist along the way, they'll be created as `Folder` instances.
|
||||||
|
|
||||||
|
Run `rojo serve` in the directory containing this project, then press the "Sync In" or "Toggle Polling" buttons in the Roblox Studio plugin to move code into your game.
|
||||||
|
|
||||||
|
### Sync Details
|
||||||
|
The structure of files and folders on the filesystem are preserved when syncing into game.
|
||||||
|
|
||||||
|
Creation of Roblox instances follows a simple set of rules. The first rule that matches the file name is chosen:
|
||||||
|
|
||||||
|
| File Name | Instance Type | Notes |
|
||||||
|
| -------------- | -------------- | ----------------------------------------- |
|
||||||
|
| `*.server.lua` | `Script` | `Source` will contain the file's contents |
|
||||||
|
| `*.client.lua` | `LocalScript` | `Source` will contain the file's contents |
|
||||||
|
| `*.lua` | `ModuleScript` | `Source` will contain the file's contents |
|
||||||
|
| `*` | `StringValue` | `Value` will contain the file's contents |
|
||||||
|
|
||||||
|
Any folders on the filesystem will turn into `Folder` objects unless they contain a file named `init.lua`, `init.server.lua`, or `init.client.lua`. Following the convention of Lua, those objects will instead be whatever the `init` file would turn into.
|
||||||
|
|
||||||
|
For example, this file tree:
|
||||||
|
|
||||||
|
* my-game
|
||||||
|
* init.client.lua
|
||||||
|
* foo.lua
|
||||||
|
|
||||||
|
Will turn into this tree in Roblox:
|
||||||
|
|
||||||
|
* `my-game` (`LocalScript` with source from `my-game/init.client.lua`)
|
||||||
|
* `foo` (`ModuleScript` with source from `my-game/foo.lua`)
|
||||||
|
|
||||||
|
## Inspiration
|
||||||
|
There are lots of other tools that sync scripts into Roblox, or otherwise work to improve the development flow outside of Roblox Studio.
|
||||||
|
|
||||||
|
Here are a few, if you're looking for alternatives or supplements to Rojo:
|
||||||
|
* [Studio Bridge by Vocksel](https://github.com/vocksel/studio-bridge)
|
||||||
|
* [RbxRefresh by Osyris](https://github.com/osyrisrblx/RbxRefresh)
|
||||||
|
* [RbxSync by evaera](https://github.com/evaera/RbxSync)
|
||||||
|
* [CodeSync](https://github.com/MemoryPenguin/CodeSync) and [rbx-exteditor](https://github.com/MemoryPenguin/rbx-exteditor) by [MemoryPenguin](https://github.com/MemoryPenguin)
|
||||||
|
* [rbxmk by Anaminus](https://github.com/anaminus/rbxmk)
|
||||||
|
|
||||||
|
I also have a couple tools that Rojo intends to replace:
|
||||||
|
* [rbxfs](https://github.com/LPGhatguy/rbxfs), which has been deprecated by Rojo
|
||||||
|
* [rbxpacker](https://github.com/LPGhatguy/rbxpacker), which is still useful
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Rojo is available under the terms of the MIT license. See [LICENSE.md](LICENSE.md) for details.
|
Rojo is available under the terms of the MIT license. See [LICENSE.md](LICENSE.md) for details.
|
||||||
1
modules/Roact
Submodule
1
modules/Roact
Submodule
Submodule modules/Roact added at 7cce62b130
1
modules/RoactRodux
Submodule
1
modules/RoactRodux
Submodule
Submodule modules/RoactRodux added at 43c4f347fe
1
modules/Rodux
Submodule
1
modules/Rodux
Submodule
Submodule modules/Rodux added at 6c573259ab
1
modules/TestEZ
Submodule
1
modules/TestEZ
Submodule
Submodule modules/TestEZ added at 9945f562e5
@@ -51,6 +51,6 @@ files["**/*.server.lua"] = {
|
|||||||
std = "+plugin",
|
std = "+plugin",
|
||||||
}
|
}
|
||||||
|
|
||||||
files["**/*-spec.lua"] = {
|
files["**/*.spec.lua"] = {
|
||||||
std = "+testez",
|
std = "+testez",
|
||||||
}
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"rootDirectory": "src",
|
|
||||||
"rootObject": "ReplicatedStorage.Rojo"
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
return {
|
return {
|
||||||
pollingRate = 0.3,
|
pollingRate = 0.3,
|
||||||
|
version = "v0.2.3",
|
||||||
|
dev = false,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
local HttpService = game:GetService("HttpService")
|
local HttpService = game:GetService("HttpService")
|
||||||
|
|
||||||
|
local HTTP_DEBUG = false
|
||||||
|
|
||||||
local Promise = require(script.Parent.Promise)
|
local Promise = require(script.Parent.Promise)
|
||||||
local HttpError = require(script.Parent.HttpError)
|
local HttpError = require(script.Parent.HttpError)
|
||||||
local HttpResponse = require(script.Parent.HttpResponse)
|
local HttpResponse = require(script.Parent.HttpResponse)
|
||||||
|
|
||||||
|
local function dprint(...)
|
||||||
|
if HTTP_DEBUG then
|
||||||
|
print(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local Http = {}
|
local Http = {}
|
||||||
Http.__index = Http
|
Http.__index = Http
|
||||||
|
|
||||||
@@ -20,6 +28,7 @@ function Http.new(baseUrl)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Http:get(endpoint)
|
function Http:get(endpoint)
|
||||||
|
dprint("\nGET", endpoint)
|
||||||
return Promise.new(function(resolve, reject)
|
return Promise.new(function(resolve, reject)
|
||||||
spawn(function()
|
spawn(function()
|
||||||
local ok, result = pcall(function()
|
local ok, result = pcall(function()
|
||||||
@@ -27,6 +36,7 @@ function Http:get(endpoint)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
if ok then
|
if ok then
|
||||||
|
dprint("\t", result, "\n")
|
||||||
resolve(HttpResponse.new(result))
|
resolve(HttpResponse.new(result))
|
||||||
else
|
else
|
||||||
reject(HttpError.fromErrorString(result))
|
reject(HttpError.fromErrorString(result))
|
||||||
@@ -36,6 +46,8 @@ function Http:get(endpoint)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Http:post(endpoint, body)
|
function Http:post(endpoint, body)
|
||||||
|
dprint("\nPOST", endpoint)
|
||||||
|
dprint(body)
|
||||||
return Promise.new(function(resolve, reject)
|
return Promise.new(function(resolve, reject)
|
||||||
spawn(function()
|
spawn(function()
|
||||||
local ok, result = pcall(function()
|
local ok, result = pcall(function()
|
||||||
@@ -43,6 +55,7 @@ function Http:post(endpoint, body)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
if ok then
|
if ok then
|
||||||
|
dprint("\t", result, "\n")
|
||||||
resolve(HttpResponse.new(result))
|
resolve(HttpResponse.new(result))
|
||||||
else
|
else
|
||||||
reject(HttpError.fromErrorString(result))
|
reject(HttpError.fromErrorString(result))
|
||||||
|
|||||||
@@ -3,26 +3,38 @@ if not plugin then
|
|||||||
end
|
end
|
||||||
|
|
||||||
local Plugin = require(script.Parent.Plugin)
|
local Plugin = require(script.Parent.Plugin)
|
||||||
|
local Config = require(script.Parent.Config)
|
||||||
|
|
||||||
local function main()
|
local function main()
|
||||||
local pluginInstance = Plugin.new()
|
local pluginInstance = Plugin.new()
|
||||||
|
|
||||||
local toolbar = plugin:CreateToolbar("Rojo Plugin 0.1.0")
|
local displayedVersion = Config.dev and "DEV" or Config.version
|
||||||
|
|
||||||
|
local toolbar = plugin:CreateToolbar("Rojo Plugin " .. displayedVersion)
|
||||||
|
|
||||||
toolbar:CreateButton("Test Connection", "Connect to Rojo Server", "")
|
toolbar:CreateButton("Test Connection", "Connect to Rojo Server", "")
|
||||||
.Click:Connect(function()
|
.Click:Connect(function()
|
||||||
pluginInstance:connect()
|
pluginInstance:connect()
|
||||||
|
:catch(function(err)
|
||||||
|
warn(err)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
toolbar:CreateButton("Sync In", "Sync into Roblox Studio", "")
|
toolbar:CreateButton("Sync In", "Sync into Roblox Studio", "")
|
||||||
.Click:Connect(function()
|
.Click:Connect(function()
|
||||||
pluginInstance:syncIn()
|
pluginInstance:syncIn()
|
||||||
|
:catch(function(err)
|
||||||
|
warn(err)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
toolbar:CreateButton("Toggle Polling", "Poll server for changes", "")
|
toolbar:CreateButton("Toggle Polling", "Poll server for changes", "")
|
||||||
.Click:Connect(function()
|
.Click:Connect(function()
|
||||||
spawn(function()
|
spawn(function()
|
||||||
pluginInstance:togglePolling()
|
pluginInstance:togglePolling()
|
||||||
|
:catch(function(err)
|
||||||
|
warn(err)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ local Config = require(script.Parent.Config)
|
|||||||
local Http = require(script.Parent.Http)
|
local Http = require(script.Parent.Http)
|
||||||
local Server = require(script.Parent.Server)
|
local Server = require(script.Parent.Server)
|
||||||
local Promise = require(script.Parent.Promise)
|
local Promise = require(script.Parent.Promise)
|
||||||
|
local Reconciler = require(script.Parent.Reconciler)
|
||||||
|
|
||||||
local function collectMatch(source, pattern)
|
local function collectMatch(source, pattern)
|
||||||
local result = {}
|
local result = {}
|
||||||
@@ -13,91 +14,12 @@ local function collectMatch(source, pattern)
|
|||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
local function fileToName(filename)
|
|
||||||
if filename:find("%.server%.lua$") then
|
|
||||||
return filename:match("^(.-)%.server%.lua$"), "Script"
|
|
||||||
elseif filename:find("%.client%.lua$") then
|
|
||||||
return filename:match("^(.-)%.client%.lua$"), "LocalScript"
|
|
||||||
elseif filename:find("%.lua") then
|
|
||||||
return filename:match("^(.-)%.lua$"), "ModuleScript"
|
|
||||||
else
|
|
||||||
return filename, "StringValue"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function nameToInstance(filename, contents)
|
|
||||||
local name, className = fileToName(filename)
|
|
||||||
|
|
||||||
local instance = Instance.new(className)
|
|
||||||
instance.Name = name
|
|
||||||
|
|
||||||
if className:find("Script$") then
|
|
||||||
instance.Source = contents
|
|
||||||
else
|
|
||||||
instance.Value = contents
|
|
||||||
end
|
|
||||||
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
local function make(item, name)
|
|
||||||
if item.type == "dir" then
|
|
||||||
local instance = Instance.new("Folder")
|
|
||||||
instance.Name = name
|
|
||||||
|
|
||||||
for childName, child in pairs(item.children) do
|
|
||||||
make(child, childName).Parent = instance
|
|
||||||
end
|
|
||||||
|
|
||||||
return instance
|
|
||||||
elseif item.type == "file" then
|
|
||||||
return nameToInstance(name, item.contents)
|
|
||||||
else
|
|
||||||
error("not implemented")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function write(parent, route, item)
|
|
||||||
local location = parent
|
|
||||||
|
|
||||||
for index = 1, #route - 1 do
|
|
||||||
local piece = route[index]
|
|
||||||
local newLocation = location:FindFirstChild(piece)
|
|
||||||
|
|
||||||
if not newLocation then
|
|
||||||
newLocation = Instance.new("Folder")
|
|
||||||
newLocation.Name = piece
|
|
||||||
newLocation.Parent = location
|
|
||||||
end
|
|
||||||
|
|
||||||
location = newLocation
|
|
||||||
end
|
|
||||||
|
|
||||||
local fileName = route[#route]
|
|
||||||
local name = fileToName(fileName)
|
|
||||||
|
|
||||||
local existing = location:FindFirstChild(name)
|
|
||||||
|
|
||||||
local new
|
|
||||||
if item then
|
|
||||||
new = make(item, fileName)
|
|
||||||
end
|
|
||||||
|
|
||||||
if existing then
|
|
||||||
existing:Destroy()
|
|
||||||
end
|
|
||||||
|
|
||||||
if new then
|
|
||||||
new.Parent = location
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local Plugin = {}
|
local Plugin = {}
|
||||||
Plugin.__index = Plugin
|
Plugin.__index = Plugin
|
||||||
|
|
||||||
function Plugin.new()
|
function Plugin.new()
|
||||||
local address = "localhost"
|
local address = "localhost"
|
||||||
local port = 8081
|
local port = Config.dev and 8001 or 8000
|
||||||
|
|
||||||
local remote = ("http://%s:%d"):format(address, port)
|
local remote = ("http://%s:%d"):format(address, port)
|
||||||
|
|
||||||
@@ -149,7 +71,7 @@ end
|
|||||||
function Plugin:connect()
|
function Plugin:connect()
|
||||||
print("Testing connection...")
|
print("Testing connection...")
|
||||||
|
|
||||||
self:server()
|
return self:server()
|
||||||
:andThen(function(server)
|
:andThen(function(server)
|
||||||
return server:getInfo()
|
return server:getInfo()
|
||||||
end)
|
end)
|
||||||
@@ -163,8 +85,10 @@ end
|
|||||||
function Plugin:togglePolling()
|
function Plugin:togglePolling()
|
||||||
if self._polling then
|
if self._polling then
|
||||||
self:stopPolling()
|
self:stopPolling()
|
||||||
|
|
||||||
|
return Promise.resolve(nil)
|
||||||
else
|
else
|
||||||
self:startPolling()
|
return self:startPolling()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -173,12 +97,30 @@ function Plugin:stopPolling()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
print("Stopping polling...")
|
print("Stopped polling.")
|
||||||
|
|
||||||
self._polling = false
|
self._polling = false
|
||||||
self._label.Enabled = false
|
self._label.Enabled = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Plugin:_pull(server, project, routes)
|
||||||
|
local items = server:read(routes):await()
|
||||||
|
|
||||||
|
for index = 1, #routes do
|
||||||
|
local route = routes[index]
|
||||||
|
local partitionName = route[1]
|
||||||
|
local partition = project.partitions[partitionName]
|
||||||
|
local item = items[index]
|
||||||
|
|
||||||
|
local fullRoute = collectMatch(partition.target, "[^.]+")
|
||||||
|
for i = 2, #route do
|
||||||
|
table.insert(fullRoute, routes[index][i])
|
||||||
|
end
|
||||||
|
|
||||||
|
Reconciler.reconcileRoute(fullRoute, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function Plugin:startPolling()
|
function Plugin:startPolling()
|
||||||
if self._polling then
|
if self._polling then
|
||||||
return
|
return
|
||||||
@@ -204,17 +146,7 @@ function Plugin:startPolling()
|
|||||||
table.insert(routes, change.route)
|
table.insert(routes, change.route)
|
||||||
end
|
end
|
||||||
|
|
||||||
local items = server:read(routes):await()
|
self:_pull(server, project, routes)
|
||||||
|
|
||||||
for index = 1, #routes do
|
|
||||||
local partitionName = routes[index][1]
|
|
||||||
local partition = project.partitions[partitionName]
|
|
||||||
local data = items[index]
|
|
||||||
|
|
||||||
local fullRoute = collectMatch(partition.target, "[^.]+")
|
|
||||||
|
|
||||||
write(game, fullRoute, data)
|
|
||||||
end
|
|
||||||
|
|
||||||
wait(Config.pollingRate)
|
wait(Config.pollingRate)
|
||||||
end
|
end
|
||||||
@@ -231,23 +163,13 @@ function Plugin:syncIn()
|
|||||||
:andThen(function(server)
|
:andThen(function(server)
|
||||||
local project = server:getInfo():await().project
|
local project = server:getInfo():await().project
|
||||||
|
|
||||||
local readRoutes = {}
|
local routes = {}
|
||||||
|
|
||||||
for name in pairs(project.partitions) do
|
for name in pairs(project.partitions) do
|
||||||
table.insert(readRoutes, {name})
|
table.insert(routes, {name})
|
||||||
end
|
end
|
||||||
|
|
||||||
local items = server:read(readRoutes):await()
|
self:_pull(server, project, routes)
|
||||||
|
|
||||||
for index = 1, #readRoutes do
|
|
||||||
local partitionName = readRoutes[index][1]
|
|
||||||
local partition = project.partitions[partitionName]
|
|
||||||
local data = items[index]
|
|
||||||
|
|
||||||
local fullRoute = collectMatch(partition.target, "[^.]+")
|
|
||||||
|
|
||||||
write(game, fullRoute, data)
|
|
||||||
end
|
|
||||||
|
|
||||||
print("Sync successful!")
|
print("Sync successful!")
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
An implementation of Promises similar to Promise/A+.
|
An implementation of Promises similar to Promise/A+.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local PROMISE_DEBUG = true
|
local PROMISE_DEBUG = false
|
||||||
|
|
||||||
-- If promise debugging is on, use a version of pcall that warns on failure.
|
-- If promise debugging is on, use a version of pcall that warns on failure.
|
||||||
-- This is useful for finding errors that happen within Promise itself.
|
-- This is useful for finding errors that happen within Promise itself.
|
||||||
@@ -89,6 +89,9 @@ function Promise.new(callback)
|
|||||||
-- Only valid if _status is set to something besides Started
|
-- Only valid if _status is set to something besides Started
|
||||||
_value = nil,
|
_value = nil,
|
||||||
|
|
||||||
|
-- If an error occurs with no observers, this will be set.
|
||||||
|
_unhandledRejection = false,
|
||||||
|
|
||||||
-- Queues representing functions we should invoke when we update!
|
-- Queues representing functions we should invoke when we update!
|
||||||
_queuedResolve = {},
|
_queuedResolve = {},
|
||||||
_queuedReject = {},
|
_queuedReject = {},
|
||||||
@@ -157,6 +160,8 @@ end
|
|||||||
The given callbacks are invoked depending on that result.
|
The given callbacks are invoked depending on that result.
|
||||||
]]
|
]]
|
||||||
function Promise:andThen(successHandler, failureHandler)
|
function Promise:andThen(successHandler, failureHandler)
|
||||||
|
self._unhandledRejection = false
|
||||||
|
|
||||||
-- Create a new promise to follow this part of the chain
|
-- Create a new promise to follow this part of the chain
|
||||||
return Promise.new(function(resolve, reject)
|
return Promise.new(function(resolve, reject)
|
||||||
-- Our default callbacks just pass values onto the next promise.
|
-- Our default callbacks just pass values onto the next promise.
|
||||||
@@ -199,6 +204,8 @@ end
|
|||||||
This matches the execution model of normal Roblox functions.
|
This matches the execution model of normal Roblox functions.
|
||||||
]]
|
]]
|
||||||
function Promise:await()
|
function Promise:await()
|
||||||
|
self._unhandledRejection = false
|
||||||
|
|
||||||
if self._status == Promise.Status.Started then
|
if self._status == Promise.Status.Started then
|
||||||
local result
|
local result
|
||||||
local bindable = Instance.new("BindableEvent")
|
local bindable = Instance.new("BindableEvent")
|
||||||
@@ -279,11 +286,22 @@ function Promise:_reject(...)
|
|||||||
-- synchronously. We'll wait one tick, and if there are still no
|
-- synchronously. We'll wait one tick, and if there are still no
|
||||||
-- observers, then we should put a message in the console.
|
-- observers, then we should put a message in the console.
|
||||||
|
|
||||||
local message = ("Unhandled promise rejection:\n\n%s\n\n%s"):format(
|
self._unhandledRejection = true
|
||||||
tostring((...)),
|
local err = tostring((...))
|
||||||
self._source
|
|
||||||
)
|
spawn(function()
|
||||||
warn(message)
|
-- Someone observed the error, hooray!
|
||||||
|
if not self._unhandledRejection then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build a reasonable message
|
||||||
|
local message = ("Unhandled promise rejection:\n\n%s\n\n%s"):format(
|
||||||
|
err,
|
||||||
|
self._source
|
||||||
|
)
|
||||||
|
warn(message)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
264
plugin/src/Reconciler.lua
Normal file
264
plugin/src/Reconciler.lua
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
local Reconciler = {}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
The set of file names that should pass as init files
|
||||||
|
These files usurp their parents.
|
||||||
|
]]
|
||||||
|
local initNames = {
|
||||||
|
["init.lua"] = true,
|
||||||
|
["init.server.lua"] = true,
|
||||||
|
["init.client.lua"] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function isInit(item, itemFileName)
|
||||||
|
if item and item.type == "dir" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
return initNames[itemFileName] or false
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Determines if the given VFS item has an init file. Yields information about
|
||||||
|
the file.
|
||||||
|
]]
|
||||||
|
local function findInit(item)
|
||||||
|
if item.type ~= "dir" then
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
for childFileName, childItem in pairs(item.children) do
|
||||||
|
if isInit(childItem, childFileName) then
|
||||||
|
return childItem, childFileName
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Given a VFS item, returns a Name and ClassName for a corresponding Roblox
|
||||||
|
instance.
|
||||||
|
|
||||||
|
Doesn't take into account init files.
|
||||||
|
]]
|
||||||
|
local function itemToName(item, fileName)
|
||||||
|
if item and item.type == "dir" then
|
||||||
|
return fileName, "Folder"
|
||||||
|
elseif item and item.type == "file" or not item then
|
||||||
|
if fileName:find("%.server%.lua$") then
|
||||||
|
return fileName:match("^(.-)%.server%.lua$"), "Script"
|
||||||
|
elseif fileName:find("%.client%.lua$") then
|
||||||
|
return fileName:match("^(.-)%.client%.lua$"), "LocalScript"
|
||||||
|
elseif fileName:find("%.lua") then
|
||||||
|
return fileName:match("^(.-)%.lua$"), "ModuleScript"
|
||||||
|
else
|
||||||
|
return fileName, "StringValue"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
error("unknown item type " .. tostring(item.type))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Given a VFS item, assigns all relevant values (except Name!) to a Roblox
|
||||||
|
instance.
|
||||||
|
]]
|
||||||
|
local function setValues(rbx, item, fileName)
|
||||||
|
local _, className = itemToName(item, fileName)
|
||||||
|
|
||||||
|
if className:find("Script") then
|
||||||
|
rbx.Source = item.contents
|
||||||
|
else
|
||||||
|
rbx.Value = item.contents
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Reconciler._reifyShallow(item, fileName)
|
||||||
|
if item.type == "dir" then
|
||||||
|
local initItem, initFileName = findInit(item)
|
||||||
|
|
||||||
|
if initItem then
|
||||||
|
local rbx = Reconciler._reify(initItem, initFileName)
|
||||||
|
rbx.Name = fileName
|
||||||
|
|
||||||
|
return rbx
|
||||||
|
else
|
||||||
|
local rbx = Instance.new("Folder")
|
||||||
|
rbx.Name = fileName
|
||||||
|
|
||||||
|
return rbx
|
||||||
|
end
|
||||||
|
elseif item.type == "file" then
|
||||||
|
local objectName, className = itemToName(item, fileName)
|
||||||
|
|
||||||
|
local rbx = Instance.new(className)
|
||||||
|
rbx.Name = objectName
|
||||||
|
|
||||||
|
setValues(rbx, item, fileName)
|
||||||
|
|
||||||
|
return rbx
|
||||||
|
else
|
||||||
|
error("unknown item type " .. tostring(item.type))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Construct a new Roblox instance tree that corresponds to the given VFS item.
|
||||||
|
]]
|
||||||
|
function Reconciler._reify(item, fileName, parent)
|
||||||
|
local rbx = Reconciler._reifyShallow(item, fileName)
|
||||||
|
|
||||||
|
if item.type == "dir" then
|
||||||
|
for childFileName, childItem in pairs(item.children) do
|
||||||
|
if not isInit(childItem, childFileName) then
|
||||||
|
local childRbx = Reconciler._reify(childItem, childFileName)
|
||||||
|
childRbx.Parent = rbx
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rbx.Parent = parent
|
||||||
|
|
||||||
|
return rbx
|
||||||
|
end
|
||||||
|
|
||||||
|
function Reconciler.reconcile(rbx, item, fileName, parent)
|
||||||
|
-- Item was deleted!
|
||||||
|
if not item then
|
||||||
|
if isInit(item, fileName) then
|
||||||
|
if not parent then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Un-usurp parent!
|
||||||
|
local newParent = Instance.new("Folder")
|
||||||
|
newParent.Name = parent.Name
|
||||||
|
|
||||||
|
for _, child in ipairs(parent:GetChildren()) do
|
||||||
|
child.Parent = newParent
|
||||||
|
end
|
||||||
|
|
||||||
|
newParent.Parent = parent.Parent
|
||||||
|
parent:Destroy()
|
||||||
|
|
||||||
|
return
|
||||||
|
else
|
||||||
|
if rbx then
|
||||||
|
rbx:Destroy()
|
||||||
|
end
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if item.type == "dir" then
|
||||||
|
-- Folder was created!
|
||||||
|
if not rbx then
|
||||||
|
return Reconciler._reify(item, fileName, parent)
|
||||||
|
end
|
||||||
|
|
||||||
|
local initItem, initFileName = findInit(item)
|
||||||
|
|
||||||
|
if initItem then
|
||||||
|
local _, initClassName = itemToName(initItem, initFileName)
|
||||||
|
|
||||||
|
if rbx.ClassName == initClassName then
|
||||||
|
setValues(rbx, initItem, initFileName)
|
||||||
|
else
|
||||||
|
rbx:Destroy()
|
||||||
|
return Reconciler._reify(item, fileName, parent)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if rbx.ClassName ~= "Folder" then
|
||||||
|
rbx:Destroy()
|
||||||
|
return Reconciler._reify(item, fileName, parent)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local visitedChildren = {}
|
||||||
|
|
||||||
|
for childFileName, childItem in pairs(item.children) do
|
||||||
|
if not isInit(childItem, childFileName) then
|
||||||
|
local childName = itemToName(childItem, childFileName)
|
||||||
|
|
||||||
|
visitedChildren[childName] = true
|
||||||
|
|
||||||
|
Reconciler.reconcile(rbx:FindFirstChild(childName), childItem, childFileName, rbx)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, childRbx in ipairs(rbx:GetChildren()) do
|
||||||
|
-- Child was deleted!
|
||||||
|
if not visitedChildren[childRbx.Name] then
|
||||||
|
childRbx:Destroy()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return rbx
|
||||||
|
elseif item.type == "file" then
|
||||||
|
if isInit(item, fileName) then
|
||||||
|
-- Usurp our container!
|
||||||
|
local _, className = itemToName(item, fileName)
|
||||||
|
|
||||||
|
if parent.ClassName == className then
|
||||||
|
rbx = parent
|
||||||
|
else
|
||||||
|
rbx = Reconciler._reify(item, fileName, parent.Parent)
|
||||||
|
rbx.Name = parent.Name
|
||||||
|
|
||||||
|
for _, child in ipairs(parent:GetChildren()) do
|
||||||
|
child.Parent = rbx
|
||||||
|
end
|
||||||
|
|
||||||
|
parent:Destroy()
|
||||||
|
end
|
||||||
|
|
||||||
|
setValues(rbx, item, fileName)
|
||||||
|
|
||||||
|
return rbx
|
||||||
|
else
|
||||||
|
if not rbx then
|
||||||
|
return Reconciler._reify(item, fileName, parent)
|
||||||
|
end
|
||||||
|
|
||||||
|
local _, className = itemToName(item, fileName)
|
||||||
|
|
||||||
|
if rbx.ClassName ~= className then
|
||||||
|
rbx:Destroy()
|
||||||
|
return Reconciler._reify(item, fileName, parent)
|
||||||
|
end
|
||||||
|
|
||||||
|
setValues(rbx, item, fileName)
|
||||||
|
|
||||||
|
return rbx
|
||||||
|
end
|
||||||
|
else
|
||||||
|
error("unknown item type " .. tostring(item.type))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Reconciler.reconcileRoute(route, item)
|
||||||
|
local location = game
|
||||||
|
|
||||||
|
for i = 1, #route - 1 do
|
||||||
|
local piece = route[i]
|
||||||
|
local newLocation = location:FindFirstChild(piece)
|
||||||
|
|
||||||
|
if not newLocation then
|
||||||
|
newLocation = Instance.new("Folder")
|
||||||
|
newLocation.Name = piece
|
||||||
|
newLocation.Parent = location
|
||||||
|
end
|
||||||
|
|
||||||
|
location = newLocation
|
||||||
|
end
|
||||||
|
|
||||||
|
local fileName = route[#route]
|
||||||
|
|
||||||
|
local name = itemToName(item, fileName)
|
||||||
|
local rbx = location:FindFirstChild(name)
|
||||||
|
Reconciler.reconcile(rbx, item, fileName, location)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Reconciler
|
||||||
4
plugin/src/runTests.lua
Normal file
4
plugin/src/runTests.lua
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
return function()
|
||||||
|
local TestEZ = require(script.Parent.Parent.TestEZ)
|
||||||
|
TestEZ.TestBootstrap:run(script.Parent)
|
||||||
|
end
|
||||||
26
rojo.json
Normal file
26
rojo.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "rojo",
|
||||||
|
"servePort": 8000,
|
||||||
|
"partitions": {
|
||||||
|
"plugin": {
|
||||||
|
"path": "plugin/src",
|
||||||
|
"target": "ReplicatedStorage.Rojo"
|
||||||
|
},
|
||||||
|
"modules/Roact": {
|
||||||
|
"path": "modules/Roact/lib",
|
||||||
|
"target": "ReplicatedStorage.Rojo.modules.Roact"
|
||||||
|
},
|
||||||
|
"modules/Rodux": {
|
||||||
|
"path": "modules/Rodux/lib",
|
||||||
|
"target": "ReplicatedStorage.Rojo.modules.Rodux"
|
||||||
|
},
|
||||||
|
"modules/RoactRodux": {
|
||||||
|
"path": "modules/RoactRodux/lib",
|
||||||
|
"target": "ReplicatedStorage.Rojo.modules.RoactRodux"
|
||||||
|
},
|
||||||
|
"modules/TestEZ": {
|
||||||
|
"path": "modules/TestEZ/lib",
|
||||||
|
"target": "ReplicatedStorage.TestEZ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -128,7 +128,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let vfs = {
|
let vfs = {
|
||||||
let mut vfs = Vfs::new();
|
let mut vfs = Vfs::new(config.clone());
|
||||||
|
|
||||||
for (name, project_partition) in &project.partitions {
|
for (name, project_partition) in &project.partitions {
|
||||||
let path = {
|
let path = {
|
||||||
@@ -158,9 +158,10 @@ fn main() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let vfs = vfs.clone();
|
let vfs = vfs.clone();
|
||||||
|
let config = config.clone();
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
VfsWatcher::new(vfs).start();
|
VfsWatcher::new(config, vfs).start();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
src/vfs.rs
11
src/vfs.rs
@@ -5,6 +5,8 @@ use std::io::Read;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use core::Config;
|
||||||
|
|
||||||
/// Represents a virtual layer over multiple parts of the filesystem.
|
/// Represents a virtual layer over multiple parts of the filesystem.
|
||||||
///
|
///
|
||||||
/// Paths in this system are represented as slices of strings, and are always
|
/// Paths in this system are represented as slices of strings, and are always
|
||||||
@@ -21,6 +23,8 @@ pub struct Vfs {
|
|||||||
/// A chronologically-sorted list of routes that changed since the Vfs was
|
/// A chronologically-sorted list of routes that changed since the Vfs was
|
||||||
/// created, along with a timestamp denoting when.
|
/// created, along with a timestamp denoting when.
|
||||||
pub change_history: Vec<VfsChange>,
|
pub change_history: Vec<VfsChange>,
|
||||||
|
|
||||||
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
@@ -38,11 +42,12 @@ pub enum VfsItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Vfs {
|
impl Vfs {
|
||||||
pub fn new() -> Vfs {
|
pub fn new(config: Config) -> Vfs {
|
||||||
Vfs {
|
Vfs {
|
||||||
partitions: HashMap::new(),
|
partitions: HashMap::new(),
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
change_history: Vec::new(),
|
change_history: Vec::new(),
|
||||||
|
config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,6 +145,10 @@ impl Vfs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_change(&mut self, timestamp: f64, route: Vec<String>) {
|
pub fn add_change(&mut self, timestamp: f64, route: Vec<String>) {
|
||||||
|
if self.config.verbose {
|
||||||
|
println!("Added change {:?}", route);
|
||||||
|
}
|
||||||
|
|
||||||
self.change_history.push(VfsChange {
|
self.change_history.push(VfsChange {
|
||||||
timestamp,
|
timestamp,
|
||||||
route,
|
route,
|
||||||
|
|||||||
@@ -6,17 +6,20 @@ use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
|||||||
|
|
||||||
use vfs::Vfs;
|
use vfs::Vfs;
|
||||||
use pathext::path_to_route;
|
use pathext::path_to_route;
|
||||||
|
use core::Config;
|
||||||
|
|
||||||
pub struct VfsWatcher {
|
pub struct VfsWatcher {
|
||||||
vfs: Arc<Mutex<Vfs>>,
|
vfs: Arc<Mutex<Vfs>>,
|
||||||
watchers: Vec<RecommendedWatcher>,
|
watchers: Vec<RecommendedWatcher>,
|
||||||
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VfsWatcher {
|
impl VfsWatcher {
|
||||||
pub fn new(vfs: Arc<Mutex<Vfs>>) -> VfsWatcher {
|
pub fn new(config: Config, vfs: Arc<Mutex<Vfs>>) -> VfsWatcher {
|
||||||
VfsWatcher {
|
VfsWatcher {
|
||||||
vfs,
|
vfs,
|
||||||
watchers: Vec::new(),
|
watchers: Vec::new(),
|
||||||
|
config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +43,7 @@ impl VfsWatcher {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let vfs = self.vfs.clone();
|
let vfs = self.vfs.clone();
|
||||||
|
let config = self.config.clone();
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
loop {
|
loop {
|
||||||
@@ -47,6 +51,10 @@ impl VfsWatcher {
|
|||||||
let mut vfs = vfs.lock().unwrap();
|
let mut vfs = vfs.lock().unwrap();
|
||||||
let current_time = vfs.current_time();
|
let current_time = vfs.current_time();
|
||||||
|
|
||||||
|
if config.verbose {
|
||||||
|
println!("FS event {:?}", event);
|
||||||
|
}
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
DebouncedEvent::Write(ref change_path) |
|
DebouncedEvent::Write(ref change_path) |
|
||||||
DebouncedEvent::Create(ref change_path) |
|
DebouncedEvent::Create(ref change_path) |
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
-- meh/init.lua
|
|
||||||
10
test-project/rojo.json
Normal file
10
test-project/rojo.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "test-project",
|
||||||
|
"servePort": 8001,
|
||||||
|
"partitions": {
|
||||||
|
"src": {
|
||||||
|
"path": "src",
|
||||||
|
"target": "ReplicatedStorage.TestProject"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
test-project/src/meh/init.lua
Normal file
0
test-project/src/meh/init.lua
Normal file
Reference in New Issue
Block a user