Compare commits
559 Commits
v0.5.0-alp
...
memofs-v0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b433e53be | ||
|
|
28ddf40344 | ||
|
|
c1286db9c1 | ||
|
|
f13940262e | ||
|
|
9f0a6101b8 | ||
|
|
0b0fe01a7c | ||
|
|
85e098d5c8 | ||
|
|
e8d1faf4e2 | ||
|
|
2a46df1110 | ||
|
|
1601e6d26e | ||
|
|
0e4f6dea2b | ||
|
|
a2356773dc | ||
|
|
4a4da4737d | ||
|
|
2cefd1bf2e | ||
|
|
c5ce15fe34 | ||
|
|
76dea568c9 | ||
|
|
8e81140eff | ||
|
|
d58e1f0792 | ||
|
|
830c242751 | ||
|
|
91d45afd0f | ||
|
|
102c77b23e | ||
|
|
aa4039a2e7 | ||
|
|
c065ded440 | ||
|
|
f69096dadb | ||
|
|
363f95ba14 | ||
|
|
dcc15e8911 | ||
|
|
bd13047b58 | ||
|
|
1a83789c01 | ||
|
|
1cbe272e19 | ||
|
|
6de74b41b3 | ||
|
|
b0fc9ee507 | ||
|
|
a95ffe1d31 | ||
|
|
4119a510f5 | ||
|
|
cfa7f03815 | ||
|
|
9b4c89820d | ||
|
|
fe874720aa | ||
|
|
f7c0f33eb5 | ||
|
|
c1bf9d9dfc | ||
|
|
255bf439d3 | ||
|
|
2a31937b81 | ||
|
|
eb8964e1d1 | ||
|
|
fe0ca280a1 | ||
|
|
e8e3b7b985 | ||
|
|
c437507442 | ||
|
|
ca151b434e | ||
|
|
10ba74c21e | ||
|
|
6191b6371d | ||
|
|
5be4175ac3 | ||
|
|
f61f3671a6 | ||
|
|
477e0ada32 | ||
|
|
a884f693ae | ||
|
|
3107b1b21b | ||
|
|
04529de7b3 | ||
|
|
199a39208c | ||
|
|
0187da5f24 | ||
|
|
2e1c37ffff | ||
|
|
4b0189dd55 | ||
|
|
d4a39674de | ||
|
|
07c6fd3711 | ||
|
|
cf5b72aa9a | ||
|
|
cdfbebd637 | ||
|
|
e86e3316a9 | ||
|
|
bb2dcbaea0 | ||
|
|
fefc7a69cd | ||
|
|
1b24cd36e0 | ||
|
|
6322c1f46d | ||
|
|
00a29bb6be | ||
|
|
3a029caf2b | ||
|
|
57263905e7 | ||
|
|
4f46012c11 | ||
|
|
7ea9a1e3e8 | ||
|
|
c7510e12c4 | ||
|
|
89b5d9294c | ||
|
|
339e1060b7 | ||
|
|
52e1dbd846 | ||
|
|
b4963f4ff7 | ||
|
|
838e8f6bde | ||
|
|
8f21514855 | ||
|
|
44041f33e3 | ||
|
|
5d9bc4473c | ||
|
|
a3e0d42e86 | ||
|
|
4a9c1d0d1b | ||
|
|
ff47f79c62 | ||
|
|
f46a391873 | ||
|
|
99b8ada42b | ||
|
|
f2227aa7cb | ||
|
|
a9f6c20113 | ||
|
|
e261e7a2c7 | ||
|
|
ae811aafd0 | ||
|
|
cc593b465d | ||
|
|
f81e8339e3 | ||
|
|
cdc5513726 | ||
|
|
a398338c02 | ||
|
|
26e2e81188 | ||
|
|
46d7bba87d | ||
|
|
57d5610a58 | ||
|
|
1968e1fdb7 | ||
|
|
f2584cf807 | ||
|
|
dd592d1d6d | ||
|
|
cfff08cdfd | ||
|
|
e83437c193 | ||
|
|
1d900a6a3c | ||
|
|
859c7bea8a | ||
|
|
1b9e90e786 | ||
|
|
41396367ac | ||
|
|
16c9a23d55 | ||
|
|
ce338a2a72 | ||
|
|
1f7f2b22e7 | ||
|
|
47c7f63d75 | ||
|
|
8b1e85fbb4 | ||
|
|
ff4e9fb027 | ||
|
|
d15ef40988 | ||
|
|
eadfb18f74 | ||
|
|
4b89bb087a | ||
|
|
f0a602b48b | ||
|
|
fe10da9a6c | ||
|
|
948303aac8 | ||
|
|
12df80da56 | ||
|
|
a48c238ed7 | ||
|
|
da6c7b4d7a | ||
|
|
15cbbacc2f | ||
|
|
c3f6a17bae | ||
|
|
0fbeb70865 | ||
|
|
02f98a4053 | ||
|
|
d2e2a13479 | ||
|
|
f32cb592e2 | ||
|
|
72342f3118 | ||
|
|
246fd5f6c4 | ||
|
|
715022def5 | ||
|
|
78b2bafde1 | ||
|
|
2d7ebac8e6 | ||
|
|
bcffd2eb99 | ||
|
|
459bf62fab | ||
|
|
bdaa671823 | ||
|
|
463bf9b116 | ||
|
|
0c7a94c062 | ||
|
|
af866f0665 | ||
|
|
2493c70241 | ||
|
|
3c3359999c | ||
|
|
2467004dc2 | ||
|
|
3a9f438390 | ||
|
|
3126de2c37 | ||
|
|
b7d026b98e | ||
|
|
3f8b178f88 | ||
|
|
6184f4ce4f | ||
|
|
fa736697a9 | ||
|
|
2c88c700ca | ||
|
|
0b2e9ce1ad | ||
|
|
ccafdf250e | ||
|
|
57b28faa74 | ||
|
|
d0d7c58af1 | ||
|
|
339fc979f5 | ||
|
|
47614c3102 | ||
|
|
94e1501329 | ||
|
|
dfb015acc2 | ||
|
|
f1daafbf9e | ||
|
|
432e10c205 | ||
|
|
62b626a931 | ||
|
|
9a2893c6bc | ||
|
|
3a538f98ed | ||
|
|
7c71708de7 | ||
|
|
b64d97e808 | ||
|
|
428a19789d | ||
|
|
7cc4055d94 | ||
|
|
114c93fa46 | ||
|
|
07801a0283 | ||
|
|
30c8ea583b | ||
|
|
d54a5f647e | ||
|
|
457ed05174 | ||
|
|
44c94da2d8 | ||
|
|
ec614e1912 | ||
|
|
559b509a03 | ||
|
|
e2e9209655 | ||
|
|
e55b72f73f | ||
|
|
85e30cc968 | ||
|
|
51dcfbab75 | ||
|
|
7c0aa45057 | ||
|
|
a5fdc2a9cc | ||
|
|
64fd2f9cf8 | ||
|
|
a77495c562 | ||
|
|
3880708e1d | ||
|
|
cc68d57f11 | ||
|
|
56f5a61362 | ||
|
|
40540c3637 | ||
|
|
82678235ab | ||
|
|
5123d21290 | ||
|
|
b0dcf515f0 | ||
|
|
9aafccc946 | ||
|
|
ccf98d7283 | ||
|
|
1b35c98be5 | ||
|
|
db23c3d35a | ||
|
|
1c6c1298d5 | ||
|
|
b9ebed14a5 | ||
|
|
7077f0f1f3 | ||
|
|
d1887c6cd3 | ||
|
|
9278c81611 | ||
|
|
dec9ec32df | ||
|
|
1967f738a8 | ||
|
|
1031600c63 | ||
|
|
24c697bea7 | ||
|
|
83665018d4 | ||
|
|
b093626a21 | ||
|
|
f3dc78b7cd | ||
|
|
f0cd4333c3 | ||
|
|
6d38a785ed | ||
|
|
e46f9fd94f | ||
|
|
6b620ddcef | ||
|
|
e5684ad947 | ||
|
|
dfabc07044 | ||
|
|
dca88e8272 | ||
|
|
9f947ae2c5 | ||
|
|
28156bcaf2 | ||
|
|
a14aacbcf9 | ||
|
|
321e026e43 | ||
|
|
30351f7b9d | ||
|
|
2393a1a114 | ||
|
|
479476561e | ||
|
|
a02f485040 | ||
|
|
2957e8ad73 | ||
|
|
f83abe15cb | ||
|
|
709cba45ce | ||
|
|
8a9a72fd50 | ||
|
|
13cb0a27a0 | ||
|
|
fa817e3cdd | ||
|
|
3bd8549f41 | ||
|
|
4e47655b17 | ||
|
|
18533d5944 | ||
|
|
c5839c94ca | ||
|
|
ffc146ff9b | ||
|
|
7b82e3d641 | ||
|
|
ab6cedb659 | ||
|
|
e60be94be0 | ||
|
|
f830b024d5 | ||
|
|
98519da7d9 | ||
|
|
cb3211cf46 | ||
|
|
c051153a1f | ||
|
|
956d7e0918 | ||
|
|
530a7aa834 | ||
|
|
052ca52cc3 | ||
|
|
2025b8a494 | ||
|
|
238233ca81 | ||
|
|
938c8259c3 | ||
|
|
35df5f25c7 | ||
|
|
cd84fb9fc1 | ||
|
|
8bcf12e317 | ||
|
|
10ed353e0a | ||
|
|
72d88200e4 | ||
|
|
6ef832b3a9 | ||
|
|
17d91563d5 | ||
|
|
fb65693627 | ||
|
|
079fc738ad | ||
|
|
ae0f3b0b02 | ||
|
|
b2c515f2e6 | ||
|
|
6f1469a551 | ||
|
|
1d441b86d2 | ||
|
|
b72e6e54e6 | ||
|
|
1ee679395d | ||
|
|
cf62ee5ccb | ||
|
|
995685adfe | ||
|
|
f0eb955628 | ||
|
|
923f661428 | ||
|
|
b562d11994 | ||
|
|
3554112f31 | ||
|
|
7cada2608f | ||
|
|
21d4acebc3 | ||
|
|
73bbaaf0af | ||
|
|
40105515d2 | ||
|
|
79f09deecb | ||
|
|
c4a7f8675f | ||
|
|
f9a5fee364 | ||
|
|
bdd9c58cae | ||
|
|
09368e87cf | ||
|
|
46858c45b8 | ||
|
|
fe1a96f850 | ||
|
|
581449d992 | ||
|
|
15e848d4bf | ||
|
|
0dbbf44ab2 | ||
|
|
c62a5d15ad | ||
|
|
53bd02a890 | ||
|
|
099aa26ef8 | ||
|
|
2598ea3577 | ||
|
|
7be5b36494 | ||
|
|
3dff4d1061 | ||
|
|
5b7037550d | ||
|
|
b94f21402c | ||
|
|
9f5875d4bb | ||
|
|
694b6101ca | ||
|
|
1737da9c1f | ||
|
|
bcf4fea598 | ||
|
|
90e41f3ce9 | ||
|
|
5f5fb51eae | ||
|
|
95f06d56d8 | ||
|
|
1c6788ea45 | ||
|
|
457a8a5cf8 | ||
|
|
282caf10a5 | ||
|
|
b2fc6c165b | ||
|
|
0a1fa9588f | ||
|
|
5f5bfadf2b | ||
|
|
b512e707a5 | ||
|
|
3678ddfa36 | ||
|
|
ab8aa89f2a | ||
|
|
fd22482f06 | ||
|
|
e741f7b557 | ||
|
|
a70b7ee150 | ||
|
|
87227c96ed | ||
|
|
91d12aeb4f | ||
|
|
b3f132201b | ||
|
|
24b9f552eb | ||
|
|
b4a8dec68c | ||
|
|
c140823bea | ||
|
|
f166cc93cd | ||
|
|
125c9767f1 | ||
|
|
7b7c978db6 | ||
|
|
26fc097672 | ||
|
|
0f2e2406e8 | ||
|
|
061a096600 | ||
|
|
07fe963bed | ||
|
|
7a1eda98ca | ||
|
|
e8a5e44319 | ||
|
|
486319407a | ||
|
|
4f3d917c9b | ||
|
|
e2761965d5 | ||
|
|
171ab196c8 | ||
|
|
5630cea9a0 | ||
|
|
5a4189a770 | ||
|
|
2440d9fc48 | ||
|
|
bd33aebc3d | ||
|
|
a46d467b75 | ||
|
|
8c6981da0d | ||
|
|
500a9f647f | ||
|
|
71968fca0d | ||
|
|
fc6f84897f | ||
|
|
b31ad4b1f8 | ||
|
|
aababf37a8 | ||
|
|
44a42a177a | ||
|
|
e773a92e53 | ||
|
|
196d27b959 | ||
|
|
ec8861e983 | ||
|
|
9b601eb9fe | ||
|
|
c80d9cbf01 | ||
|
|
717c15256f | ||
|
|
13dafc2091 | ||
|
|
cfc3bcfa41 | ||
|
|
2e052e97c5 | ||
|
|
5d48d05287 | ||
|
|
e34ba844c5 | ||
|
|
1d84d3e440 | ||
|
|
7a7e9087e6 | ||
|
|
8481caa67c | ||
|
|
651e63a0fb | ||
|
|
88e462c4e5 | ||
|
|
8ef797d1a4 | ||
|
|
877fd1af35 | ||
|
|
fc01eecdcb | ||
|
|
e6ba6203bb | ||
|
|
6a786f18e6 | ||
|
|
57d46287d7 | ||
|
|
bb6ab74c19 | ||
|
|
3e759b3e8e | ||
|
|
47ee8d54a8 | ||
|
|
824b984a64 | ||
|
|
618185a52d | ||
|
|
8dbc6ab7d3 | ||
|
|
5eb6754b7c | ||
|
|
a914a92cea | ||
|
|
439a1a758e | ||
|
|
9383240627 | ||
|
|
ea765eb929 | ||
|
|
d5c816f24d | ||
|
|
cf5f20bbb5 | ||
|
|
27839dfd21 | ||
|
|
27517e1aee | ||
|
|
a31bfbefa7 | ||
|
|
f1729163cf | ||
|
|
6747d97d60 | ||
|
|
2fb2342fd4 | ||
|
|
0f530b7e80 | ||
|
|
78e3cf4ffb | ||
|
|
4e512b7023 | ||
|
|
ee3fed97e0 | ||
|
|
b45d4f6401 | ||
|
|
d4c28de2c4 | ||
|
|
b7d613ace6 | ||
|
|
68dbb31272 | ||
|
|
8fe1fa48b8 | ||
|
|
e5575b782c | ||
|
|
ea112dd93d | ||
|
|
056fc5e087 | ||
|
|
cacb02b7c8 | ||
|
|
c0b6ec8ccb | ||
|
|
f4c8f0a3d8 | ||
|
|
81407ffe3c | ||
|
|
6f7dbe99fe | ||
|
|
ec9afba029 | ||
|
|
6164c5d78d | ||
|
|
94dbcd5c06 | ||
|
|
4d0dee7ee8 | ||
|
|
49e10698b1 | ||
|
|
8e1536b59b | ||
|
|
aed160994a | ||
|
|
b2b3173420 | ||
|
|
f465af964a | ||
|
|
5a7d6a673a | ||
|
|
7fb9aa2115 | ||
|
|
fea303ac8b | ||
|
|
8e8291a0bd | ||
|
|
4dc175fcd2 | ||
|
|
009a99a8eb | ||
|
|
91caa67e04 | ||
|
|
53793f7dd2 | ||
|
|
32af8783ba | ||
|
|
d6f51f8eba | ||
|
|
f40b7361e8 | ||
|
|
0231e114d5 | ||
|
|
ca61a3e83f | ||
|
|
6979f5c82d | ||
|
|
7d45b45667 | ||
|
|
7575504b80 | ||
|
|
36bcb611f1 | ||
|
|
cc34c36794 | ||
|
|
b84cb93d2c | ||
|
|
08211a7740 | ||
|
|
97985dd458 | ||
|
|
edf157215c | ||
|
|
89671c71fa | ||
|
|
eeedcc50ab | ||
|
|
de0bb069aa | ||
|
|
5d53f8bfb3 | ||
|
|
d4a7de8070 | ||
|
|
3fe15641c7 | ||
|
|
08df71a7e4 | ||
|
|
77db25b4d9 | ||
|
|
62bb5a28aa | ||
|
|
f768b200d2 | ||
|
|
a5b6db74e5 | ||
|
|
cbe4467a22 | ||
|
|
8417d18eec | ||
|
|
93ae8187cd | ||
|
|
972338d86a | ||
|
|
a4a6e3b8f6 | ||
|
|
d788dd3042 | ||
|
|
974760f020 | ||
|
|
65569c4a60 | ||
|
|
39da45f0bd | ||
|
|
dc7c27e619 | ||
|
|
90661b7743 | ||
|
|
d07571ea7e | ||
|
|
fbf29e336f | ||
|
|
09a0a803a1 | ||
|
|
dd0327ba85 | ||
|
|
d900887d97 | ||
|
|
2a0efe70a5 | ||
|
|
ce09e57315 | ||
|
|
91023c5239 | ||
|
|
714fb10fac | ||
|
|
aa3e43207f | ||
|
|
e045989d39 | ||
|
|
ad5695210d | ||
|
|
4dab6e5008 | ||
|
|
522f26cf4e | ||
|
|
3eca4bc439 | ||
|
|
b374f67b52 | ||
|
|
c68277be2c | ||
|
|
bb8a3e82e6 | ||
|
|
b511d4ba53 | ||
|
|
fd997d4bda | ||
|
|
21d04a9f85 | ||
|
|
dcb5c12197 | ||
|
|
125e8766c5 | ||
|
|
7bce1f6df4 | ||
|
|
8f66fb6fef | ||
|
|
711e009e6d | ||
|
|
212fe31cb3 | ||
|
|
a3dc4fa001 | ||
|
|
ff53113358 | ||
|
|
94cbe15b54 | ||
|
|
90516e035d | ||
|
|
c77c754f6d | ||
|
|
288c52a2cd | ||
|
|
f0fa7326dd | ||
|
|
f29b0f2f26 | ||
|
|
5dcac24f99 | ||
|
|
1eb11ac377 | ||
|
|
2e89cdcfad | ||
|
|
1b0beccd3d | ||
|
|
abb5a72fc4 | ||
|
|
bf706f7586 | ||
|
|
4459663510 | ||
|
|
68a34dc28b | ||
|
|
ba1826587c | ||
|
|
2e7a8d50b0 | ||
|
|
2a4ca21050 | ||
|
|
0ed6c57c7f | ||
|
|
983d44947e | ||
|
|
5bd88dc82f | ||
|
|
51bbab803f | ||
|
|
a587ba4558 | ||
|
|
075b6cca30 | ||
|
|
4c263bbb3e | ||
|
|
420627d892 | ||
|
|
ce3a409997 | ||
|
|
0f9f1782ae | ||
|
|
d4704a02c5 | ||
|
|
9ca2ed2c93 | ||
|
|
ae12ffdefb | ||
|
|
1e13097126 | ||
|
|
9b8a6b1168 | ||
|
|
8f6dda5cd3 | ||
|
|
91780f236e | ||
|
|
f16474815c | ||
|
|
a8ff6d7e6e | ||
|
|
8395782a2e | ||
|
|
28ea625b01 | ||
|
|
efc569f6ed | ||
|
|
d377e10771 | ||
|
|
fef85877e6 | ||
|
|
19135bfaf4 | ||
|
|
5a147fccc2 | ||
|
|
20976814ba | ||
|
|
27e2612fc9 | ||
|
|
3ea432ef2d | ||
|
|
fe6acbc1e3 | ||
|
|
379b162e64 | ||
|
|
84832955dd | ||
|
|
34b99a51c3 | ||
|
|
fb5245e2af | ||
|
|
ff0a830e0c | ||
|
|
a365f071a4 | ||
|
|
f290e7b5b2 | ||
|
|
83a0ae673c | ||
|
|
7de646c290 | ||
|
|
5d681a72ac | ||
|
|
d725970e6e | ||
|
|
54b82760cd | ||
|
|
77f79fa913 | ||
|
|
6db714a2b1 | ||
|
|
913ac7c9f5 | ||
|
|
eecbfd29e7 | ||
|
|
41025225b2 | ||
|
|
07c7b28c03 | ||
|
|
3faf3d2a56 | ||
|
|
be094d5b7c | ||
|
|
459673bd59 | ||
|
|
2968b70e6b | ||
|
|
b6989a18fc | ||
|
|
4d6a504836 | ||
|
|
6c3737df68 | ||
|
|
9f382ed9bd | ||
|
|
f9e86e58d6 | ||
|
|
469f9c927f | ||
|
|
312724189b | ||
|
|
ec0a1f1ce4 | ||
|
|
ad93631ef8 | ||
|
|
3b6238ff93 | ||
|
|
5b9facee00 | ||
|
|
376f2a554a | ||
|
|
5fd0bd3db9 | ||
|
|
2deb3bbf23 | ||
|
|
01bef0c2b8 |
@@ -3,16 +3,24 @@ root = true
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = false
|
||||
|
||||
[*.{json,js,css}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{md,rs}]
|
||||
[*.md]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{rs,toml}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
|
||||
[*.snap]
|
||||
insert_final_newline = true
|
||||
|
||||
[*.lua]
|
||||
indent_style = tab
|
||||
36
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
rust_version: [stable, "1.40.0"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: rustup default ${{ matrix.rust_version }}
|
||||
|
||||
- name: Build
|
||||
run: cargo build --locked --verbose
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --locked --verbose
|
||||
|
||||
- name: Rustfmt and Clippy
|
||||
run: |
|
||||
cargo fmt -- --check
|
||||
cargo clippy
|
||||
if: matrix.rust_version == 'stable'
|
||||
|
||||
- name: Build (All Features)
|
||||
run: cargo build --locked --verbose --all-features
|
||||
|
||||
- name: Run tests (All Features)
|
||||
run: cargo test --locked --verbose --all-features
|
||||
60
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["*"]
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Build release binary
|
||||
run: cargo build --verbose --locked --release
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: rojo-win64
|
||||
path: target/release/rojo.exe
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Install Rust
|
||||
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
|
||||
- name: Build release binary
|
||||
run: |
|
||||
source $HOME/.cargo/env
|
||||
cargo build --verbose --locked --release
|
||||
env:
|
||||
OPENSSL_STATIC: 1
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: rojo-macos
|
||||
path: target/release/rojo
|
||||
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Build
|
||||
run: cargo build --locked --verbose --release
|
||||
env:
|
||||
OPENSSL_STATIC: 1
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: rojo-linux
|
||||
path: target/release/rojo
|
||||
21
.gitignore
vendored
@@ -1,5 +1,18 @@
|
||||
/site
|
||||
# Rust output directory
|
||||
/target
|
||||
/scratch-project
|
||||
**/*.rs.bk
|
||||
/generate-docs.run
|
||||
|
||||
# Headers for clibrojo
|
||||
/include
|
||||
|
||||
# Roblox model and place files in the root, used for debugging
|
||||
/*.rbxm
|
||||
/*.rbxmx
|
||||
/*.rbxl
|
||||
/*.rbxlx
|
||||
|
||||
# Roblox Studio holds 'lock' files on places
|
||||
*.rbxl.lock
|
||||
*.rbxlx.lock
|
||||
|
||||
# Snapshot files from the 'insta' Rust crate
|
||||
**/*.snap.new
|
||||
15
.gitmodules
vendored
@@ -1,18 +1,15 @@
|
||||
[submodule "plugin/modules/roact"]
|
||||
path = plugin/modules/roact
|
||||
url = https://github.com/Roblox/roact.git
|
||||
[submodule "plugin/modules/rodux"]
|
||||
path = plugin/modules/rodux
|
||||
url = https://github.com/Roblox/rodux.git
|
||||
[submodule "plugin/modules/roact-rodux"]
|
||||
path = plugin/modules/roact-rodux
|
||||
url = https://github.com/Roblox/roact-rodux.git
|
||||
[submodule "plugin/modules/testez"]
|
||||
path = plugin/modules/testez
|
||||
url = https://github.com/Roblox/testez.git
|
||||
[submodule "plugin/modules/lemur"]
|
||||
path = plugin/modules/lemur
|
||||
url = https://github.com/LPGhatguy/lemur.git
|
||||
[submodule "plugin/modules/promise"]
|
||||
path = plugin/modules/promise
|
||||
url = https://github.com/LPGhatguy/roblox-lua-promise.git
|
||||
[submodule "plugin/modules/t"]
|
||||
path = plugin/modules/t
|
||||
url = https://github.com/osyrisrblx/t.git
|
||||
[submodule "plugin/modules/rbx-dom"]
|
||||
path = plugin/modules/rbx-dom
|
||||
url = http://github.com/rojo-rbx/rbx-dom
|
||||
@@ -20,6 +20,7 @@ stds.roblox = {
|
||||
"CFrame",
|
||||
"Enum",
|
||||
"Instance",
|
||||
"DockWidgetPluginGuiInfo",
|
||||
}
|
||||
}
|
||||
|
||||
54
.travis.yml
@@ -1,54 +0,0 @@
|
||||
matrix:
|
||||
include:
|
||||
# Lua tests are currently disabled because of holes in Lemur that are pretty
|
||||
# tedious to fix. It should be fixed by either adding missing features to
|
||||
# Lemur or by migrating to a CI system based on real Roblox instead.
|
||||
|
||||
# - language: python
|
||||
# env:
|
||||
# - LUA="lua=5.1"
|
||||
|
||||
# before_install:
|
||||
# - pip install hererocks
|
||||
# - hererocks lua_install -r^ --$LUA
|
||||
# - export PATH=$PATH:$PWD/lua_install/bin
|
||||
|
||||
# install:
|
||||
# - luarocks install luafilesystem
|
||||
# - luarocks install busted
|
||||
# - luarocks install luacov
|
||||
# - luarocks install luacov-coveralls
|
||||
# - luarocks install luacheck
|
||||
|
||||
# script:
|
||||
# - cd plugin
|
||||
# - luacheck src
|
||||
# - lua -lluacov spec.lua
|
||||
|
||||
# after_success:
|
||||
# - cd plugin
|
||||
# - luacov-coveralls -e $TRAVIS_BUILD_DIR/lua_install
|
||||
|
||||
- language: rust
|
||||
rust: 1.32.0
|
||||
cache: cargo
|
||||
|
||||
script:
|
||||
- cargo test --verbose
|
||||
- cargo test --verbose --all-features
|
||||
|
||||
- language: rust
|
||||
rust: stable
|
||||
cache: cargo
|
||||
|
||||
script:
|
||||
- cargo test --verbose
|
||||
- cargo test --verbose --all-features
|
||||
|
||||
- language: rust
|
||||
rust: beta
|
||||
cache: cargo
|
||||
|
||||
script:
|
||||
- cargo test --verbose
|
||||
- cargo test --verbose --all-features
|
||||
244
CHANGELOG.md
@@ -1,41 +1,183 @@
|
||||
# Rojo Changelog
|
||||
|
||||
## [Unreleased]
|
||||
## Unreleased Changes for 0.6.x
|
||||
* Added basic settings panel to plugin, with two settings:
|
||||
* "Open Scripts Externally": When enabled, opening a script in Studio will instead open it in your default text editor.
|
||||
* "Two-Way Sync": When enabled, Rojo will attempt to save changes to your place back to the filesystem. **Very early feature, very broken, beware!**
|
||||
* Added `--color` option to force-enable or force-disable color in Rojo's output.
|
||||
* The server half of **experimental** two-way sync is now enabled by default.
|
||||
* Increased default logging verbosity in commands like `rojo build`.
|
||||
|
||||
## [0.5.0 Alpha 5](https://github.com/LPGhatguy/rojo/releases/tag/v0.5.0-alpha.5) (March 1, 2019)
|
||||
## [0.6.0 Alpha 3](https://github.com/rojo-rbx/rojo/releases/tag/v0.6.0-alpha.3) (March 13, 2020)
|
||||
* Added `--watch` argument to `rojo build`. ([#284](https://github.com/rojo-rbx/rojo/pull/284))
|
||||
* Added dark theme support to plugin. ([#241](https://github.com/rojo-rbx/rojo/issues/241))
|
||||
* Added a revamped `rojo init` command, which will now create more complete projects.
|
||||
* Added the `rojo doc` command, which opens Rojo's documentation in your browser.
|
||||
* Fixed many crashes from malformed projects and filesystem edge cases in `rojo serve`.
|
||||
* Simplified filesystem access code dramatically.
|
||||
* Improved error reporting and logging across the board.
|
||||
* Log messages have a less noisy prefix.
|
||||
* Any thread panicking now causes Rojo to abort instead of existing as a zombie.
|
||||
* Errors now have a list of causes, helping make many errors more clear.
|
||||
|
||||
## [0.6.0 Alpha 2](https://github.com/rojo-rbx/rojo/releases/tag/v0.6.0-alpha.2) (March 6, 2020)
|
||||
* Fixed `rojo upload` command always uploading models.
|
||||
* Removed `--kind` parameter to `rojo upload`; Rojo now automatically uploads the correct kind of asset based on your project file.
|
||||
|
||||
## [0.5.4](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.4) (February 26, 2020)
|
||||
This is a general maintenance release for the Rojo 0.5.x release series.
|
||||
|
||||
* Updated reflection database and other dependencies.
|
||||
* First stable release with binaries for macOS and Linux.
|
||||
|
||||
## [0.6.0 Alpha 1](https://github.com/rojo-rbx/rojo/releases/tag/v0.6.0-alpha.1) (January 22, 2020)
|
||||
|
||||
### General
|
||||
* Added support for nested project files. ([#95](https://github.com/rojo-rbx/rojo/issues/95))
|
||||
* Added project file hot-reloading. ([#10](https://github.com/rojo-rbx/rojo/issues/10)])
|
||||
* Fixed Rojo dropping Ref properties ([#142](https://github.com/rojo-rbx/rojo/issues/142))
|
||||
* This means that properties like `PrimaryPart` now work!
|
||||
* Improved live sync protocol to reduce round-trips and improve syncing consistency.
|
||||
* Improved support for binary model files and places.
|
||||
|
||||
### Command Line
|
||||
* Added `--verbose`/`-v` flag, which can be specified multiple times to increase verbosity.
|
||||
* Added support for automatically finding Roblox Studio's auth cookie for `rojo upload` on Windows.
|
||||
* Added support for building, serving and uploading sources that aren't Rojo projects.
|
||||
* Improved feedback from `rojo serve`.
|
||||
* Removed support for legacy `roblox-project.json` projects, deprecated in an early Rojo 0.5.0 alpha.
|
||||
* Rojo no longer traverses directories upwards looking for project files.
|
||||
* Though undocumented, Rojo 0.5.x will search for a project file contained in any ancestor folders. This feature was removed to better support other 0.6.x features.
|
||||
|
||||
### Roblox Studio Plugin
|
||||
* Added "connecting" state to improve experience when live syncing.
|
||||
* Added "error" state to show errors in a place that isn't the output panel.
|
||||
* Improved diagnostics for when the Rojo plugin cannot create an instance.
|
||||
|
||||
## [0.5.3](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.3) (October 15, 2019)
|
||||
* Fixed an issue where Rojo would throw an error when encountering recently-added instance classes.
|
||||
|
||||
## [0.5.2](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.2) (October 14, 2019)
|
||||
* Fixed an issue where `LocalizationTable` instances would have their column order randomized. ([#173](https://github.com/rojo-rbx/rojo/issues/173))
|
||||
|
||||
## [0.5.1](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.1) (October 4, 2019)
|
||||
* Fixed an issue where Rojo would drop changes if they happened too quickly ([#252](https://github.com/rojo-rbx/rojo/issues/252))
|
||||
* Improved diagnostics for when the Rojo plugin cannot create an instance.
|
||||
* Updated dependencies
|
||||
* This brings Rojo's reflection database from client release 395 to client release 404.
|
||||
|
||||
## [0.5.0](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0) (August 27, 2019)
|
||||
* Changed `.model.json` naming, which may require projects to migrate ambiguous cases:
|
||||
* The file name now takes precedence over the `Name` field in the model, like Rojo 0.4.x.
|
||||
* The `Name` field of the top-level instance is now optional. It's recommended that you remove it from your models.
|
||||
* Rojo will emit a warning when `Name` is specified and does not match the name from the file.
|
||||
* Fixed `Rect` values being set to `0, 0, 0, 0` when synced with the Rojo plugin. ([#201](https://github.com/rojo-rbx/rojo/issues/201))
|
||||
* Fixed live-syncing of `PhysicalProperties`, `NumberSequence`, and `ColorSequence` values
|
||||
|
||||
## [0.5.0 Alpha 13](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.13) (August 2, 2019)
|
||||
* Bumped minimum Rust version to 1.34.0.
|
||||
* Fixed default port documentation in `rojo serve --help` ([#219](https://github.com/rojo-rbx/rojo/issues/219))
|
||||
* Fixed BrickColor support by upgrading Roblox-related dependencies
|
||||
|
||||
## [0.5.0 Alpha 12](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.12) (July 2, 2019)
|
||||
* Added `.meta.json` files
|
||||
* `init.meta.json` files replace `init.model.json` files from Rojo 0.4.x ([#183](https://github.com/rojo-rbx/rojo/pull/183))
|
||||
* Other `.meta.json` files allow attaching extra data to other files ([#189](https://github.com/rojo-rbx/rojo/pull/189))
|
||||
* Added support for infinite and NaN values in types like `Vector2` when building models and places.
|
||||
* These types aren't supported for live-syncing yet due to limitations around JSON encoding.
|
||||
* Added support for using `SharedString` values when building XML models and places.
|
||||
* Added support for live-syncing `CollectionService` tags.
|
||||
* Added a warning when building binary place files, since they're still experimental and have bugs.
|
||||
* Added a warning when trying to use Rojo 0.5.x with a Rojo 0.4.x-only project.
|
||||
* Added a warning when a Rojo project contains keys that start with `$`, which are reserved names. ([#191](https://github.com/rojo-rbx/rojo/issues/191))
|
||||
* Rojo now throws an error if unknown keys are found most files.
|
||||
* Added an icon to the plugin's toolbar button
|
||||
* Changed the plugin to use a docking widget for all UI.
|
||||
* Changed the plugin to ignore unknown properties when live-syncing.
|
||||
* Rojo's approach to this problem might change later, like with a strict model mode ([#190](https://github.com/rojo-rbx/rojo/issues/190)) or another approach.
|
||||
* Upgraded to reflection database from client release 388.
|
||||
* Updated Rojo's branding to shift the color palette to make it work better on dark backgrounds
|
||||
|
||||
## [0.5.0 Alpha 11](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.11) (May 29, 2019)
|
||||
* Added support for implicit property values in JSON model files ([#154](https://github.com/rojo-rbx/rojo/pull/154))
|
||||
* `Content` propertyes can now be specified in projects and model files as regular string literals.
|
||||
* Added support for `BrickColor` properties.
|
||||
* Added support for properties added in client release 384, like `Lighting.Technology` being set to `"ShadowMap"`.
|
||||
* Improved performance when working with XML models and places
|
||||
* Fixed serializing empty `Content` properties as XML
|
||||
* Fixed serializing infinite and NaN floating point properties in XML
|
||||
* Improved compatibility with XML models
|
||||
* Plugin should now be able to live-sync more properties, and ignore ones it can't, like `Lighting.Technology`.
|
||||
|
||||
## 0.5.0 Alpha 10
|
||||
* This release was a dud due to [issue #176](https://github.com/rojo-rbx/rojo/issues/176) and was rolled back.
|
||||
|
||||
## [0.5.0 Alpha 9](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.9) (April 4, 2019)
|
||||
* Changed `rojo build` to use buffered I/O, which can make it up to 2x faster in some cases.
|
||||
* Building [*Road Not Taken*](https://github.com/rojo-rbx/roads) to an `rbxlx` file dropped from 150ms to 70ms on my machine
|
||||
* Fixed `LocalizationTable` instances being made from `csv` files incorrectly interpreting empty rows and columns. ([#149](https://github.com/rojo-rbx/rojo/pull/149))
|
||||
* Fixed CSV files with entries that parse as numbers causing Rojo to panic. ([#152](https://github.com/rojo-rbx/rojo/pull/152))
|
||||
* Improved error messages when malformed CSV files are found in a Rojo project.
|
||||
|
||||
## [0.5.0 Alpha 8](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.8) (March 29, 2019)
|
||||
* Added support for a bunch of new types when dealing with XML model/place files:
|
||||
* `ColorSequence`
|
||||
* `Float64`
|
||||
* `Int64`
|
||||
* `NumberRange`
|
||||
* `NumberSequence`
|
||||
* `PhysicalProperties`
|
||||
* `Ray`
|
||||
* `Rect`
|
||||
* `Ref`
|
||||
* Improved server instance ordering behavior when files are added during a live session ([#135](https://github.com/rojo-rbx/rojo/pull/135))
|
||||
* Fixed error being thrown when trying to unload the Rojo plugin.
|
||||
* Added partial fix for [issue #141](https://github.com/rojo-rbx/rojo/issues/141) for `Lighting.Technology`, which should restore live sync functionality for the default project file.
|
||||
|
||||
## [0.5.0 Alpha 6](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.6) (March 19, 2019)
|
||||
* Fixed `rojo init` giving unexpected results by upgrading to `rbx_dom_weak` 1.1.0
|
||||
* Fixed live server not responding when the Rojo plugin is connected ([#133](https://github.com/rojo-rbx/rojo/issues/133))
|
||||
* Updated default place file:
|
||||
* Improved default properties to be closer to Studio's built-in 'Baseplate' template
|
||||
* Added a baseplate to the project file (Thanks, [@AmaranthineCodices](https://github.com/AmaranthineCodices/)!)
|
||||
* Added more type support to Rojo plugin
|
||||
* Fixed some cases where the Rojo plugin would leave around objects that it knows should be deleted
|
||||
* Updated plugin to correctly listen to `Plugin.Unloading` when installing or uninstalling new plugins
|
||||
|
||||
## [0.5.0 Alpha 5](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.5) (March 1, 2019)
|
||||
* Upgraded core dependencies, which improves compatibility for lots of instance types
|
||||
* Upgraded from `rbx_tree` 0.2.0 to `rbx_dom_weak` 1.0.0
|
||||
* Upgraded from `rbx_xml` 0.2.0 to `rbx_xml` 0.4.0
|
||||
* Upgraded from `rbx_binary` 0.2.0 to `rbx_binary` 0.4.0
|
||||
* Added support for non-primitive types in the Rojo plugin.
|
||||
* Types like `Color3` and `CFrame` can now be updated live!
|
||||
* Fixed plugin assets flashing in on first load ([#121](https://github.com/LPGhatguy/rojo/issues/121))
|
||||
* Fixed plugin assets flashing in on first load ([#121](https://github.com/rojo-rbx/rojo/issues/121))
|
||||
* Changed Rojo's HTTP server from Rouille to Hyper, which reduced the release size by around a megabyte.
|
||||
* Added property type inference to projects, which makes specifying services a lot easier ([#130](https://github.com/LPGhatguy/rojo/pull/130))
|
||||
* Added property type inference to projects, which makes specifying services a lot easier ([#130](https://github.com/rojo-rbx/rojo/pull/130))
|
||||
* Made error messages from invalid and missing files more user-friendly
|
||||
|
||||
## [0.5.0 Alpha 4](https://github.com/LPGhatguy/rojo/releases/tag/v0.5.0-alpha.4) (February 8, 2019)
|
||||
* Added support for nested partitions ([#102](https://github.com/LPGhatguy/rojo/issues/102))
|
||||
* Added support for 'transmuting' partitions ([#112](https://github.com/LPGhatguy/rojo/issues/112))
|
||||
* Added support for aliasing filesystem paths ([#105](https://github.com/LPGhatguy/rojo/issues/105))
|
||||
* Changed Windows builds to statically link the CRT ([#89](https://github.com/LPGhatguy/rojo/issues/89))
|
||||
## [0.5.0 Alpha 4](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.4) (February 8, 2019)
|
||||
* Added support for nested partitions ([#102](https://github.com/rojo-rbx/rojo/issues/102))
|
||||
* Added support for 'transmuting' partitions ([#112](https://github.com/rojo-rbx/rojo/issues/112))
|
||||
* Added support for aliasing filesystem paths ([#105](https://github.com/rojo-rbx/rojo/issues/105))
|
||||
* Changed Windows builds to statically link the CRT ([#89](https://github.com/rojo-rbx/rojo/issues/89))
|
||||
|
||||
## [0.5.0 Alpha 3](https://github.com/LPGhatguy/rojo/releases/tag/v0.5.0-alpha.3) (February 1, 2019)
|
||||
* Changed default project file name from `roblox-project.json` to `default.project.json` ([#120](https://github.com/LPGhatguy/rojo/pull/120))
|
||||
## [0.5.0 Alpha 3](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.3) (February 1, 2019)
|
||||
* Changed default project file name from `roblox-project.json` to `default.project.json` ([#120](https://github.com/rojo-rbx/rojo/pull/120))
|
||||
* The old file name will still be supported until 0.5.0 is fully released.
|
||||
* Added warning when loading project files that don't end in `.project.json`
|
||||
* This new extension enables Rojo to distinguish project files from random JSON files, which is necessary to support nested projects.
|
||||
* Added new (empty) diagnostic page served from the server
|
||||
* Added better error messages for when a file is missing that's referenced by a Rojo project
|
||||
* Added support for visualization endpoints returning GraphViz source when Dot is not available
|
||||
* Fixed an in-memory filesystem regression introduced recently ([#119](https://github.com/LPGhatguy/rojo/pull/119))
|
||||
* Fixed an in-memory filesystem regression introduced recently ([#119](https://github.com/rojo-rbx/rojo/pull/119))
|
||||
|
||||
## [0.5.0 Alpha 2](https://github.com/LPGhatguy/rojo/releases/tag/v0.5.0-alpha.2) (January 28, 2019)
|
||||
## [0.5.0 Alpha 2](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.2) (January 28, 2019)
|
||||
* Added support for `.model.json` files, compatible with 0.4.x
|
||||
* Fixed in-memory filesystem not handling out-of-order filesystem change events
|
||||
* Fixed long-polling error caused by a promise mixup ([#110](https://github.com/LPGhatguy/rojo/issues/110))
|
||||
* Fixed long-polling error caused by a promise mixup ([#110](https://github.com/rojo-rbx/rojo/issues/110))
|
||||
|
||||
## [0.5.0 Alpha 1](https://github.com/LPGhatguy/rojo/releases/tag/v0.5.0-alpha.1) (January 25, 2019)
|
||||
## [0.5.0 Alpha 1](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.1) (January 25, 2019)
|
||||
* Changed plugin UI to be way prettier
|
||||
* Thanks to [Reselim](https://github.com/Reselim) for the design!
|
||||
* Changed plugin error messages to be a little more useful
|
||||
@@ -43,7 +185,7 @@
|
||||
* Fixed bug where bad server responses could cause the plugin to be in a bad state
|
||||
* Upgraded to rbx\_tree, rbx\_xml, and rbx\_binary 0.2.0, which dramatically expands the kinds of properties that Rojo can handle, especially in XML.
|
||||
|
||||
## [0.5.0 Alpha 0](https://github.com/LPGhatguy/rojo/releases/tag/v0.5.0-alpha.0) (January 14, 2019)
|
||||
## [0.5.0 Alpha 0](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.0) (January 14, 2019)
|
||||
* "Epiphany" rewrite, in progress since the beginning of time
|
||||
* New live sync protocol
|
||||
* Uses HTTP long polling to reduce request count and improve responsiveness
|
||||
@@ -68,105 +210,105 @@
|
||||
* Multiple places can be specified, like when building a multi-place game
|
||||
* Added support for specifying properties on services in project files
|
||||
|
||||
## [0.4.13](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.13) (November 12, 2018)
|
||||
## [0.4.13](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.13) (November 12, 2018)
|
||||
* When `rojo.json` points to a file or directory that does not exist, Rojo now issues a warning instead of throwing an error and exiting
|
||||
|
||||
## [0.4.12](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.12) (June 21, 2018)
|
||||
* Fixed obscure assertion failure when renaming or deleting files ([#78](https://github.com/LPGhatguy/rojo/issues/78))
|
||||
* Added a `PluginAction` for the sync in command, which should help with some automation scripts ([#80](https://github.com/LPGhatguy/rojo/pull/80))
|
||||
## [0.4.12](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.12) (June 21, 2018)
|
||||
* Fixed obscure assertion failure when renaming or deleting files ([#78](https://github.com/rojo-rbx/rojo/issues/78))
|
||||
* Added a `PluginAction` for the sync in command, which should help with some automation scripts ([#80](https://github.com/rojo-rbx/rojo/pull/80))
|
||||
|
||||
## [0.4.11](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.11) (June 10, 2018)
|
||||
## [0.4.11](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.11) (June 10, 2018)
|
||||
* Defensively insert existing instances into RouteMap; should fix most duplication cases when syncing into existing trees.
|
||||
* Fixed incorrect synchronization from `Plugin:_pull` that would cause polling to create issues
|
||||
* Fixed incorrect file routes being assigned to `init.lua` and `init.model.json` files
|
||||
* Untangled route handling-internals slightly
|
||||
|
||||
## [0.4.10](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.10) (June 2, 2018)
|
||||
* Added support for `init.model.json` files, which enable versioning `Tool` instances (among other things) with Rojo. ([#66](https://github.com/LPGhatguy/rojo/issues/66))
|
||||
## [0.4.10](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.10) (June 2, 2018)
|
||||
* Added support for `init.model.json` files, which enable versioning `Tool` instances (among other things) with Rojo. ([#66](https://github.com/rojo-rbx/rojo/issues/66))
|
||||
* Fixed obscure error when syncing into an invalid service.
|
||||
* Fixed multiple sync processes occurring when a server ID mismatch is detected.
|
||||
|
||||
## [0.4.9](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.9) (May 26, 2018)
|
||||
* Fixed warning when renaming or removing files that would sometimes corrupt the instance cache ([#72](https://github.com/LPGhatguy/rojo/pull/72))
|
||||
## [0.4.9](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.9) (May 26, 2018)
|
||||
* Fixed warning when renaming or removing files that would sometimes corrupt the instance cache ([#72](https://github.com/rojo-rbx/rojo/pull/72))
|
||||
* JSON models are no longer as strict -- `Children` and `Properties` are now optional.
|
||||
|
||||
## [0.4.8](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.8) (May 26, 2018)
|
||||
## [0.4.8](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.8) (May 26, 2018)
|
||||
* Hotfix to prevent errors from being thrown when objects managed by Rojo are deleted
|
||||
|
||||
## [0.4.7](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.7) (May 25, 2018)
|
||||
* Added icons to the Rojo plugin, made by [@Vorlias](https://github.com/Vorlias)! ([#70](https://github.com/LPGhatguy/rojo/pull/70))
|
||||
* Server will now issue a warning if no partitions are specified in `rojo serve` ([#40](https://github.com/LPGhatguy/rojo/issues/40))
|
||||
## [0.4.7](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.7) (May 25, 2018)
|
||||
* Added icons to the Rojo plugin, made by [@Vorlias](https://github.com/Vorlias)! ([#70](https://github.com/rojo-rbx/rojo/pull/70))
|
||||
* Server will now issue a warning if no partitions are specified in `rojo serve` ([#40](https://github.com/rojo-rbx/rojo/issues/40))
|
||||
|
||||
## [0.4.6](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.6) (May 21, 2018)
|
||||
* Rojo handles being restarted by Roblox Studio more gracefully ([#67](https://github.com/LPGhatguy/rojo/issues/67))
|
||||
## [0.4.6](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.6) (May 21, 2018)
|
||||
* Rojo handles being restarted by Roblox Studio more gracefully ([#67](https://github.com/rojo-rbx/rojo/issues/67))
|
||||
* Folders should no longer get collapsed when syncing occurs.
|
||||
* **Significant** robustness improvements with regards to caching.
|
||||
* **This should catch all existing script duplication bugs.**
|
||||
* If there are any bugs with script duplication or caching in the future, restarting the Rojo server process will fix them for that session.
|
||||
* Fixed message in plugin not being prefixed with `Rojo: `.
|
||||
|
||||
## [0.4.5](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.5) (May 1, 2018)
|
||||
## [0.4.5](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.5) (May 1, 2018)
|
||||
* Rojo messages are now prefixed with `Rojo: ` to make them stand out in the output more.
|
||||
* Fixed server to notice file changes *much* more quickly. (200ms vs 1000ms)
|
||||
* Server now lists name of project when starting up.
|
||||
* Rojo now throws an error if no project file is found. ([#63](https://github.com/LPGhatguy/rojo/issues/63))
|
||||
* Fixed multiple sync operations occuring at the same time. ([#61](https://github.com/LPGhatguy/rojo/issues/61))
|
||||
* Partitions targeting files directly now work as expected. ([#57](https://github.com/LPGhatguy/rojo/issues/57))
|
||||
* Rojo now throws an error if no project file is found. ([#63](https://github.com/rojo-rbx/rojo/issues/63))
|
||||
* Fixed multiple sync operations occuring at the same time. ([#61](https://github.com/rojo-rbx/rojo/issues/61))
|
||||
* Partitions targeting files directly now work as expected. ([#57](https://github.com/rojo-rbx/rojo/issues/57))
|
||||
|
||||
## [0.4.4](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.4) (April 7, 2018)
|
||||
## [0.4.4](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.4) (April 7, 2018)
|
||||
* Fix small regression introduced in 0.4.3
|
||||
|
||||
## [0.4.3](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.3) (April 7, 2018)
|
||||
* Plugin now automatically selects `HttpService` if it determines that HTTP isn't enabled ([#58](https://github.com/LPGhatguy/rojo/pull/58))
|
||||
## [0.4.3](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.3) (April 7, 2018)
|
||||
* Plugin now automatically selects `HttpService` if it determines that HTTP isn't enabled ([#58](https://github.com/rojo-rbx/rojo/pull/58))
|
||||
* Plugin now has much more robust handling and will wipe all state when the server changes.
|
||||
* This should fix issues that would otherwise be solved by restarting Roblox Studio.
|
||||
|
||||
## [0.4.2](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.2) (April 4, 2018)
|
||||
## [0.4.2](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.2) (April 4, 2018)
|
||||
* Fixed final case of duplicated instance insertion, caused by reconciled instances not being inserted into `RouteMap`.
|
||||
* The reconciler is still not a perfect solution, especially if script instances get moved around without being destroyed. I don't think this can be fixed before a big refactor.
|
||||
|
||||
## [0.4.1](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.1) (April 1, 2018)
|
||||
## [0.4.1](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.1) (April 1, 2018)
|
||||
* Merged plugin repository into main Rojo repository for easier tracking.
|
||||
* Improved `RouteMap` object tracking; this should fix some cases of duplicated instances being synced into the tree.
|
||||
|
||||
## [0.4.0](https://github.com/LPGhatguy/rojo/releases/tag/v0.4.0) (March 27, 2018)
|
||||
## [0.4.0](https://github.com/rojo-rbx/rojo/releases/tag/v0.4.0) (March 27, 2018)
|
||||
* Protocol version 1, which shifts more responsibility onto the server
|
||||
* This is a **major breaking** change!
|
||||
* The server now has a content of 'filter plugins', which transform data at various stages in the pipeline
|
||||
* The server now exposes Roblox instance objects instead of file contents, which lines up with how `rojo pack` will work, and paves the way for more robust syncing.
|
||||
* Added `*.model.json` files, which let you embed small Roblox objects into your Rojo tree.
|
||||
* Improved error messages in some cases ([#46](https://github.com/LPGhatguy/rojo/issues/46))
|
||||
* Improved error messages in some cases ([#46](https://github.com/rojo-rbx/rojo/issues/46))
|
||||
|
||||
## [0.3.2](https://github.com/LPGhatguy/rojo/releases/tag/v0.3.2) (December 20, 2017)
|
||||
## [0.3.2](https://github.com/rojo-rbx/rojo/releases/tag/v0.3.2) (December 20, 2017)
|
||||
* Fixed `rojo serve` failing to correctly construct an absolute root path when passed as an argument
|
||||
* Fixed intense CPU usage when running `rojo serve`
|
||||
|
||||
## [0.3.1](https://github.com/LPGhatguy/rojo/releases/tag/v0.3.1) (December 14, 2017)
|
||||
## [0.3.1](https://github.com/rojo-rbx/rojo/releases/tag/v0.3.1) (December 14, 2017)
|
||||
* Improved error reporting when invalid JSON is found in a `rojo.json` project
|
||||
* These messages are passed on from Serde
|
||||
|
||||
## [0.3.0](https://github.com/LPGhatguy/rojo/releases/tag/v0.3.0) (December 12, 2017)
|
||||
## [0.3.0](https://github.com/rojo-rbx/rojo/releases/tag/v0.3.0) (December 12, 2017)
|
||||
* Factored out the plugin into a separate repository
|
||||
* Fixed server when using a file as a partition
|
||||
* Previously, trailing slashes were put on the end of a partition even if the read request was an empty string. This broke file reading on Windows when a partition pointed to a file instead of a directory!
|
||||
* Started running automatic tests on Travis CI (#9)
|
||||
|
||||
## [0.2.3](https://github.com/LPGhatguy/rojo/releases/tag/v0.2.3) (December 4, 2017)
|
||||
## [0.2.3](https://github.com/rojo-rbx/rojo/releases/tag/v0.2.3) (December 4, 2017)
|
||||
* 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](https://github.com/LPGhatguy/rojo/releases/tag/v0.2.2) (December 1, 2017)
|
||||
## [0.2.2](https://github.com/rojo-rbx/rojo/releases/tag/v0.2.2) (December 1, 2017)
|
||||
* Plugin only release
|
||||
* Fixed broken reconciliation behavior with `init` files
|
||||
|
||||
## [0.2.1](https://github.com/LPGhatguy/rojo/releases/tag/v0.2.1) (December 1, 2017)
|
||||
## [0.2.1](https://github.com/rojo-rbx/rojo/releases/tag/v0.2.1) (December 1, 2017)
|
||||
* Plugin only release
|
||||
* Changes default port to 8000
|
||||
|
||||
## [0.2.0](https://github.com/LPGhatguy/rojo/releases/tag/v0.2.0) (December 1, 2017)
|
||||
## [0.2.0](https://github.com/rojo-rbx/rojo/releases/tag/v0.2.0) (December 1, 2017)
|
||||
* Support for `init.lua` like rbxfs and rbxpacker
|
||||
* More robust syncing with a new reconciler
|
||||
|
||||
## [0.1.0](https://github.com/LPGhatguy/rojo/releases/tag/v0.1.0) (November 29, 2017)
|
||||
* Initial release, functionally very similar to [rbxfs](https://github.com/LPGhatguy/rbxfs)
|
||||
## [0.1.0](https://github.com/rojo-rbx/rojo/releases/tag/v0.1.0) (November 29, 2017)
|
||||
* Initial release, functionally very similar to [rbxfs](https://github.com/rojo-rbx/rbxfs)
|
||||
54
CONTRIBUTING.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Contributing to the Rojo Project
|
||||
Rojo is a big project and can always use more help! This guide covers all repositories underneath the [rojo-rbx organization on GitHub](https://github.com/rojo-rbx).
|
||||
|
||||
Some of the repositories covered are:
|
||||
|
||||
* https://github.com/rojo-rbx/rojo
|
||||
* https://github.com/rojo-rbx/rbx-dom
|
||||
* https://github.com/rojo-rbx/vscode-rojo
|
||||
* https://github.com/rojo-rbx/rbxlx-to-rojo
|
||||
|
||||
## Code
|
||||
Code contributions are welcome for features and bugs that have been reported in the project's bug tracker. We want to make sure that no one wastes their time, so be sure to talk with maintainers about what changes would be accepted before doing any work!
|
||||
|
||||
You'll want these tools to work on Rojo:
|
||||
|
||||
* Latest stable Rust compiler
|
||||
* Latest stable [Rojo](https://github.com/rojo-rbx/rojo)
|
||||
* Latest stable [Remodel](https://github.com/rojo-rbx/remodel)
|
||||
* Latest stable [run-in-roblox](https://github.com/rojo-rbx/run-in-roblox)
|
||||
|
||||
## Documentation
|
||||
Documentation impacts way more people than the individual lines of code we write.
|
||||
|
||||
If you find any problems in documentation, including typos, bad grammar, misleading phrasing, or missing content, feel free to file issues and pull requests to fix them.
|
||||
|
||||
## Bug Reports and Feature Requests
|
||||
Most of the tools around Rojo try to be clear when an issue is a bug. Even if they aren't, sometimes things don't work quite right.
|
||||
|
||||
Sometimes there's something that Rojo doesn't do that it probably should.
|
||||
|
||||
Please file issues and we'll try to help figure out what the best way forward is.
|
||||
|
||||
## Pushing a Rojo Release
|
||||
The Rojo release process is pretty manual right now. If you need to do it, here's how:
|
||||
|
||||
1. Bump server version in [`Cargo.toml`](Cargo.toml)
|
||||
2. Bump plugin version in [`plugin/src/Config.lua`](plugin/src/Config.lua)
|
||||
3. Run `cargo test` to update `Cargo.lock` and double-check tests
|
||||
4. Update [`CHANGELOG.md`](CHANGELOG.md)
|
||||
5. Commit!
|
||||
* `git add . && git commit -m "Release vX.Y.Z"`
|
||||
6. Tag the commit with the version from `Cargo.toml` prepended with a v, like `v0.4.13`
|
||||
7. Publish the CLI
|
||||
* `cargo publish`
|
||||
8. Build and upload the plugin
|
||||
* `rojo build plugin -o Rojo.rbxm`
|
||||
* Upload `Rojo.rbxm` to Roblox.com, keep it for later
|
||||
9. Push commits and tags
|
||||
* `git push && git push --tags`
|
||||
10. Copy GitHub release content from previous release
|
||||
* Update the leading text with a summary about the release
|
||||
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md)
|
||||
* Write a small summary of each major feature
|
||||
* Attach release artifacts from GitHub Actions for each platform
|
||||
2354
Cargo.lock
generated
107
Cargo.toml
@@ -1,5 +1,108 @@
|
||||
[package]
|
||||
name = "rojo"
|
||||
version = "0.6.0-alpha.3"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
description = "Enables professional-grade development tools for Roblox developers"
|
||||
license = "MPL-2.0"
|
||||
homepage = "https://rojo.space"
|
||||
documentation = "https://rojo.space/docs"
|
||||
repository = "https://github.com/rojo-rbx/rojo"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
exclude = [
|
||||
"/plugin/**",
|
||||
"/test-projects/**",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# Turn on support for specifying glob ignore path rules in the project format.
|
||||
unstable_glob_ignore_paths = []
|
||||
|
||||
# Enable this feature to live-reload assets from the web UI.
|
||||
dev_live_assets = []
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"server",
|
||||
"rojo-e2e",
|
||||
"rojo-test",
|
||||
"rojo-insta-ext",
|
||||
"clibrojo",
|
||||
"memofs",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
".",
|
||||
"rojo-test",
|
||||
"rojo-insta-ext",
|
||||
"memofs",
|
||||
]
|
||||
|
||||
[lib]
|
||||
name = "librojo"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "rojo"
|
||||
path = "src/bin.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "build"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
memofs = { version = "0.1.1", path = "memofs" }
|
||||
|
||||
anyhow = "1.0.27"
|
||||
backtrace = "0.3"
|
||||
crossbeam-channel = "0.4.0"
|
||||
csv = "1.1.1"
|
||||
env_logger = "0.7.1"
|
||||
fs-err = "2.2.0"
|
||||
futures = "0.1.29"
|
||||
globset = "0.4.4"
|
||||
humantime = "1.3.0"
|
||||
hyper = "0.12.35"
|
||||
jod-thread = "0.1.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.8"
|
||||
maplit = "1.0.1"
|
||||
notify = "4.0.14"
|
||||
opener = "0.4.1"
|
||||
rbx_binary = "0.5.0"
|
||||
rbx_dom_weak = "1.10.1"
|
||||
rbx_reflection = "3.3.408"
|
||||
rbx_xml = "0.11.3"
|
||||
regex = "1.3.1"
|
||||
reqwest = "0.9.20"
|
||||
ritz = "0.1.0"
|
||||
rlua = "0.17.0"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
structopt = "0.3.5"
|
||||
termcolor = "1.0.5"
|
||||
thiserror = "1.0.11"
|
||||
tokio = "0.1.22"
|
||||
uuid = { version = "0.8.1", features = ["v4", "serde"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.6.2"
|
||||
|
||||
[dev-dependencies]
|
||||
rojo-insta-ext = { path = "rojo-insta-ext" }
|
||||
|
||||
criterion = "0.3"
|
||||
insta = { version = "0.13.1", features = ["redactions"] }
|
||||
lazy_static = "1.2"
|
||||
paste = "0.1"
|
||||
pretty_assertions = "0.6.1"
|
||||
serde_yaml = "0.8.9"
|
||||
tempfile = "3.0"
|
||||
walkdir = "2.1"
|
||||
|
||||
62
README.md
@@ -1,68 +1,54 @@
|
||||
<div align="center">
|
||||
<img src="assets/rojo-logo.png" alt="Rojo" height="217" />
|
||||
<a href="https://rojo.space">
|
||||
<img src="assets/logo-512.png" alt="Rojo" height="217" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div> </div>
|
||||
|
||||
<div align="center">
|
||||
<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" />
|
||||
<a href="https://github.com/rojo-rbx/rojo/actions">
|
||||
<img src="https://github.com/rojo-rbx/rojo/workflows/CI/badge.svg" alt="Actions status" />
|
||||
</a>
|
||||
<a href="https://crates.io/crates/rojo">
|
||||
<img src="https://img.shields.io/crates/v/rojo.svg?label=version" alt="Latest server version" />
|
||||
<img src="https://img.shields.io/crates/v/rojo.svg?label=latest%20release" alt="Latest server version" />
|
||||
</a>
|
||||
<a href="https://lpghatguy.github.io/rojo/0.4.x">
|
||||
<img src="https://img.shields.io/badge/docs-0.4.x-brightgreen.svg" alt="Rojo Documentation" />
|
||||
</a>
|
||||
<a href="https://lpghatguy.github.io/rojo/0.5.x">
|
||||
<img src="https://img.shields.io/badge/docs-0.5.x-brightgreen.svg" alt="Rojo Documentation" />
|
||||
<a href="https://rojo.space/docs">
|
||||
<img src="https://img.shields.io/badge/docs-website-brightgreen.svg" alt="Rojo 0.5.x Documentation" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
**Rojo** is a flexible multi-tool designed for creating robust Roblox projects.
|
||||
**Rojo** is a tool designed to enable Roblox developers to use professional-grade software engineering tools.
|
||||
|
||||
It lets Roblox developers use industry-leading tools like Git and VS Code, and crucial utilities like Luacheck.
|
||||
With Rojo, it's possible to use industry-leading tools like **Visual Studio Code** and **Git**.
|
||||
|
||||
Rojo is designed for **power users** who want to use the **best tools available** for building games, libraries, and plugins.
|
||||
Rojo is designed for power users who want to use the best tools available for building games, libraries, and plugins.
|
||||
|
||||
## Features
|
||||
Rojo lets you:
|
||||
Rojo enables:
|
||||
|
||||
* Work on scripts from the filesystem, in your favorite editor
|
||||
* Version your place, model, or plugin using Git or another VCS
|
||||
* Sync `rbxmx` and `rbxm` models into your game in real time
|
||||
* Package and deploy your project to Roblox.com from the command line
|
||||
* Working on scripts and models from the filesystem, in your favorite editor
|
||||
* Versioning your game, library, or plugin using Git or another VCS
|
||||
* Streaming `rbxmx` and `rbxm` models into your game in real time
|
||||
* Packaging and deploying your project to Roblox.com from the command line
|
||||
|
||||
Soon, Rojo will be able to:
|
||||
In the future, Rojo will be able to:
|
||||
|
||||
* Sync instances from Roblox Studio to the filesystem
|
||||
* Compile MoonScript and other custom things for your project
|
||||
* Automatically convert your existing game to work with Rojo
|
||||
* Import custom instances like MoonScript code
|
||||
|
||||
## [Documentation](https://lpghatguy.github.io/rojo)
|
||||
You can also view the documentation by browsing the [docs](https://github.com/LPGhatguy/rojo/tree/master/docs) folder of the repository, but because it uses a number of Markdown extensions, it may not be very readable.
|
||||
|
||||
## Inspiration and Alternatives
|
||||
There are lots of other tools that sync scripts into Roblox or provide other tools for working with Roblox places.
|
||||
|
||||
Here are a few, if you're looking for alternatives or supplements to Rojo:
|
||||
|
||||
* [rbxmk by Anaminus](https://github.com/anaminus/rbxmk)
|
||||
* [Rofresh by Osyris](https://github.com/osyrisrblx/rofresh)
|
||||
* [RbxRefresh by Osyris](https://github.com/osyrisrblx/RbxRefresh)
|
||||
* [Studio Bridge by Vocksel](https://github.com/vocksel/studio-bridge)
|
||||
* [Elixir by Vocksel](https://github.com/vocksel/elixir)
|
||||
* [RbxSync by evaera](https://github.com/evaera/RbxSync)
|
||||
* [CodeSync by MemoryPenguin](https://github.com/MemoryPenguin/CodeSync)
|
||||
* [rbx-exteditor by MemoryPenguin](https://github.com/MemoryPenguin/rbx-exteditor)
|
||||
|
||||
If you use a plugin that _isn't_ Rojo for syncing code, open an issue and let me know why! I'd like Rojo to be the end-all tool so that people stop reinventing solutions to this problem.
|
||||
## [Documentation](https://rojo.space/docs)
|
||||
Documentation is hosted in the [rojo.space repository](https://github.com/rojo-rbx/rojo.space).
|
||||
|
||||
## Contributing
|
||||
Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions for helping work on Rojo!
|
||||
|
||||
Pull requests are welcome!
|
||||
|
||||
Rojo supports Rust 1.32 and newer. Any changes to the minimum required compiler version require a _minor_ version bump.
|
||||
Rojo supports Rust 1.40.0 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
||||
|
||||
## License
|
||||
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
||||
11
assets/default-model-project/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# {project_name}
|
||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
||||
|
||||
## Getting Started
|
||||
To build this library or plugin, use:
|
||||
|
||||
```bash
|
||||
rojo build -o "{project_name}.rbxmx"
|
||||
```
|
||||
|
||||
For more help, check out [the Rojo documentation](https://rojo.space/docs).
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "malformed-stuff",
|
||||
"name": "{project_name}",
|
||||
"tree": {
|
||||
"$path": "src"
|
||||
}
|
||||
3
assets/default-model-project/gitignore.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# Roblox Studio lock files
|
||||
/*.rbxlx.lock
|
||||
/*.rbxl.lock
|
||||
5
assets/default-model-project/src-init.lua
Normal file
@@ -0,0 +1,5 @@
|
||||
return {
|
||||
hello = function()
|
||||
print("Hello world, from {project_name}!")
|
||||
end,
|
||||
}
|
||||
17
assets/default-place-project/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# {project_name}
|
||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
||||
|
||||
## Getting Started
|
||||
To build the place from scratch, use:
|
||||
|
||||
```bash
|
||||
rojo build -o "{project_name}.rbxlx"
|
||||
```
|
||||
|
||||
Next, open `{project_name}.rbxlx` in Roblox Studio and start the Rojo server:
|
||||
|
||||
```bash
|
||||
rojo serve
|
||||
```
|
||||
|
||||
For more help, check out [the Rojo documentation](https://rojo.space/docs).
|
||||
89
assets/default-place-project/default.project.json
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"name": "{project_name}",
|
||||
"tree": {
|
||||
"$className": "DataModel",
|
||||
|
||||
"ReplicatedStorage": {
|
||||
"$className": "ReplicatedStorage",
|
||||
|
||||
"Common": {
|
||||
"$path": "src/common"
|
||||
}
|
||||
},
|
||||
|
||||
"ServerScriptService": {
|
||||
"$className": "ServerScriptService",
|
||||
|
||||
"Server": {
|
||||
"$path": "src/server"
|
||||
}
|
||||
},
|
||||
|
||||
"StarterPlayer": {
|
||||
"$className": "StarterPlayer",
|
||||
|
||||
"StarterPlayerScripts": {
|
||||
"$className": "StarterPlayerScripts",
|
||||
|
||||
"Client": {
|
||||
"$path": "src/client"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"Workspace": {
|
||||
"$className": "Workspace",
|
||||
"$properties": {
|
||||
"FilteringEnabled": true
|
||||
},
|
||||
"Baseplate": {
|
||||
"$className": "Part",
|
||||
"$properties": {
|
||||
"Anchored": true,
|
||||
"Color": [
|
||||
0.38823,
|
||||
0.37254,
|
||||
0.38823
|
||||
],
|
||||
"Locked": true,
|
||||
"Position": [
|
||||
0,
|
||||
-10,
|
||||
0
|
||||
],
|
||||
"Size": [
|
||||
512,
|
||||
20,
|
||||
512
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Lighting": {
|
||||
"$className": "Lighting",
|
||||
"$properties": {
|
||||
"Ambient": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"Brightness": 2,
|
||||
"GlobalShadows": true,
|
||||
"Outlines": false,
|
||||
"Technology": "Voxel"
|
||||
}
|
||||
},
|
||||
"SoundService": {
|
||||
"$className": "SoundService",
|
||||
"$properties": {
|
||||
"RespectFilteringEnabled": true
|
||||
}
|
||||
},
|
||||
"HttpService": {
|
||||
"$className": "HttpService",
|
||||
"$properties": {
|
||||
"HttpEnabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
assets/default-place-project/gitignore.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# Project place file
|
||||
/{project_name}.rbxlx
|
||||
|
||||
# Roblox Studio lock files
|
||||
/*.rbxlx.lock
|
||||
/*.rbxl.lock
|
||||
BIN
assets/icon-32.png
Normal file
|
After Width: | Height: | Size: 975 B |
181
assets/index.css
Normal file
@@ -0,0 +1,181 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
text-decoration: inherit;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
box-sizing: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
font-family: sans-serif;
|
||||
font-size: 18px;
|
||||
text-decoration: none;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width:100%;
|
||||
max-height:100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.path-list > li {
|
||||
margin-left: 1.2em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0.5rem auto;
|
||||
width: 100%;
|
||||
max-width: 50rem;
|
||||
background-color: #efefef;
|
||||
border: 1px solid #666;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.header {
|
||||
flex: 0 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #666;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.main-logo {
|
||||
flex: 0 0 10rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.stats {
|
||||
flex: 0 0 20rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.stat {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stat-name {
|
||||
display: inline;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.main-section:not(:last-of-type) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.button-list {
|
||||
flex: 0 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
margin: -1rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
border: 1px solid #666;
|
||||
padding: 0.3em 1em;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.instance {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.instance-title {
|
||||
font-size: 1.2rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.expandable-section {
|
||||
margin: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.expandable-items {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.expandable-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.expandable-label > label {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.expandable-input ~ .expandable-label .expandable-visualizer {
|
||||
font-family: monospace;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
font-size: 2rem;
|
||||
margin: 0 0.5rem;
|
||||
transition: transform 100ms ease-in-out;
|
||||
transform-origin: 60% 60%;
|
||||
}
|
||||
|
||||
.expandable-visualizer::before {
|
||||
content: "›";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.expandable-input:checked ~ .expandable-label {
|
||||
border-bottom: 1px solid #bbb;
|
||||
}
|
||||
|
||||
.expandable-input:checked ~ .expandable-label .expandable-visualizer {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.expandable-input:not(:checked) ~ .expandable-items {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vfs-entry {
|
||||
}
|
||||
|
||||
.vfs-entry-name {
|
||||
position: relative;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.vfs-entry-children .vfs-entry-name::before {
|
||||
content: "";
|
||||
width: 0.6em;
|
||||
height: 1px;
|
||||
background-color: #999;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: -0.8em;
|
||||
}
|
||||
|
||||
.vfs-entry-note {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.vfs-entry-children {
|
||||
padding-left: 0.8em;
|
||||
margin-left: 0.2em;
|
||||
border-left: 1px solid #999;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 13 KiB |
@@ -1,41 +0,0 @@
|
||||
<TextureAtlas imagePath="sheet.png">
|
||||
<SubTexture name="grey_arrowDownGrey.png" x="78" y="498" width="15" height="10"/>
|
||||
<SubTexture name="grey_arrowDownWhite.png" x="123" y="496" width="15" height="10"/>
|
||||
<SubTexture name="grey_arrowUpGrey.png" x="108" y="498" width="15" height="10"/>
|
||||
<SubTexture name="grey_arrowUpWhite.png" x="93" y="498" width="15" height="10"/>
|
||||
<SubTexture name="grey_box.png" x="147" y="433" width="38" height="36"/>
|
||||
<SubTexture name="grey_boxCheckmark.png" x="147" y="469" width="38" height="36"/>
|
||||
<SubTexture name="grey_boxCross.png" x="185" y="433" width="38" height="36"/>
|
||||
<SubTexture name="grey_boxTick.png" x="190" y="198" width="36" height="36"/>
|
||||
<SubTexture name="grey_button00.png" x="0" y="143" width="190" height="45"/>
|
||||
<SubTexture name="grey_button01.png" x="0" y="188" width="190" height="49"/>
|
||||
<SubTexture name="grey_button02.png" x="0" y="98" width="190" height="45"/>
|
||||
<SubTexture name="grey_button03.png" x="0" y="331" width="190" height="49"/>
|
||||
<SubTexture name="grey_button04.png" x="0" y="286" width="190" height="45"/>
|
||||
<SubTexture name="grey_button05.png" x="0" y="0" width="195" height="49"/>
|
||||
<SubTexture name="grey_button06.png" x="0" y="49" width="191" height="49"/>
|
||||
<SubTexture name="grey_button07.png" x="195" y="0" width="49" height="49"/>
|
||||
<SubTexture name="grey_button08.png" x="240" y="49" width="49" height="49"/>
|
||||
<SubTexture name="grey_button09.png" x="98" y="433" width="49" height="45"/>
|
||||
<SubTexture name="grey_button10.png" x="191" y="49" width="49" height="49"/>
|
||||
<SubTexture name="grey_button11.png" x="0" y="433" width="49" height="45"/>
|
||||
<SubTexture name="grey_button12.png" x="244" y="0" width="49" height="49"/>
|
||||
<SubTexture name="grey_button13.png" x="49" y="433" width="49" height="45"/>
|
||||
<SubTexture name="grey_button14.png" x="0" y="384" width="190" height="49"/>
|
||||
<SubTexture name="grey_button15.png" x="0" y="237" width="190" height="49"/>
|
||||
<SubTexture name="grey_checkmarkGrey.png" x="99" y="478" width="21" height="20"/>
|
||||
<SubTexture name="grey_checkmarkWhite.png" x="78" y="478" width="21" height="20"/>
|
||||
<SubTexture name="grey_circle.png" x="185" y="469" width="36" height="36"/>
|
||||
<SubTexture name="grey_crossGrey.png" x="120" y="478" width="18" height="18"/>
|
||||
<SubTexture name="grey_crossWhite.png" x="190" y="318" width="18" height="18"/>
|
||||
<SubTexture name="grey_panel.png" x="190" y="98" width="100" height="100"/>
|
||||
<SubTexture name="grey_sliderDown.png" x="190" y="234" width="28" height="42"/>
|
||||
<SubTexture name="grey_sliderEnd.png" x="138" y="478" width="8" height="10"/>
|
||||
<SubTexture name="grey_sliderHorizontal.png" x="0" y="380" width="190" height="4"/>
|
||||
<SubTexture name="grey_sliderLeft.png" x="0" y="478" width="39" height="31"/>
|
||||
<SubTexture name="grey_sliderRight.png" x="39" y="478" width="39" height="31"/>
|
||||
<SubTexture name="grey_sliderUp.png" x="190" y="276" width="28" height="42"/>
|
||||
<SubTexture name="grey_sliderVertical.png" x="208" y="318" width="4" height="100"/>
|
||||
<SubTexture name="grey_tickGrey.png" x="190" y="336" width="17" height="17"/>
|
||||
<SubTexture name="grey_tickWhite.png" x="190" y="353" width="17" height="17"/>
|
||||
</TextureAtlas>
|
||||
BIN
assets/logo-512.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 375 B |
|
Before Width: | Height: | Size: 382 B |
|
Before Width: | Height: | Size: 430 B |
BIN
assets/test-folder.rbxm
Normal file
42
benches/build.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
use librojo::cli::{build, BuildCommand};
|
||||
|
||||
pub fn benchmark_small_place(c: &mut Criterion) {
|
||||
bench_build_place(c, "Small Place", "test-projects/benchmark_small_place")
|
||||
}
|
||||
|
||||
criterion_group!(benches, benchmark_small_place);
|
||||
criterion_main!(benches);
|
||||
|
||||
fn bench_build_place(c: &mut Criterion, name: &str, path: &str) {
|
||||
let mut group = c.benchmark_group(name);
|
||||
|
||||
// 'rojo build' generally takes a fair bit of time to execute.
|
||||
group.sample_size(10);
|
||||
group.bench_function("build", |b| {
|
||||
b.iter_batched(
|
||||
|| place_setup(path),
|
||||
|(_dir, options)| build(options).unwrap(),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn place_setup<P: AsRef<Path>>(input_path: P) -> (TempDir, BuildCommand) {
|
||||
let dir = tempdir().unwrap();
|
||||
let input = input_path.as_ref().to_path_buf();
|
||||
let output = dir.path().join("output.rbxlx");
|
||||
|
||||
let options = BuildCommand {
|
||||
project: input,
|
||||
output,
|
||||
};
|
||||
|
||||
(dir, options)
|
||||
}
|
||||
5
bin/dev-plugin.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
watchexec -c -w plugin "sh -c './bin/install-dev-plugin.sh'"
|
||||
13
bin/install-dev-plugin.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
DIR="$( mktemp -d )"
|
||||
PLUGIN_FILE="$DIR/Rojo.rbxm"
|
||||
TESTEZ_FILE="$DIR/TestEZ.rbxm"
|
||||
|
||||
rojo build plugin -o "$PLUGIN_FILE"
|
||||
rojo build plugin/testez.project.json -o "$TESTEZ_FILE"
|
||||
remodel bin/mark-plugin-as-dev.lua "$PLUGIN_FILE" "$TESTEZ_FILE" 2>/dev/null
|
||||
|
||||
cp "$PLUGIN_FILE" "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"
|
||||
5
bin/install-release-plugin.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
rojo build plugin -o "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"
|
||||
12
bin/mark-plugin-as-dev.lua
Normal file
@@ -0,0 +1,12 @@
|
||||
local pluginPath, testezPath = ...
|
||||
|
||||
local plugin = remodel.readModelFile(pluginPath)[1]
|
||||
local testez = remodel.readModelFile(testezPath)[1]
|
||||
|
||||
local marker = Instance.new("Folder")
|
||||
marker.Name = "ROJO_DEV_BUILD"
|
||||
marker.Parent = plugin
|
||||
|
||||
testez.Parent = plugin
|
||||
|
||||
remodel.writeModelFile(plugin, pluginPath)
|
||||
8
bin/put-plugin-in-test-place.lua
Normal file
@@ -0,0 +1,8 @@
|
||||
local pluginPath, placePath = ...
|
||||
|
||||
local plugin = remodel.readModelFile(pluginPath)[1]
|
||||
local place = remodel.readPlaceFile(placePath)
|
||||
|
||||
plugin.Parent = place:GetService("ReplicatedStorage")
|
||||
|
||||
remodel.writePlaceFile(place, placePath)
|
||||
6
bin/run-all-tests.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
./bin/run-cli-tests.sh
|
||||
./bin/run-plugin-tests.sh
|
||||
9
bin/run-cli-tests.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
cargo test --all --locked
|
||||
cargo fmt -- --check
|
||||
|
||||
touch src/lib.rs # Nudge Rust source to make Clippy actually check things
|
||||
cargo clippy
|
||||
16
bin/run-plugin-tests.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
DIR="$( mktemp -d )"
|
||||
PLUGIN_FILE="$DIR/Rojo.rbxmx"
|
||||
PLACE_FILE="$DIR/RojoTestPlace.rbxlx"
|
||||
|
||||
rojo build plugin -o "$PLUGIN_FILE"
|
||||
rojo build plugin/place.project.json -o "$PLACE_FILE"
|
||||
|
||||
remodel bin/put-plugin-in-test-place.lua "$PLUGIN_FILE" "$PLACE_FILE"
|
||||
|
||||
run-in-roblox -s plugin/testBootstrap.server.lua "$PLACE_FILE"
|
||||
|
||||
luacheck plugin/src plugin/log plugin/http
|
||||
13
clibrojo/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "clibrojo"
|
||||
version = "0.1.0"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rojo = { path = ".." }
|
||||
19
clibrojo/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Rojo as a C Library
|
||||
This is an experiment to expose a C API for Rojo that would be suitable for embedding it into an existing C/C++ application.
|
||||
|
||||
I'm hoping to expand it to drop the HTTP layer and communicate through a channel, which could make it feasible to embed into an existing Roblox IDE with minimal changes or additional code.
|
||||
|
||||
## Building
|
||||
This project is currently not built by default and could break/disappear at any time.
|
||||
|
||||
```bash
|
||||
cargo build -p clibrojo
|
||||
```
|
||||
|
||||
On Windows, Cargo will generate a `clibrojo.dll` and associated `.lib` file. Link these into your project.
|
||||
|
||||
To generate the associated C header file to include in the project, use [cbindgen](https://github.com/eqrion/cbindgen):
|
||||
|
||||
```bash
|
||||
cbindgen --crate clibrojo --output include/rojo.h
|
||||
```
|
||||
14
clibrojo/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use std::{ffi::CStr, os::raw::c_char, path::PathBuf};
|
||||
|
||||
use librojo::commands::{serve, ServeOptions};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rojo_serve(path: *const c_char) {
|
||||
let path = unsafe { PathBuf::from(CStr::from_ptr(path).to_str().unwrap()) };
|
||||
|
||||
serve(&ServeOptions {
|
||||
fuzzy_project_path: path,
|
||||
port: None,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
39
design.gv
@@ -1,39 +0,0 @@
|
||||
digraph G {
|
||||
graph [
|
||||
ranksep = "0.7",
|
||||
nodesep = "1.0",
|
||||
];
|
||||
node [
|
||||
fontname = "Hack",
|
||||
shape = "record",
|
||||
];
|
||||
|
||||
roblox_studio -> plugin [dir = "both"];
|
||||
plugin -> web_server [style = "dashed", dir = "both"];
|
||||
|
||||
web_server -> session;
|
||||
|
||||
session -> rbx_session;
|
||||
session -> fs_watcher;
|
||||
session -> message_queue;
|
||||
|
||||
fs_watcher -> imfs [weight = "10"];
|
||||
fs_watcher -> rbx_session [constraint = "false"];
|
||||
|
||||
imfs -> fs;
|
||||
|
||||
rbx_session -> imfs;
|
||||
rbx_session -> middlewares [weight = "10"];
|
||||
rbx_session -> message_queue [constraint = "false"];
|
||||
|
||||
plugin [label = "Studio Plugin"];
|
||||
roblox_studio [label = "Roblox Studio"];
|
||||
fs [label = "Filesystem"];
|
||||
fs_watcher [label = "Filesystem Watcher"];
|
||||
session [label = "Session"];
|
||||
web_server [label = "Web API"];
|
||||
imfs [label = "In-Memory Filesystem"];
|
||||
rbx_session [label = "RbxSession"];
|
||||
message_queue [label = "MessageQueue"];
|
||||
middlewares [label = "Middlewares"];
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
[TOC]
|
||||
|
||||
## Creating the Rojo Project
|
||||
|
||||
To use Rojo to build a place, you'll need to create a new project file, which tells Rojo how your project is structured on-disk and in Roblox.
|
||||
|
||||
Create a new folder, then run `rojo init` inside that folder to initialize an empty project.
|
||||
|
||||
```sh
|
||||
mkdir my-new-project
|
||||
cd my-new-project
|
||||
|
||||
rojo init
|
||||
```
|
||||
|
||||
Rojo will make a small project file in your directory, named `default.project.json`. It'll make sure that any code in the directory `src` will get put into `ReplicatedStorage.Source`.
|
||||
|
||||
Speaking of, let's make sure we create a directory named `src`, and maybe a Lua file inside of it:
|
||||
|
||||
```sh
|
||||
mkdir src
|
||||
echo 'print("Hello, world!")' > src/hello.lua
|
||||
```
|
||||
|
||||
## Building Your Place
|
||||
Now that we have a project, one thing we can do is build a Roblox place file for our project. This is a great way to get started with a project quickly with no fuss.
|
||||
|
||||
All we have to do is call `rojo build`:
|
||||
|
||||
```sh
|
||||
rojo build -o MyNewProject.rbxl
|
||||
```
|
||||
|
||||
If you open `MyNewProject.rbxl` in Roblox Studio now, you should see a `Folder` containing a `ModuleScript` under `ReplicatedStorage`!
|
||||
|
||||
!!! info
|
||||
To generate an XML place file instead, like if you're checking the place file into version control, just use `rbxlx` as the extension on the output file instead.
|
||||
|
||||
## Live-Syncing into Studio
|
||||
Building a place file is great for the initial build, but for actively working on your place, you'll want something quicker.
|
||||
|
||||
In Roblox Studio, make sure the Rojo plugin is installed. If you need it, check out [the installation guide](installation) to learn how to install it.
|
||||
|
||||
To expose your project to the plugin, you'll need to _serve_ it from the command line:
|
||||
|
||||
```sh
|
||||
rojo serve
|
||||
```
|
||||
|
||||
This will start up a web server that tells Roblox Studio what instances are in your project and sends notifications if any of them change.
|
||||
|
||||
Note the port number, then switch into Roblox Studio and press the Rojo **Connect** button in the plugins tab. Type in the port number, if necessary, and press **Start**.
|
||||
|
||||
If everything went well, you should now be able to change files in the `src` directory and watch them sync into Roblox Studio in real time!
|
||||
|
||||
## Uploading Your Place
|
||||
Aimed at teams that want serious levels of automation, Rojo can upload places to Roblox.com automatically.
|
||||
|
||||
You'll need an existing place on Roblox.com as well as the `.ROBLOSECURITY` cookie of an account that has write access to that place.
|
||||
|
||||
!!! warning
|
||||
It's recommended that you set up a Roblox account dedicated to deploying your place instead of your personal account in case your security cookie is compromised.
|
||||
|
||||
Generating and uploading your place file is as simple as:
|
||||
|
||||
```sh
|
||||
rojo upload --asset_id [PLACE ID] --cookie "[SECURITY COOKIE]"
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
.md-typeset__table {
|
||||
width: 100%;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 19 KiB |
@@ -1,17 +0,0 @@
|
||||
digraph "Sync Files" {
|
||||
graph [
|
||||
ranksep = "0.7",
|
||||
nodesep = "0.5",
|
||||
];
|
||||
node [
|
||||
fontname = "monospace",
|
||||
shape = "record",
|
||||
];
|
||||
|
||||
my_model [label = "MyModel"]
|
||||
init_server [label = "init.server.lua"]
|
||||
foo [label = "foo.lua"]
|
||||
|
||||
my_model -> init_server
|
||||
my_model -> foo
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
|
||||
-->
|
||||
<!-- Title: Sync Files Pages: 1 -->
|
||||
<svg width="258pt" height="132pt"
|
||||
viewBox="0.00 0.00 258.00 132.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 128)">
|
||||
<title>Sync Files</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-128 254,-128 254,4 -4,4"/>
|
||||
<!-- my_model -->
|
||||
<g id="node1" class="node"><title>my_model</title>
|
||||
<polygon fill="none" stroke="black" points="104,-87.5 104,-123.5 178,-123.5 178,-87.5 104,-87.5"/>
|
||||
<text text-anchor="middle" x="141" y="-101.8" font-family="monospace" font-size="14.00">MyModel</text>
|
||||
</g>
|
||||
<!-- init_server -->
|
||||
<g id="node2" class="node"><title>init_server</title>
|
||||
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 140,-36.5 140,-0.5 0,-0.5"/>
|
||||
<text text-anchor="middle" x="70" y="-14.8" font-family="monospace" font-size="14.00">init.server.lua</text>
|
||||
</g>
|
||||
<!-- my_model->init_server -->
|
||||
<g id="edge1" class="edge"><title>my_model->init_server</title>
|
||||
<path fill="none" stroke="black" d="M126.632,-87.299C116.335,-74.9713 102.308,-58.1787 90.7907,-44.3902"/>
|
||||
<polygon fill="black" stroke="black" points="93.4435,-42.1065 84.3465,-36.6754 88.0711,-46.594 93.4435,-42.1065"/>
|
||||
</g>
|
||||
<!-- foo -->
|
||||
<g id="node3" class="node"><title>foo</title>
|
||||
<polygon fill="none" stroke="black" points="176,-0.5 176,-36.5 250,-36.5 250,-0.5 176,-0.5"/>
|
||||
<text text-anchor="middle" x="213" y="-14.8" font-family="monospace" font-size="14.00">foo.lua</text>
|
||||
</g>
|
||||
<!-- my_model->foo -->
|
||||
<g id="edge2" class="edge"><title>my_model->foo</title>
|
||||
<path fill="none" stroke="black" d="M155.57,-87.299C166.013,-74.9713 180.237,-58.1787 191.917,-44.3902"/>
|
||||
<polygon fill="black" stroke="black" points="194.659,-46.5681 198.451,-36.6754 189.317,-42.0437 194.659,-46.5681"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,15 +0,0 @@
|
||||
digraph "Sync Files" {
|
||||
graph [
|
||||
ranksep = "0.7",
|
||||
nodesep = "0.5",
|
||||
];
|
||||
node [
|
||||
fontname = "monospace",
|
||||
shape = "record",
|
||||
];
|
||||
|
||||
my_model [label = "MyModel (Script)"]
|
||||
foo [label = "foo (ModuleScript)"]
|
||||
|
||||
my_model -> foo
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
|
||||
-->
|
||||
<!-- Title: Sync Files Pages: 1 -->
|
||||
<svg width="173pt" height="132pt"
|
||||
viewBox="0.00 0.00 173.00 132.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 128)">
|
||||
<title>Sync Files</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-128 169,-128 169,4 -4,4"/>
|
||||
<!-- my_model -->
|
||||
<g id="node1" class="node"><title>my_model</title>
|
||||
<polygon fill="none" stroke="black" points="8,-87.5 8,-123.5 157,-123.5 157,-87.5 8,-87.5"/>
|
||||
<text text-anchor="middle" x="82.5" y="-101.8" font-family="monospace" font-size="14.00">MyModel (Script)</text>
|
||||
</g>
|
||||
<!-- foo -->
|
||||
<g id="node2" class="node"><title>foo</title>
|
||||
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 165,-36.5 165,-0.5 0,-0.5"/>
|
||||
<text text-anchor="middle" x="82.5" y="-14.8" font-family="monospace" font-size="14.00">foo (ModuleScript)</text>
|
||||
</g>
|
||||
<!-- my_model->foo -->
|
||||
<g id="edge1" class="edge"><title>my_model->foo</title>
|
||||
<path fill="none" stroke="black" d="M82.5,-87.299C82.5,-75.6626 82.5,-60.0479 82.5,-46.7368"/>
|
||||
<polygon fill="black" stroke="black" points="86.0001,-46.6754 82.5,-36.6754 79.0001,-46.6755 86.0001,-46.6754"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,17 +0,0 @@
|
||||
digraph "Sync Files" {
|
||||
graph [
|
||||
ranksep = "0.7",
|
||||
nodesep = "0.5",
|
||||
];
|
||||
node [
|
||||
fontname = "monospace",
|
||||
shape = "record",
|
||||
];
|
||||
|
||||
model [label = "My Cool Model (Folder)"]
|
||||
root_part [label = "RootPart (Part)"]
|
||||
send_money [label = "SendMoney (RemoteEvent)"]
|
||||
|
||||
model -> root_part
|
||||
model -> send_money
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
|
||||
-->
|
||||
<!-- Title: Sync Files Pages: 1 -->
|
||||
<svg width="390pt" height="132pt"
|
||||
viewBox="0.00 0.00 390.00 132.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 128)">
|
||||
<title>Sync Files</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-128 386,-128 386,4 -4,4"/>
|
||||
<!-- model -->
|
||||
<g id="node1" class="node"><title>model</title>
|
||||
<polygon fill="none" stroke="black" points="75,-87.5 75,-123.5 273,-123.5 273,-87.5 75,-87.5"/>
|
||||
<text text-anchor="middle" x="174" y="-101.8" font-family="monospace" font-size="14.00">My Cool Model (Folder)</text>
|
||||
</g>
|
||||
<!-- root_part -->
|
||||
<g id="node2" class="node"><title>root_part</title>
|
||||
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 140,-36.5 140,-0.5 0,-0.5"/>
|
||||
<text text-anchor="middle" x="70" y="-14.8" font-family="monospace" font-size="14.00">RootPart (Part)</text>
|
||||
</g>
|
||||
<!-- model->root_part -->
|
||||
<g id="edge1" class="edge"><title>model->root_part</title>
|
||||
<path fill="none" stroke="black" d="M152.954,-87.299C137.448,-74.6257 116.168,-57.2335 99.0438,-43.2377"/>
|
||||
<polygon fill="black" stroke="black" points="100.972,-40.2938 91.0147,-36.6754 96.5426,-45.7138 100.972,-40.2938"/>
|
||||
</g>
|
||||
<!-- send_money -->
|
||||
<g id="node3" class="node"><title>send_money</title>
|
||||
<polygon fill="none" stroke="black" points="176,-0.5 176,-36.5 382,-36.5 382,-0.5 176,-0.5"/>
|
||||
<text text-anchor="middle" x="279" y="-14.8" font-family="monospace" font-size="14.00">SendMoney (RemoteEvent)</text>
|
||||
</g>
|
||||
<!-- model->send_money -->
|
||||
<g id="edge2" class="edge"><title>model->send_money</title>
|
||||
<path fill="none" stroke="black" d="M195.248,-87.299C210.904,-74.6257 232.388,-57.2335 249.677,-43.2377"/>
|
||||
<polygon fill="black" stroke="black" points="252.213,-45.6878 257.783,-36.6754 247.809,-40.2471 252.213,-45.6878"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
@@ -1,11 +0,0 @@
|
||||
This is the documentation home for Rojo 0.5.x.
|
||||
|
||||
Available versions of these docs:
|
||||
|
||||
* [Latest version (currently 0.5.x)](https://lpghatguy.github.io/rojo)
|
||||
* [0.5.x](https://lpghatguy.github.io/rojo/0.5.x)
|
||||
* [0.4.x](https://lpghatguy.github.io/rojo/0.4.x)
|
||||
|
||||
**Rojo** is a flexible multi-tool designed for creating robust Roblox projects.
|
||||
|
||||
This documentation is a continual work in progress. If you find any issues, please file an issue on [Rojo's issue tracker](https://github.com/LPGhatguy/rojo/issues)!
|
||||
@@ -1,45 +0,0 @@
|
||||
[TOC]
|
||||
|
||||
## Overview
|
||||
|
||||
Rojo has two components:
|
||||
|
||||
* The command line interface (CLI)
|
||||
* The Roblox Studio plugin
|
||||
|
||||
!!! info
|
||||
It's important that your installed version of the plugin and CLI are compatible.
|
||||
|
||||
The plugin will show errors in the Roblox Studio output window if there is a version mismatch.
|
||||
|
||||
## Installing the CLI
|
||||
|
||||
### Installing from GitHub
|
||||
If you're on Windows, there are pre-built binaries available from Rojo's [GitHub Releases page](https://github.com/LPGhatguy/rojo/releases).
|
||||
|
||||
The Rojo CLI must be run from the command line, like Terminal.app on MacOS or `cmd.exe` on Windows. It's recommended that you put the Rojo CLI executable on your `PATH` to make this easier.
|
||||
|
||||
### Installing from Cargo
|
||||
If you have Rust installed, the easiest way to get Rojo is with Cargo!
|
||||
|
||||
To install the latest 0.5.0 alpha, use:
|
||||
|
||||
```sh
|
||||
cargo install rojo --version 0.5.0-alpha.5
|
||||
```
|
||||
|
||||
## Installing the Plugin
|
||||
|
||||
### Installing from GitHub
|
||||
The Rojo Roblox Studio plugin is available available from Rojo's [GitHub Releases page](https://github.com/LPGhatguy/rojo/releases).
|
||||
|
||||
Download the attached `rbxm` file and put it into your Roblox Studio plugins folder. You can find that folder by pressing **Plugins Folder** from your Plugins toolbar in Roblox Studio:
|
||||
|
||||

|
||||
{: align="center" }
|
||||
|
||||
### Installing from Roblox.com
|
||||
Visit [Rojo's Roblox.com Plugin page](https://www.roblox.com/library/1997686364/Rojo-0-5-0-alpha-3) in Roblox Studio and press **Install**.
|
||||
|
||||
## Visual Studio Code Extension
|
||||
If you use Visual Studio Code on Windows, you can install [Evaera's unofficial Rojo extension](https://marketplace.visualstudio.com/items?itemName=evaera.vscode-rojo), which will install both halves of Rojo for you. It even has a nifty UI to add partitions and start/stop the Rojo server!
|
||||
@@ -1,45 +0,0 @@
|
||||
This document aims to give a general overview of how Rojo works. It's intended for people who want to contribute to the project as well as anyone who's just curious how the tool works!
|
||||
|
||||
[TOC]
|
||||
|
||||
## CLI
|
||||
|
||||
### RbxTree
|
||||
Rojo uses a library named [`rbx_tree`](https://github.com/LPGhatguy/rbx-tree) as its implementation of the Roblox DOM. It serves as a common format for serialization to all the formats Rojo supports!
|
||||
|
||||
Rojo uses two related libraries to deserialize instances from Roblox's file formats, `rbx_xml` and `rbx_binary`.
|
||||
|
||||
### In-Memory Filesystem (IMFS)
|
||||
Relevant source files:
|
||||
|
||||
* [`server/src/imfs.rs`](https://github.com/LPGhatguy/rojo/blob/master/server/src/imfs.rs)
|
||||
* [`server/src/fs_watcher.rs`](https://github.com/LPGhatguy/rojo/blob/master/server/src/fs_watcher.rs)
|
||||
|
||||
Rojo keeps an in-memory copy of all files that it needs reasons about. This enables taking fast, stateless, tear-tree snapshots of files to turn them into instances.
|
||||
|
||||
Keeping an in-memory copy of file contents will also enable Rojo to debounce changes that are caused by Rojo itself. This'll happen when two-way sync finally happens.
|
||||
|
||||
### Snapshot Reconciler
|
||||
Relevant source files:
|
||||
|
||||
* [`server/src/snapshot_reconciler.rs`](https://github.com/LPGhatguy/rojo/blob/master/server/src/snapshot_reconciler.rs)
|
||||
* [`server/src/rbx_snapshot.rs`](https://github.com/LPGhatguy/rojo/blob/master/server/src/rbx_snapshot.rs)
|
||||
* [`server/src/rbx_session.rs`](https://github.com/LPGhatguy/rojo/blob/master/server/src/rbx_session.rs)
|
||||
|
||||
To simplify incremental updates of instances, Rojo generates lightweight snapshots describing how files map to instances. This means that Rojo can treat file change events similarly to damage painting as opposed to trying to surgically update the correct instances.
|
||||
|
||||
This approach reduces the number of desynchronization bugs, reduces the complexity of important pieces of the codebase, and makes writing plugins a lot easier.
|
||||
|
||||
### HTTP API
|
||||
Relevant source files:
|
||||
|
||||
* [`server/src/web.rs`](https://github.com/LPGhatguy/rojo/blob/master/server/src/web.rs)
|
||||
|
||||
The Rojo live-sync server and Roblox Studio plugin communicate via HTTP.
|
||||
|
||||
Requests sent from the plugin to the server are regular HTTP requests.
|
||||
|
||||
Messages sent from the server to the plugin are delivered via HTTP long-polling. This is an approach that uses long-lived HTTP requests that restart on timeout. It's largely been replaced by WebSockets, but Roblox doesn't have support for them.
|
||||
|
||||
## Roblox Studio Plugin
|
||||
TODO
|
||||
@@ -1,58 +0,0 @@
|
||||
Rojo underwent a large refactor during most of 2018 to enable a bunch of new features and lay groundwork for lots more in 2019. As such, Rojo **0.5.x** projects are not compatible with Rojo **0.4.x** projects.
|
||||
|
||||
[TOC]
|
||||
|
||||
## Supporting Both 0.4.x and 0.5.x
|
||||
Rojo 0.5.x uses a different name for its project format. While 0.4.x used `rojo.json`, 0.5.x uses `default.project.json`, which allows them to coexist.
|
||||
|
||||
If you aren't sure about upgrading or want to upgrade gradually, it's possible to keep both files in the same project without causing problems.
|
||||
|
||||
## Upgrading Your Project File
|
||||
Project files in 0.5.x are more explicit and flexible than they were in 0.4.x. Project files can now describe models and plugins in addition to places.
|
||||
|
||||
This new project file format also guards against two of the biggest pitfalls when writing a config file:
|
||||
|
||||
* Using a service as a partition target directly, which often wiped away extra instances
|
||||
* Defining two partitions that overlapped, which made Rojo act unpredictably
|
||||
|
||||
The biggest change is that the `partitions` field has been replaced with a new field, `tree`, that describes the entire hierarchy of your project from the top-down.
|
||||
|
||||
A project for 0.4.x that syncs from the `src` directory into `ReplicatedStorage.Source` would look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Rojo 0.4.x Example",
|
||||
"partitions": {
|
||||
"path": "src",
|
||||
"target": "ReplicatedStorage.Source"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In 0.5.x, the project format is more explicit:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Rojo 0.5.x Example",
|
||||
"tree": {
|
||||
"$className": "DataModel",
|
||||
"ReplicatedStorage": {
|
||||
"$className": "ReplicatedStorage",
|
||||
"Source": {
|
||||
"$path": "src"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For each object in the tree, we define *metadata* and *children*.
|
||||
|
||||
Metadata begins with a dollar sign (`$`), like `$className`. This is so that children and metadata can coexist without creating too many nested layers.
|
||||
|
||||
All other values are considered children, where the key is the instance's name, and the value is an object, repeating the process.
|
||||
|
||||
## Migrating Unknown Files
|
||||
If you used Rojo to sync in files as `StringValue` objects, you'll need to make sure those files end with the `txt` extension to preserve this in Rojo 0.5.x.
|
||||
|
||||
Unknown files are now ignored in Rojo instead of being converted to `StringValue` objects.
|
||||
@@ -1,100 +0,0 @@
|
||||
[TOC]
|
||||
|
||||
## Project File
|
||||
|
||||
Rojo projects are JSON files that have the `.project.json` extension. They have these fields:
|
||||
|
||||
* `name`: A string indicating the name of the project.
|
||||
* This is only used for diagnostics.
|
||||
* `tree`: An [Instance Description](#instance-description) describing the root instance of the project.
|
||||
|
||||
## Instance Description
|
||||
Instance Descriptions correspond one-to-one with the actual Roblox Instances in the project. They can be specified directly in the project file or be pulled from the filesystem.
|
||||
|
||||
* `$className`: The ClassName of the Instance being described.
|
||||
* Optional if `$path` is specified.
|
||||
* `$path`: The path on the filesystem to pull files from into the project.
|
||||
* Optional if `$className` is specified.
|
||||
* Paths are relative to the folder containing the project file.
|
||||
* `$properties`: Properties to apply to the instance. Values should be [Instance Property Values](#instance-property-value).
|
||||
* Optional
|
||||
* `$ignoreUnknownInstances`: Whether instances that Rojo doesn't know about should be deleted.
|
||||
* Optional
|
||||
* Default is `false` if `$path` is specified, otherwise `true`.
|
||||
|
||||
All other fields in an Instance Description are turned into instances whose name is the key. These values should also be Instance Descriptions!
|
||||
|
||||
Instance Descriptions are fairly verbose and strict. In the future, it'll be possible for Rojo to infer class names for known services like `Workspace`.
|
||||
|
||||
## Instance Property Value
|
||||
The shape of Instance Property Values is defined by the [rbx_tree](https://github.com/LPGhatguy/rbx-tree) library, so it uses slightly different conventions than the rest of Rojo.
|
||||
|
||||
Each value should be an object with the following required fields:
|
||||
|
||||
* `Type`: The type of property to represent.
|
||||
* [Supported types can be found here](https://github.com/LPGhatguy/rbx-tree#property-type-coverage).
|
||||
* `Value`: The value of the property.
|
||||
* The shape of this field depends on which property type is being used. `Vector3` and `Color3` values are both represented as a list of numbers, for example.
|
||||
|
||||
Instance Property Values are intentionally very strict. Rojo will eventually be able to infer types for you!
|
||||
|
||||
## Example Projects
|
||||
This project bundles up everything in the `src` directory. It'd be suitable for making a plugin or model:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "AwesomeLibrary",
|
||||
"tree": {
|
||||
"$path": "src"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This project describes the layout you might use if you were making the next hit simulator game, *Sisyphus Simulator*:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Sisyphus Simulator",
|
||||
"tree": {
|
||||
"$className": "DataModel",
|
||||
|
||||
"HttpService": {
|
||||
"$className": "HttpService",
|
||||
"$properties": {
|
||||
"HttpEnabled": {
|
||||
"Type": "Bool",
|
||||
"Value": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"ReplicatedStorage": {
|
||||
"$className": "ReplicatedStorage",
|
||||
"$path": "src/ReplicatedStorage"
|
||||
},
|
||||
|
||||
"StarterPlayer": {
|
||||
"$className": "StarterPlayer",
|
||||
|
||||
"StarterPlayerScripts": {
|
||||
"$className": "StarterPlayerScripts",
|
||||
"$path": "src/StarterPlayerScripts"
|
||||
}
|
||||
},
|
||||
|
||||
"Workspace": {
|
||||
"$className": "Workspace",
|
||||
"$properties": {
|
||||
"Gravity": {
|
||||
"Type": "Float32",
|
||||
"Value": 67.3
|
||||
}
|
||||
},
|
||||
|
||||
"Terrain": {
|
||||
"$path": "Terrain.rbxm"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
mkdocs
|
||||
mkdocs-material
|
||||
pymdown-extensions
|
||||
@@ -1,91 +0,0 @@
|
||||
This page aims to describe how Rojo turns files on the filesystem into Roblox objects.
|
||||
|
||||
[TOC]
|
||||
|
||||
## Overview
|
||||
| File Name | Instance Type |
|
||||
| -------------- | ------------------- |
|
||||
| any directory | `Folder` |
|
||||
| `*.server.lua` | `Script` |
|
||||
| `*.client.lua` | `LocalScript` |
|
||||
| `*.lua` | `ModuleScript` |
|
||||
| `*.csv` | `LocalizationTable` |
|
||||
| `*.txt` | `StringValue` |
|
||||
| `*.model.json` | Any |
|
||||
| `*.rbxm` | Any |
|
||||
| `*.rbxmx` | Any |
|
||||
|
||||
## Limitations
|
||||
Not all property types can be synced by Rojo in real-time due to limitations of the Roblox Studio plugin API. In these cases, you can usually generate a place file and open it when you start working on a project.
|
||||
|
||||
Some common cases you might hit are:
|
||||
|
||||
* Binary data (Terrain, CSG, CollectionService tags)
|
||||
* `MeshPart.MeshId`
|
||||
* `HttpService.HttpEnabled`
|
||||
|
||||
For a list of all property types that Rojo can reason about, both when live-syncing and when building place files, look at [rbx_tree's type coverage chart](https://github.com/LPGhatguy/rbx-tree#property-type-coverage).
|
||||
|
||||
## Folders
|
||||
Any directory on the filesystem will turn into a `Folder` instance unless it contains an 'init' script, described below.
|
||||
|
||||
## Scripts
|
||||
The default script type in Rojo projects is `ModuleScript`, since most scripts in well-structued Roblox projects will be modules.
|
||||
|
||||
If a directory contains a file named `init.server.lua`, `init.client.lua`, or `init.lua`, that folder will be transformed into a `*Script` instance with the contents of the 'init' file. This can be used to create scripts inside of scripts.
|
||||
|
||||
For example, these files:
|
||||
|
||||

|
||||
{: align="center" }
|
||||
|
||||
Will turn into these instances in Roblox:
|
||||
|
||||

|
||||
{: align="center" }
|
||||
|
||||
## Localization Tables
|
||||
Any CSV files are transformed into `LocalizationTable` instances. Rojo expects these files to follow the same format that Roblox does when importing and exporting localization information.
|
||||
|
||||
## Plain Text Files
|
||||
Plain text files (`.txt`) files are transformed into `StringValue` instances. This is useful for bringing in text data that can be read by scripts at runtime.
|
||||
|
||||
## JSON Models
|
||||
Files ending in `.model.json` can be used to describe simple models. They're designed to be hand-written and are useful for instances like `RemoteEvent`.
|
||||
|
||||
A JSON model describing a folder containing a `Part` and a `RemoteEvent` could be described as:
|
||||
|
||||
```json
|
||||
{
|
||||
"Name": "My Cool Model",
|
||||
"ClassName": "Folder",
|
||||
"Children": [
|
||||
{
|
||||
"Name": "RootPart",
|
||||
"ClassName": "Part",
|
||||
"Properties": {
|
||||
"Size": {
|
||||
"Type": "Vector3",
|
||||
"Value": [4, 4, 4]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "SendMoney",
|
||||
"ClassName": "RemoteEvent"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
It would turn into instances in this shape:
|
||||
|
||||

|
||||
{: align="center" }
|
||||
|
||||
## Binary and XML Models
|
||||
Rojo supports both binary (`.rbxm`) and XML (`.rbxmx`) models generated by Roblox Studio or another tool.
|
||||
|
||||
Not all property types are supported for all formats!
|
||||
|
||||
For a rundown of supported types, check out [rbx_tree's type coverage chart](https://github.com/LPGhatguy/rbx-tree#property-type-coverage).
|
||||
@@ -1,23 +0,0 @@
|
||||
There are a number of existing plugins for Roblox that move code from the filesystem into Roblox.
|
||||
|
||||
Besides Rojo, you might consider:
|
||||
|
||||
* [rbxmk by Anaminus](https://github.com/anaminus/rbxmk)
|
||||
* [Rofresh by Osyris](https://github.com/osyrisrblx/rofresh)
|
||||
* [RbxRefresh by Osyris](https://github.com/osyrisrblx/RbxRefresh)
|
||||
* [Studio Bridge by Vocksel](https://github.com/vocksel/studio-bridge)
|
||||
* [Elixir by Vocksel](https://github.com/vocksel/elixir)
|
||||
* [RbxSync by evaera](https://github.com/evaera/RbxSync)
|
||||
* [CodeSync by MemoryPenguin](https://github.com/MemoryPenguin/CodeSync)
|
||||
* [rbx-exteditor by MemoryPenguin](https://github.com/MemoryPenguin/rbx-exteditor)
|
||||
|
||||
So why did I build Rojo?
|
||||
|
||||
Each of these tools solves what is essentially the same problem from a few different angles. The goal of Rojo is to take all of the lessons and ideas learned from these projects and build a tool that can solve this problem for good.
|
||||
|
||||
Additionally:
|
||||
|
||||
* I think that this tool needs to be built in a compiled language without a runtime, for easy distribution and good performance.
|
||||
* I think that the conventions promoted by other sync plugins (`.module.lua` for modules, as well a single sync point) are sub-optimal.
|
||||
* I think that I have a good enough understanding of the problem to build something robust.
|
||||
* I think that Rojo should be able to do more than just sync code.
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Kludged documentation generator to support multiple versions.
|
||||
# Make sure the `site` folder is a checkout of this repository's `gh-pages`
|
||||
# branch.
|
||||
|
||||
set -e
|
||||
|
||||
REMOTE=$(git remote get-url origin)
|
||||
CHECKOUT="$(mktemp -d)"
|
||||
OUTPUT="$(pwd)/site"
|
||||
|
||||
if [ -d site ]
|
||||
then
|
||||
cd site
|
||||
git pull
|
||||
else
|
||||
git clone "$REMOTE" site
|
||||
cd site
|
||||
git checkout gh-pages
|
||||
fi
|
||||
|
||||
git clone "$REMOTE" "$CHECKOUT"
|
||||
cd "$CHECKOUT"
|
||||
|
||||
echo "Building master"
|
||||
git checkout master
|
||||
mkdocs build --site-dir "$OUTPUT"
|
||||
|
||||
echo "Building 0.5.x"
|
||||
mkdocs build --site-dir "$OUTPUT/0.5.x"
|
||||
|
||||
echo "Building 0.4.x"
|
||||
git checkout v0.4.x
|
||||
mkdocs build --site-dir "$OUTPUT/0.4.x"
|
||||
9
memofs/CHANGELOG.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# memofs Changelog
|
||||
|
||||
## Unreleased Changes
|
||||
|
||||
## 0.1.1 (2020-03-18)
|
||||
* Improved error messages using the [fs-err](https://crates.io/crates/fs-err) crate.
|
||||
|
||||
## 0.1.0 (2020-03-10)
|
||||
* Initial release
|
||||
16
memofs/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "memofs"
|
||||
description = "Virtual filesystem with configurable backends."
|
||||
version = "0.1.1"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
homepage = "https://github.com/rojo-rbx/rojo/tree/master/memofs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
crossbeam-channel = "0.4.0"
|
||||
fs-err = "2.3.0"
|
||||
notify = "4.0.15"
|
||||
7
memofs/LICENSE.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright 2020 The Rojo Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
22
memofs/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# memofs
|
||||
[](https://crates.io/crates/memofs)
|
||||
|
||||
Implementation of a virtual filesystem with a configurable backend and file
|
||||
watching.
|
||||
|
||||
memofs is currently an unstable minimum viable library. Its primary consumer is
|
||||
[Rojo](https://github.com/rojo-rbx/rojo), a build system for Roblox.
|
||||
|
||||
### Current Features
|
||||
* API similar to `std::fs`
|
||||
* Configurable backends
|
||||
* `StdBackend`, which uses `std::fs` and the `notify` crate
|
||||
* `NoopBackend`, which always throws errors
|
||||
* `InMemoryFs`, a simple in-memory filesystem useful for testing
|
||||
|
||||
### Future Features
|
||||
* Hash-based hierarchical memoization keys (hence the name)
|
||||
* Configurable caching (write-through, write-around, write-back)
|
||||
|
||||
## License
|
||||
memofs is available under the terms of the MIT license. See [LICENSE.txt](LICENSE.txt) or <https://opensource.org/licenses/MIT> for more details.
|
||||
7
memofs/README.tpl
Normal file
@@ -0,0 +1,7 @@
|
||||
# {{crate}}
|
||||
[](https://crates.io/crates/memofs)
|
||||
|
||||
{{readme}}
|
||||
|
||||
## License
|
||||
memofs is available under the terms of the MIT license. See [LICENSE.txt](LICENSE.txt) or <https://opensource.org/licenses/MIT> for more details.
|
||||
249
memofs/src/in_memory_fs.rs
Normal file
@@ -0,0 +1,249 @@
|
||||
use std::collections::{BTreeSet, HashMap, VecDeque};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
|
||||
use crate::{DirEntry, Metadata, ReadDir, VfsBackend, VfsEvent, VfsSnapshot};
|
||||
|
||||
/// In-memory filesystem that can be used as a VFS backend.
|
||||
///
|
||||
/// Internally reference counted to enable giving a copy to
|
||||
/// [`Vfs`](struct.Vfs.html) and keeping the original to mutate the filesystem's
|
||||
/// state with.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InMemoryFs {
|
||||
inner: Arc<Mutex<InMemoryFsInner>>,
|
||||
}
|
||||
|
||||
impl InMemoryFs {
|
||||
/// Create a new empty `InMemoryFs`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(InMemoryFsInner::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a [`VfsSnapshot`](enum.VfsSnapshot.html) into a subtree of the
|
||||
/// in-memory filesystem.
|
||||
///
|
||||
/// This function will return an error if the operations required to apply
|
||||
/// the snapshot result in errors, like trying to create a file inside a
|
||||
/// file.
|
||||
pub fn load_snapshot<P: Into<PathBuf>>(
|
||||
&mut self,
|
||||
path: P,
|
||||
snapshot: VfsSnapshot,
|
||||
) -> io::Result<()> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.load_snapshot(path.into(), snapshot)
|
||||
}
|
||||
|
||||
/// Raises a filesystem change event.
|
||||
///
|
||||
/// If this `InMemoryFs` is being used as the backend of a
|
||||
/// [`Vfs`](struct.Vfs.html), then any listeners be notified of this event.
|
||||
pub fn raise_event(&mut self, event: VfsEvent) {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.event_sender.send(event).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InMemoryFsInner {
|
||||
entries: HashMap<PathBuf, Entry>,
|
||||
orphans: BTreeSet<PathBuf>,
|
||||
|
||||
event_receiver: Receiver<VfsEvent>,
|
||||
event_sender: Sender<VfsEvent>,
|
||||
}
|
||||
|
||||
impl InMemoryFsInner {
|
||||
fn new() -> Self {
|
||||
let (event_sender, event_receiver) = crossbeam_channel::unbounded();
|
||||
|
||||
Self {
|
||||
entries: HashMap::new(),
|
||||
orphans: BTreeSet::new(),
|
||||
event_receiver,
|
||||
event_sender,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_snapshot(&mut self, path: PathBuf, snapshot: VfsSnapshot) -> io::Result<()> {
|
||||
if let Some(parent_path) = path.parent() {
|
||||
if let Some(parent_entry) = self.entries.get_mut(parent_path) {
|
||||
if let Entry::Dir { children } = parent_entry {
|
||||
children.insert(path.clone());
|
||||
} else {
|
||||
return must_be_dir(parent_path);
|
||||
}
|
||||
} else {
|
||||
self.orphans.insert(path.clone());
|
||||
}
|
||||
} else {
|
||||
self.orphans.insert(path.clone());
|
||||
}
|
||||
|
||||
match snapshot {
|
||||
VfsSnapshot::File { contents } => {
|
||||
self.entries.insert(path, Entry::File { contents });
|
||||
}
|
||||
VfsSnapshot::Dir { children } => {
|
||||
self.entries.insert(
|
||||
path.clone(),
|
||||
Entry::Dir {
|
||||
children: BTreeSet::new(),
|
||||
},
|
||||
);
|
||||
|
||||
for (child_name, child) in children {
|
||||
let full_path = path.join(child_name);
|
||||
self.load_snapshot(full_path, child)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&mut self, root_path: PathBuf) {
|
||||
self.orphans.remove(&root_path);
|
||||
|
||||
let mut to_remove = VecDeque::new();
|
||||
to_remove.push_back(root_path);
|
||||
|
||||
while let Some(path) = to_remove.pop_front() {
|
||||
if let Some(Entry::Dir { children }) = self.entries.remove(&path) {
|
||||
to_remove.extend(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Entry {
|
||||
File { contents: Vec<u8> },
|
||||
|
||||
Dir { children: BTreeSet<PathBuf> },
|
||||
}
|
||||
|
||||
impl VfsBackend for InMemoryFs {
|
||||
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>> {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
|
||||
match inner.entries.get(path) {
|
||||
Some(Entry::File { contents }) => Ok(contents.clone()),
|
||||
Some(Entry::Dir { .. }) => must_be_file(path),
|
||||
None => not_found(path),
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
inner.load_snapshot(
|
||||
path.to_path_buf(),
|
||||
VfsSnapshot::File {
|
||||
contents: data.to_owned(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
|
||||
match inner.entries.get(path) {
|
||||
Some(Entry::Dir { children }) => {
|
||||
let iter = children
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|path| Ok(DirEntry { path }));
|
||||
|
||||
Ok(ReadDir {
|
||||
inner: Box::new(iter),
|
||||
})
|
||||
}
|
||||
Some(Entry::File { .. }) => must_be_dir(path),
|
||||
None => not_found(path),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
match inner.entries.get(path) {
|
||||
Some(Entry::File { .. }) => {
|
||||
inner.remove(path.to_owned());
|
||||
Ok(())
|
||||
}
|
||||
Some(Entry::Dir { .. }) => must_be_file(path),
|
||||
None => not_found(path),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
match inner.entries.get(path) {
|
||||
Some(Entry::Dir { .. }) => {
|
||||
inner.remove(path.to_owned());
|
||||
Ok(())
|
||||
}
|
||||
Some(Entry::File { .. }) => must_be_dir(path),
|
||||
None => not_found(path),
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata(&mut self, path: &Path) -> io::Result<Metadata> {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
|
||||
match inner.entries.get(path) {
|
||||
Some(Entry::File { .. }) => Ok(Metadata { is_file: true }),
|
||||
Some(Entry::Dir { .. }) => Ok(Metadata { is_file: false }),
|
||||
None => not_found(path),
|
||||
}
|
||||
}
|
||||
|
||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
|
||||
inner.event_receiver.clone()
|
||||
}
|
||||
|
||||
fn watch(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unwatch(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn must_be_file<T>(path: &Path) -> io::Result<T> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"path {} was a directory, but must be a file",
|
||||
path.display()
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn must_be_dir<T>(path: &Path) -> io::Result<T> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"path {} was a file, but must be a directory",
|
||||
path.display()
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn not_found<T>(path: &Path) -> io::Result<T> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!("path {} not found", path.display()),
|
||||
))
|
||||
}
|
||||
404
memofs/src/lib.rs
Normal file
@@ -0,0 +1,404 @@
|
||||
/*!
|
||||
Implementation of a virtual filesystem with a configurable backend and file
|
||||
watching.
|
||||
|
||||
memofs is currently an unstable minimum viable library. Its primary consumer is
|
||||
[Rojo](https://github.com/rojo-rbx/rojo), a build system for Roblox.
|
||||
|
||||
## Current Features
|
||||
* API similar to `std::fs`
|
||||
* Configurable backends
|
||||
* `StdBackend`, which uses `std::fs` and the `notify` crate
|
||||
* `NoopBackend`, which always throws errors
|
||||
* `InMemoryFs`, a simple in-memory filesystem useful for testing
|
||||
|
||||
## Future Features
|
||||
* Hash-based hierarchical memoization keys (hence the name)
|
||||
* Configurable caching (write-through, write-around, write-back)
|
||||
*/
|
||||
|
||||
mod in_memory_fs;
|
||||
mod noop_backend;
|
||||
mod snapshot;
|
||||
mod std_backend;
|
||||
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
pub use in_memory_fs::InMemoryFs;
|
||||
pub use noop_backend::NoopBackend;
|
||||
pub use snapshot::VfsSnapshot;
|
||||
pub use std_backend::StdBackend;
|
||||
|
||||
mod sealed {
|
||||
use super::*;
|
||||
|
||||
/// Sealing trait for VfsBackend.
|
||||
pub trait Sealed {}
|
||||
|
||||
impl Sealed for NoopBackend {}
|
||||
impl Sealed for StdBackend {}
|
||||
impl Sealed for InMemoryFs {}
|
||||
}
|
||||
|
||||
/// Trait that transforms `io::Result<T>` into `io::Result<Option<T>>`.
|
||||
///
|
||||
/// `Ok(None)` takes the place of IO errors whose `io::ErrorKind` is `NotFound`.
|
||||
pub trait IoResultExt<T> {
|
||||
fn with_not_found(self) -> io::Result<Option<T>>;
|
||||
}
|
||||
|
||||
impl<T> IoResultExt<T> for io::Result<T> {
|
||||
fn with_not_found(self) -> io::Result<Option<T>> {
|
||||
match self {
|
||||
Ok(v) => Ok(Some(v)),
|
||||
Err(err) => {
|
||||
if err.kind() == io::ErrorKind::NotFound {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Backend that can be used to create a `Vfs`.
|
||||
///
|
||||
/// This trait is sealed and cannot not be implemented outside this crate.
|
||||
pub trait VfsBackend: sealed::Sealed + Send + 'static {
|
||||
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>>;
|
||||
fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()>;
|
||||
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir>;
|
||||
fn metadata(&mut self, path: &Path) -> io::Result<Metadata>;
|
||||
fn remove_file(&mut self, path: &Path) -> io::Result<()>;
|
||||
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()>;
|
||||
|
||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent>;
|
||||
fn watch(&mut self, path: &Path) -> io::Result<()>;
|
||||
fn unwatch(&mut self, path: &Path) -> io::Result<()>;
|
||||
}
|
||||
|
||||
/// Vfs equivalent to [`std::fs::DirEntry`][std::fs::DirEntry].
|
||||
///
|
||||
/// [std::fs::DirEntry]: https://doc.rust-lang.org/stable/std/fs/struct.DirEntry.html
|
||||
pub struct DirEntry {
|
||||
pub(crate) path: PathBuf,
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
/// Vfs equivalent to [`std::fs::ReadDir`][std::fs::ReadDir].
|
||||
///
|
||||
/// [std::fs::ReadDir]: https://doc.rust-lang.org/stable/std/fs/struct.ReadDir.html
|
||||
pub struct ReadDir {
|
||||
pub(crate) inner: Box<dyn Iterator<Item = io::Result<DirEntry>>>,
|
||||
}
|
||||
|
||||
impl Iterator for ReadDir {
|
||||
type Item = io::Result<DirEntry>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
}
|
||||
|
||||
/// Vfs equivalent to [`std::fs::Metadata`][std::fs::Metadata].
|
||||
///
|
||||
/// [std::fs::Metadata]: https://doc.rust-lang.org/stable/std/fs/struct.Metadata.html
|
||||
#[derive(Debug)]
|
||||
pub struct Metadata {
|
||||
pub(crate) is_file: bool,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
pub fn is_file(&self) -> bool {
|
||||
self.is_file
|
||||
}
|
||||
|
||||
pub fn is_dir(&self) -> bool {
|
||||
!self.is_file
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an event that a filesystem can raise that might need to be
|
||||
/// handled.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum VfsEvent {
|
||||
Create(PathBuf),
|
||||
Write(PathBuf),
|
||||
Remove(PathBuf),
|
||||
}
|
||||
|
||||
/// Contains implementation details of the Vfs, wrapped by `Vfs` and `VfsLock`,
|
||||
/// the public interfaces to this type.
|
||||
struct VfsInner {
|
||||
backend: Box<dyn VfsBackend>,
|
||||
}
|
||||
|
||||
impl VfsInner {
|
||||
fn read<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Arc<Vec<u8>>> {
|
||||
let path = path.as_ref();
|
||||
let contents = self.backend.read(path)?;
|
||||
self.backend.watch(path)?;
|
||||
Ok(Arc::new(contents))
|
||||
}
|
||||
|
||||
fn write<P: AsRef<Path>, C: AsRef<[u8]>>(&mut self, path: P, contents: C) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let contents = contents.as_ref();
|
||||
self.backend.write(path, contents)
|
||||
}
|
||||
|
||||
fn read_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<ReadDir> {
|
||||
let path = path.as_ref();
|
||||
let dir = self.backend.read_dir(path)?;
|
||||
self.backend.watch(path)?;
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
fn remove_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let _ = self.backend.unwatch(path);
|
||||
self.backend.remove_file(path)
|
||||
}
|
||||
|
||||
fn remove_dir_all<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let _ = self.backend.unwatch(path);
|
||||
self.backend.remove_dir_all(path)
|
||||
}
|
||||
|
||||
fn metadata<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Metadata> {
|
||||
let path = path.as_ref();
|
||||
self.backend.metadata(path)
|
||||
}
|
||||
|
||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||
self.backend.event_receiver()
|
||||
}
|
||||
|
||||
fn commit_event(&mut self, event: &VfsEvent) -> io::Result<()> {
|
||||
match event {
|
||||
VfsEvent::Remove(path) => {
|
||||
let _ = self.backend.unwatch(&path);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A virtual filesystem with a configurable backend.
|
||||
///
|
||||
/// All operations on the Vfs take a lock on an internal backend. For performing
|
||||
/// large batches of operations, it might be more performant to call `lock()`
|
||||
/// and use [`VfsLock`](struct.VfsLock.html) instead.
|
||||
pub struct Vfs {
|
||||
inner: Mutex<VfsInner>,
|
||||
}
|
||||
|
||||
impl Vfs {
|
||||
/// Creates a new `Vfs` with the default backend, `StdBackend`.
|
||||
pub fn new_default() -> Self {
|
||||
Self::new(StdBackend::new())
|
||||
}
|
||||
|
||||
/// Creates a new `Vfs` with the given backend.
|
||||
pub fn new<B: VfsBackend>(backend: B) -> Self {
|
||||
let lock = VfsInner {
|
||||
backend: Box::new(backend),
|
||||
};
|
||||
|
||||
Self {
|
||||
inner: Mutex::new(lock),
|
||||
}
|
||||
}
|
||||
|
||||
/// Manually lock the Vfs, useful for large batches of operations.
|
||||
pub fn lock(&self) -> VfsLock<'_> {
|
||||
VfsLock {
|
||||
inner: self.inner.lock().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a file from the VFS, or the underlying backend if it isn't
|
||||
/// resident.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::read`][std::fs::read].
|
||||
///
|
||||
/// [std::fs::read]: https://doc.rust-lang.org/stable/std/fs/fn.read.html
|
||||
#[inline]
|
||||
pub fn read<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<Vec<u8>>> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().read(path)
|
||||
}
|
||||
|
||||
/// Write a file to the VFS and the underlying backend.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::write`][std::fs::write].
|
||||
///
|
||||
/// [std::fs::write]: https://doc.rust-lang.org/stable/std/fs/fn.write.html
|
||||
#[inline]
|
||||
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(&self, path: P, contents: C) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let contents = contents.as_ref();
|
||||
self.inner.lock().unwrap().write(path, contents)
|
||||
}
|
||||
|
||||
/// Read all of the children of a directory.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::read_dir`][std::fs::read_dir].
|
||||
///
|
||||
/// [std::fs::read_dir]: https://doc.rust-lang.org/stable/std/fs/fn.read_dir.html
|
||||
#[inline]
|
||||
pub fn read_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<ReadDir> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().read_dir(path)
|
||||
}
|
||||
|
||||
/// Remove a file.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file].
|
||||
///
|
||||
/// [std::fs::remove_file]: https://doc.rust-lang.org/stable/std/fs/fn.remove_file.html
|
||||
#[inline]
|
||||
pub fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().remove_file(path)
|
||||
}
|
||||
|
||||
/// Remove a directory and all of its descendants.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::remove_dir_all`][std::fs::remove_dir_all].
|
||||
///
|
||||
/// [std::fs::remove_dir_all]: https://doc.rust-lang.org/stable/std/fs/fn.remove_dir_all.html
|
||||
#[inline]
|
||||
pub fn remove_dir_all<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().remove_dir_all(path)
|
||||
}
|
||||
|
||||
/// Query metadata about the given path.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::metadata`][std::fs::metadata].
|
||||
///
|
||||
/// [std::fs::metadata]: https://doc.rust-lang.org/stable/std/fs/fn.metadata.html
|
||||
#[inline]
|
||||
pub fn metadata<P: AsRef<Path>>(&self, path: P) -> io::Result<Metadata> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().metadata(path)
|
||||
}
|
||||
|
||||
/// Retrieve a handle to the event receiver for this `Vfs`.
|
||||
#[inline]
|
||||
pub fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||
self.inner.lock().unwrap().event_receiver()
|
||||
}
|
||||
|
||||
/// Commit an event to this `Vfs`.
|
||||
#[inline]
|
||||
pub fn commit_event(&self, event: &VfsEvent) -> io::Result<()> {
|
||||
self.inner.lock().unwrap().commit_event(event)
|
||||
}
|
||||
}
|
||||
|
||||
/// A locked handle to a [`Vfs`](struct.Vfs.html), created by `Vfs::lock`.
|
||||
///
|
||||
/// Implements roughly the same API as [`Vfs`](struct.Vfs.html).
|
||||
pub struct VfsLock<'a> {
|
||||
inner: MutexGuard<'a, VfsInner>,
|
||||
}
|
||||
|
||||
impl VfsLock<'_> {
|
||||
/// Read a file from the VFS, or the underlying backend if it isn't
|
||||
/// resident.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::read`][std::fs::read].
|
||||
///
|
||||
/// [std::fs::read]: https://doc.rust-lang.org/stable/std/fs/fn.read.html
|
||||
#[inline]
|
||||
pub fn read<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Arc<Vec<u8>>> {
|
||||
let path = path.as_ref();
|
||||
self.inner.read(path)
|
||||
}
|
||||
|
||||
/// Write a file to the VFS and the underlying backend.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::write`][std::fs::write].
|
||||
///
|
||||
/// [std::fs::write]: https://doc.rust-lang.org/stable/std/fs/fn.write.html
|
||||
#[inline]
|
||||
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(
|
||||
&mut self,
|
||||
path: P,
|
||||
contents: C,
|
||||
) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let contents = contents.as_ref();
|
||||
self.inner.write(path, contents)
|
||||
}
|
||||
|
||||
/// Read all of the children of a directory.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::read_dir`][std::fs::read_dir].
|
||||
///
|
||||
/// [std::fs::read_dir]: https://doc.rust-lang.org/stable/std/fs/fn.read_dir.html
|
||||
#[inline]
|
||||
pub fn read_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<ReadDir> {
|
||||
let path = path.as_ref();
|
||||
self.inner.read_dir(path)
|
||||
}
|
||||
|
||||
/// Remove a file.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file].
|
||||
///
|
||||
/// [std::fs::remove_file]: https://doc.rust-lang.org/stable/std/fs/fn.remove_file.html
|
||||
#[inline]
|
||||
pub fn remove_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.inner.remove_file(path)
|
||||
}
|
||||
|
||||
/// Remove a directory and all of its descendants.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::remove_dir_all`][std::fs::remove_dir_all].
|
||||
///
|
||||
/// [std::fs::remove_dir_all]: https://doc.rust-lang.org/stable/std/fs/fn.remove_dir_all.html
|
||||
#[inline]
|
||||
pub fn remove_dir_all<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.inner.remove_dir_all(path)
|
||||
}
|
||||
|
||||
/// Query metadata about the given path.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::metadata`][std::fs::metadata].
|
||||
///
|
||||
/// [std::fs::metadata]: https://doc.rust-lang.org/stable/std/fs/fn.metadata.html
|
||||
#[inline]
|
||||
pub fn metadata<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Metadata> {
|
||||
let path = path.as_ref();
|
||||
self.inner.metadata(path)
|
||||
}
|
||||
|
||||
/// Retrieve a handle to the event receiver for this `Vfs`.
|
||||
#[inline]
|
||||
pub fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||
self.inner.event_receiver()
|
||||
}
|
||||
|
||||
/// Commit an event to this `Vfs`.
|
||||
#[inline]
|
||||
pub fn commit_event(&mut self, event: &VfsEvent) -> io::Result<()> {
|
||||
self.inner.commit_event(event)
|
||||
}
|
||||
}
|
||||
76
memofs/src/noop_backend.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{Metadata, ReadDir, VfsBackend, VfsEvent};
|
||||
|
||||
/// `VfsBackend` that returns an error on every operation.
|
||||
#[non_exhaustive]
|
||||
pub struct NoopBackend;
|
||||
|
||||
impl NoopBackend {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl VfsBackend for NoopBackend {
|
||||
fn read(&mut self, _path: &Path) -> io::Result<Vec<u8>> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"NoopBackend doesn't do anything",
|
||||
))
|
||||
}
|
||||
|
||||
fn write(&mut self, _path: &Path, _data: &[u8]) -> io::Result<()> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"NoopBackend doesn't do anything",
|
||||
))
|
||||
}
|
||||
|
||||
fn read_dir(&mut self, _path: &Path) -> io::Result<ReadDir> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"NoopBackend doesn't do anything",
|
||||
))
|
||||
}
|
||||
|
||||
fn remove_file(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"NoopBackend doesn't do anything",
|
||||
))
|
||||
}
|
||||
|
||||
fn remove_dir_all(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"NoopBackend doesn't do anything",
|
||||
))
|
||||
}
|
||||
|
||||
fn metadata(&mut self, _path: &Path) -> io::Result<Metadata> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"NoopBackend doesn't do anything",
|
||||
))
|
||||
}
|
||||
|
||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||
crossbeam_channel::never()
|
||||
}
|
||||
|
||||
fn watch(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"NoopBackend doesn't do anything",
|
||||
))
|
||||
}
|
||||
|
||||
fn unwatch(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"NoopBackend doesn't do anything",
|
||||
))
|
||||
}
|
||||
}
|
||||
44
memofs/src/snapshot.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// A slice of a tree of files. Can be loaded into an
|
||||
/// [`InMemoryFs`](struct.InMemoryFs.html).
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum VfsSnapshot {
|
||||
File {
|
||||
contents: Vec<u8>,
|
||||
},
|
||||
|
||||
Dir {
|
||||
children: BTreeMap<String, VfsSnapshot>,
|
||||
},
|
||||
}
|
||||
|
||||
impl VfsSnapshot {
|
||||
pub fn file<C: Into<Vec<u8>>>(contents: C) -> Self {
|
||||
Self::File {
|
||||
contents: contents.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dir<K: Into<String>, I: IntoIterator<Item = (K, VfsSnapshot)>>(children: I) -> Self {
|
||||
Self::Dir {
|
||||
children: children
|
||||
.into_iter()
|
||||
.map(|(key, value)| (key.into(), value))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_file() -> Self {
|
||||
Self::File {
|
||||
contents: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_dir() -> Self {
|
||||
Self::Dir {
|
||||
children: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
110
memofs/src/std_backend.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
use notify::{watcher, DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
|
||||
use crate::{DirEntry, Metadata, ReadDir, VfsBackend, VfsEvent};
|
||||
|
||||
/// `VfsBackend` that uses `std::fs` and the `notify` crate.
|
||||
pub struct StdBackend {
|
||||
watcher: RecommendedWatcher,
|
||||
watcher_receiver: Receiver<VfsEvent>,
|
||||
}
|
||||
|
||||
impl StdBackend {
|
||||
pub fn new() -> StdBackend {
|
||||
let (notify_tx, notify_rx) = mpsc::channel();
|
||||
let watcher = watcher(notify_tx, Duration::from_millis(50)).unwrap();
|
||||
|
||||
let (tx, rx) = crossbeam_channel::unbounded();
|
||||
|
||||
thread::spawn(move || {
|
||||
for event in notify_rx {
|
||||
match event {
|
||||
DebouncedEvent::Create(path) => {
|
||||
tx.send(VfsEvent::Create(path))?;
|
||||
}
|
||||
DebouncedEvent::Write(path) => {
|
||||
tx.send(VfsEvent::Write(path))?;
|
||||
}
|
||||
DebouncedEvent::Remove(path) => {
|
||||
tx.send(VfsEvent::Remove(path))?;
|
||||
}
|
||||
DebouncedEvent::Rename(from, to) => {
|
||||
tx.send(VfsEvent::Remove(from))?;
|
||||
tx.send(VfsEvent::Create(to))?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Result::<(), crossbeam_channel::SendError<VfsEvent>>::Ok(())
|
||||
});
|
||||
|
||||
Self {
|
||||
watcher,
|
||||
watcher_receiver: rx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VfsBackend for StdBackend {
|
||||
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>> {
|
||||
fs_err::read(path)
|
||||
}
|
||||
|
||||
fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()> {
|
||||
fs_err::write(path, data)
|
||||
}
|
||||
|
||||
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
|
||||
let entries: Result<Vec<_>, _> = fs_err::read_dir(path)?.collect();
|
||||
let mut entries = entries?;
|
||||
|
||||
entries.sort_by_cached_key(|entry| entry.file_name());
|
||||
|
||||
let inner = entries
|
||||
.into_iter()
|
||||
.map(|entry| Ok(DirEntry { path: entry.path() }));
|
||||
|
||||
Ok(ReadDir {
|
||||
inner: Box::new(inner),
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
|
||||
fs_err::remove_file(path)
|
||||
}
|
||||
|
||||
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()> {
|
||||
fs_err::remove_dir_all(path)
|
||||
}
|
||||
|
||||
fn metadata(&mut self, path: &Path) -> io::Result<Metadata> {
|
||||
let inner = fs_err::metadata(path)?;
|
||||
|
||||
Ok(Metadata {
|
||||
is_file: inner.is_file(),
|
||||
})
|
||||
}
|
||||
|
||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||
self.watcher_receiver.clone()
|
||||
}
|
||||
|
||||
fn watch(&mut self, path: &Path) -> io::Result<()> {
|
||||
self.watcher
|
||||
.watch(path, RecursiveMode::NonRecursive)
|
||||
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner))
|
||||
}
|
||||
|
||||
fn unwatch(&mut self, path: &Path) -> io::Result<()> {
|
||||
self.watcher
|
||||
.unwatch(path)
|
||||
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner))
|
||||
}
|
||||
}
|
||||
31
mkdocs.yml
@@ -1,31 +0,0 @@
|
||||
site_name: Rojo Documentation
|
||||
repo_name: LPGhatguy/rojo
|
||||
repo_url: https://github.com/LPGhatguy/rojo
|
||||
|
||||
theme:
|
||||
name: material
|
||||
palette:
|
||||
primary: 'Red'
|
||||
accent: 'Red'
|
||||
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Why Rojo?: why-rojo.md
|
||||
- Installation: installation.md
|
||||
- Creating a Place with Rojo: creating-a-place.md
|
||||
- Migrating from 0.4.x to 0.5.x: migrating-to-epiphany.md
|
||||
- Project Format: project-format.md
|
||||
- Sync Details: sync-details.md
|
||||
- Rojo Internals:
|
||||
- Internals Overview: internals/overview.md
|
||||
|
||||
extra_css:
|
||||
- extra.css
|
||||
|
||||
markdown_extensions:
|
||||
- attr_list
|
||||
- admonition
|
||||
- codehilite:
|
||||
guess_lang: false
|
||||
- toc:
|
||||
permalink: true
|
||||
1
plugin/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/luacov.*
|
||||
@@ -1,8 +0,0 @@
|
||||
return {
|
||||
include = {
|
||||
"^src",
|
||||
},
|
||||
exclude = {
|
||||
"%.spec$",
|
||||
},
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
# Rojo Plugin
|
||||
|
||||
This is the source to the Rojo Roblox Studio plugin.
|
||||
|
||||
Documentation is WIP.
|
||||
@@ -5,17 +5,26 @@
|
||||
"Plugin": {
|
||||
"$path": "src"
|
||||
},
|
||||
"Log": {
|
||||
"$path": "log"
|
||||
},
|
||||
"Http": {
|
||||
"$path": "http"
|
||||
},
|
||||
"Fmt": {
|
||||
"$path": "fmt"
|
||||
},
|
||||
"Roact": {
|
||||
"$path": "modules/roact/lib"
|
||||
},
|
||||
"Rodux": {
|
||||
"$path": "modules/rodux/lib"
|
||||
},
|
||||
"RoactRodux": {
|
||||
"$path": "modules/roact-rodux/lib"
|
||||
"$path": "modules/roact/src"
|
||||
},
|
||||
"Promise": {
|
||||
"$path": "modules/promise/lib"
|
||||
},
|
||||
"t": {
|
||||
"$path": "modules/t/lib"
|
||||
},
|
||||
"RbxDom": {
|
||||
"$path": "modules/rbx-dom/rbx_dom_lua/src"
|
||||
}
|
||||
}
|
||||
}
|
||||
245
plugin/fmt/init.lua
Normal file
@@ -0,0 +1,245 @@
|
||||
--[[
|
||||
This library describes a formatting mechanism akin to Rust's std::fmt.
|
||||
|
||||
It has a couple building blocks:
|
||||
|
||||
* A new syntax for formatting strings, taken verbatim from Rust. It'd also
|
||||
be possible to use printf-style formatting specifiers to integrate with
|
||||
the existing string.format utility.
|
||||
|
||||
* An equivalent to Rust's `Display` trait. We're mapping the semantics of
|
||||
tostring and the __tostring metamethod onto this trait. A lot of types
|
||||
should already have __tostring implementations, too!
|
||||
|
||||
* An equivalent to Rust's `Debug` trait. This library Lua-ifies that idea by
|
||||
inventing a new metamethod, `__fmtDebug`. We pass along the "extended
|
||||
form" attribute which is the equivalent of the "alternate mode" in Rust's
|
||||
Debug trait since it's the author's opinion that treating it as a
|
||||
verbosity flag is semantically accurate.
|
||||
]]
|
||||
|
||||
--[[
|
||||
The default implementation of __fmtDebug for tables when the extended option
|
||||
is not set.
|
||||
]]
|
||||
local function defaultTableDebug(buffer, input)
|
||||
buffer:writeRaw("{")
|
||||
|
||||
for key, value in pairs(input) do
|
||||
buffer:write("[{:?}] = {:?}", key, value)
|
||||
|
||||
if next(input, key) ~= nil then
|
||||
buffer:writeRaw(", ")
|
||||
end
|
||||
end
|
||||
|
||||
buffer:writeRaw("}")
|
||||
end
|
||||
|
||||
--[[
|
||||
The default implementation of __fmtDebug for tables with the extended option
|
||||
set.
|
||||
]]
|
||||
local function defaultTableDebugExtended(buffer, input)
|
||||
-- Special case for empty tables.
|
||||
if next(input) == nil then
|
||||
buffer:writeRaw("{}")
|
||||
return
|
||||
end
|
||||
|
||||
buffer:writeLineRaw("{")
|
||||
buffer:indent()
|
||||
|
||||
for key, value in pairs(input) do
|
||||
buffer:writeLine("[{:?}] = {:#?},", key, value)
|
||||
end
|
||||
|
||||
buffer:unindent()
|
||||
buffer:writeRaw("}")
|
||||
end
|
||||
|
||||
--[[
|
||||
The default debug representation for all types.
|
||||
]]
|
||||
local function debugImpl(buffer, value, extendedForm)
|
||||
local valueType = typeof(value)
|
||||
|
||||
if valueType == "string" then
|
||||
local formatted = string.format("%q", value)
|
||||
buffer:writeRaw(formatted)
|
||||
elseif valueType == "table" then
|
||||
local valueMeta = getmetatable(value)
|
||||
|
||||
if valueMeta ~= nil and valueMeta.__fmtDebug ~= nil then
|
||||
-- This type implement's the metamethod we made up to line up with
|
||||
-- Rust's 'Debug' trait.
|
||||
|
||||
valueMeta.__fmtDebug(value, buffer, extendedForm)
|
||||
else
|
||||
if extendedForm then
|
||||
defaultTableDebugExtended(buffer, value)
|
||||
else
|
||||
defaultTableDebug(buffer, value)
|
||||
end
|
||||
end
|
||||
elseif valueType == "Instance" then
|
||||
buffer:writeRaw(value:GetFullName())
|
||||
else
|
||||
buffer:writeRaw(tostring(value))
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Defines and implements the library's template syntax.
|
||||
]]
|
||||
local function writeFmt(buffer, template, ...)
|
||||
local currentArg = 0
|
||||
local i = 1
|
||||
local len = #template
|
||||
|
||||
while i <= len do
|
||||
local openBrace = template:find("{", i)
|
||||
|
||||
if openBrace == nil then
|
||||
-- There are no remaining open braces in this string, so we can
|
||||
-- write the rest of the string to the buffer.
|
||||
|
||||
buffer:writeRaw(template:sub(i))
|
||||
break
|
||||
else
|
||||
-- We found an open brace! This could be:
|
||||
-- - A literal '{', written as '{{'
|
||||
-- - The beginning of an interpolation, like '{}'
|
||||
-- - An error, if there's no matching '}'
|
||||
|
||||
local charAfterBrace = template:sub(openBrace + 1, openBrace + 1)
|
||||
if charAfterBrace == "{" then
|
||||
-- This is a literal brace, so we'll write everything up to this
|
||||
-- point (including the first brace), and then skip over the
|
||||
-- second brace.
|
||||
|
||||
buffer:writeRaw(template:sub(i, openBrace))
|
||||
i = openBrace + 2
|
||||
else
|
||||
-- This SHOULD be an interpolation. We'll find our matching
|
||||
-- brace and treat the contents as the formatting specifier.
|
||||
|
||||
-- If there were any unwritten characters before this
|
||||
-- interpolation, write them to the buffer.
|
||||
if openBrace - i > 0 then
|
||||
buffer:writeRaw(template:sub(i, openBrace - 1))
|
||||
end
|
||||
|
||||
local closeBrace = template:find("}", openBrace + 1)
|
||||
assert(closeBrace ~= nil, "Unclosed formatting specifier. Use '{{' to write an open brace.")
|
||||
|
||||
local formatSpecifier = template:sub(openBrace + 1, closeBrace - 1)
|
||||
currentArg = currentArg + 1
|
||||
local arg = select(currentArg, ...)
|
||||
|
||||
if formatSpecifier == "" then
|
||||
-- This should use the equivalent of Rust's 'Display', ie
|
||||
-- tostring and the __tostring metamethod.
|
||||
|
||||
buffer:writeRaw(tostring(arg))
|
||||
elseif formatSpecifier == ":?" then
|
||||
-- This should use the equivalent of Rust's 'Debug',
|
||||
-- invented for this library as __fmtDebug.
|
||||
|
||||
debugImpl(buffer, arg, false)
|
||||
elseif formatSpecifier == ":#?" then
|
||||
-- This should use the equivlant of Rust's 'Debug' with the
|
||||
-- 'alternate' (ie expanded) flag set.
|
||||
|
||||
debugImpl(buffer, arg, true)
|
||||
else
|
||||
error("unsupported format specifier " .. formatSpecifier, 2)
|
||||
end
|
||||
|
||||
i = closeBrace + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function debugOutputBuffer()
|
||||
local buffer = {}
|
||||
local startOfLine = true
|
||||
local indentLevel = 0
|
||||
local indentation = ""
|
||||
|
||||
function buffer:writeLine(template, ...)
|
||||
writeFmt(self, template, ...)
|
||||
self:nextLine()
|
||||
end
|
||||
|
||||
function buffer:writeLineRaw(value)
|
||||
self:writeRaw(value)
|
||||
self:nextLine()
|
||||
end
|
||||
|
||||
function buffer:write(template, ...)
|
||||
return writeFmt(self, template, ...)
|
||||
end
|
||||
|
||||
function buffer:writeRaw(value)
|
||||
if #value > 0 then
|
||||
if startOfLine and #indentation > 0 then
|
||||
startOfLine = false
|
||||
table.insert(self, indentation)
|
||||
end
|
||||
|
||||
table.insert(self, value)
|
||||
startOfLine = false
|
||||
end
|
||||
end
|
||||
|
||||
function buffer:nextLine()
|
||||
table.insert(self, "\n")
|
||||
startOfLine = true
|
||||
end
|
||||
|
||||
function buffer:indent()
|
||||
indentLevel = indentLevel + 1
|
||||
indentation = string.rep(" ", indentLevel)
|
||||
end
|
||||
|
||||
function buffer:unindent()
|
||||
indentLevel = math.max(0, indentLevel - 1)
|
||||
indentation = string.rep(" ", indentLevel)
|
||||
end
|
||||
|
||||
function buffer:finish()
|
||||
return table.concat(self, "")
|
||||
end
|
||||
|
||||
return buffer
|
||||
end
|
||||
|
||||
local function fmt(template, ...)
|
||||
local buffer = debugOutputBuffer()
|
||||
writeFmt(buffer, template, ...)
|
||||
return buffer:finish()
|
||||
end
|
||||
|
||||
--[[
|
||||
Wrap the given object in a type that implements the given function as its
|
||||
Debug implementation, and forwards __tostring to the type's underlying
|
||||
tostring implementation.
|
||||
]]
|
||||
local function debugify(object, fmtFunc)
|
||||
return setmetatable({}, {
|
||||
__fmtDebug = function(_, ...)
|
||||
return fmtFunc(object, ...)
|
||||
end,
|
||||
__tostring = function()
|
||||
return tostring(object)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return {
|
||||
debugOutputBuffer = debugOutputBuffer,
|
||||
fmt = fmt,
|
||||
debugify = debugify,
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
local Logging = require(script.Parent.Logging)
|
||||
local Error = {}
|
||||
Error.__index = Error
|
||||
|
||||
local HttpError = {}
|
||||
HttpError.__index = HttpError
|
||||
|
||||
HttpError.Error = {
|
||||
Error.Kind = {
|
||||
HttpNotEnabled = {
|
||||
message = "Rojo requires HTTP access, which is not enabled.\n" ..
|
||||
"Check your game settings, located in the 'Home' tab of Studio.",
|
||||
@@ -13,20 +11,20 @@ HttpError.Error = {
|
||||
"Make sure the server is running -- use 'rojo serve' to run it!",
|
||||
},
|
||||
Timeout = {
|
||||
message = "Request timed out.",
|
||||
message = "HTTP request timed out.",
|
||||
},
|
||||
Unknown = {
|
||||
message = "Unknown error: {{message}}",
|
||||
message = "Unknown HTTP error: {{message}}",
|
||||
},
|
||||
}
|
||||
|
||||
setmetatable(HttpError.Error, {
|
||||
setmetatable(Error.Kind, {
|
||||
__index = function(_, key)
|
||||
error(("%q is not a valid member of HttpError.Error"):format(tostring(key)), 2)
|
||||
error(("%q is not a valid member of Http.Error.Kind"):format(tostring(key)), 2)
|
||||
end,
|
||||
})
|
||||
|
||||
function HttpError.new(type, extraMessage)
|
||||
function Error.new(type, extraMessage)
|
||||
extraMessage = extraMessage or ""
|
||||
local message = type.message:gsub("{{message}}", extraMessage)
|
||||
|
||||
@@ -35,38 +33,34 @@ function HttpError.new(type, extraMessage)
|
||||
message = message,
|
||||
}
|
||||
|
||||
setmetatable(err, HttpError)
|
||||
setmetatable(err, Error)
|
||||
|
||||
return err
|
||||
end
|
||||
|
||||
function HttpError:__tostring()
|
||||
function Error:__tostring()
|
||||
return self.message
|
||||
end
|
||||
|
||||
--[[
|
||||
This method shouldn't have to exist. Ugh.
|
||||
]]
|
||||
function HttpError.fromErrorString(message)
|
||||
function Error.fromRobloxErrorString(message)
|
||||
local lower = message:lower()
|
||||
|
||||
if lower:find("^http requests are not enabled") then
|
||||
return HttpError.new(HttpError.Error.HttpNotEnabled)
|
||||
return Error.new(Error.Kind.HttpNotEnabled)
|
||||
end
|
||||
|
||||
if lower:find("^httperror: timedout") then
|
||||
return HttpError.new(HttpError.Error.Timeout)
|
||||
return Error.new(Error.Kind.Timeout)
|
||||
end
|
||||
|
||||
if lower:find("^httperror: connectfail") then
|
||||
return HttpError.new(HttpError.Error.ConnectFailed)
|
||||
return Error.new(Error.Kind.ConnectFailed)
|
||||
end
|
||||
|
||||
return HttpError.new(HttpError.Error.Unknown, message)
|
||||
return Error.new(Error.Kind.Unknown, message)
|
||||
end
|
||||
|
||||
function HttpError:report()
|
||||
Logging.warn(self.message)
|
||||
end
|
||||
|
||||
return HttpError
|
||||
return Error
|
||||
@@ -1,34 +1,34 @@
|
||||
local HttpService = game:GetService("HttpService")
|
||||
|
||||
local stringTemplate = [[
|
||||
HttpResponse {
|
||||
Http.Response {
|
||||
code: %d
|
||||
body: %s
|
||||
}]]
|
||||
|
||||
local HttpResponse = {}
|
||||
HttpResponse.__index = HttpResponse
|
||||
local Response = {}
|
||||
Response.__index = Response
|
||||
|
||||
function HttpResponse:__tostring()
|
||||
function Response:__tostring()
|
||||
return stringTemplate:format(self.code, self.body)
|
||||
end
|
||||
|
||||
function HttpResponse.fromRobloxResponse(response)
|
||||
function Response.fromRobloxResponse(response)
|
||||
local self = {
|
||||
body = response.Body,
|
||||
code = response.StatusCode,
|
||||
headers = response.Headers,
|
||||
}
|
||||
|
||||
return setmetatable(self, HttpResponse)
|
||||
return setmetatable(self, Response)
|
||||
end
|
||||
|
||||
function HttpResponse:isSuccess()
|
||||
function Response:isSuccess()
|
||||
return self.code >= 200 and self.code < 300
|
||||
end
|
||||
|
||||
function HttpResponse:json()
|
||||
function Response:json()
|
||||
return HttpService:JSONDecode(self.body)
|
||||
end
|
||||
|
||||
return HttpResponse
|
||||
return Response
|
||||
66
plugin/http/init.lua
Normal file
@@ -0,0 +1,66 @@
|
||||
local HttpService = game:GetService("HttpService")
|
||||
|
||||
local Promise = require(script.Parent.Promise)
|
||||
local Log = require(script.Parent.Log)
|
||||
|
||||
local HttpError = require(script.Error)
|
||||
local HttpResponse = require(script.Response)
|
||||
|
||||
local lastRequestId = 0
|
||||
|
||||
local Http = {}
|
||||
|
||||
Http.Error = HttpError
|
||||
Http.Response = HttpResponse
|
||||
|
||||
local function performRequest(requestParams)
|
||||
local requestId = lastRequestId + 1
|
||||
lastRequestId = requestId
|
||||
|
||||
Log.trace("HTTP {}({}) {}", requestParams.Method, requestId, requestParams.Url)
|
||||
|
||||
if requestParams.Body ~= nil then
|
||||
Log.trace("{}", requestParams.Body)
|
||||
end
|
||||
|
||||
return Promise.new(function(resolve, reject)
|
||||
coroutine.wrap(function()
|
||||
local success, response = pcall(function()
|
||||
return HttpService:RequestAsync(requestParams)
|
||||
end)
|
||||
|
||||
if success then
|
||||
Log.trace("Request {} success, status code {}", requestId, response.StatusCode)
|
||||
resolve(HttpResponse.fromRobloxResponse(response))
|
||||
else
|
||||
Log.trace("Request {} failure: {:?}", requestId, response)
|
||||
reject(HttpError.fromRobloxErrorString(response))
|
||||
end
|
||||
end)()
|
||||
end)
|
||||
end
|
||||
|
||||
function Http.get(url)
|
||||
return performRequest({
|
||||
Url = url,
|
||||
Method = "GET",
|
||||
})
|
||||
end
|
||||
|
||||
function Http.post(url, body)
|
||||
return performRequest({
|
||||
Url = url,
|
||||
Method = "POST",
|
||||
Body = body,
|
||||
})
|
||||
end
|
||||
|
||||
function Http.jsonEncode(object)
|
||||
return HttpService:JSONEncode(object)
|
||||
end
|
||||
|
||||
function Http.jsonDecode(source)
|
||||
return HttpService:JSONDecode(source)
|
||||
end
|
||||
|
||||
return Http
|
||||
5
plugin/http/init.spec.lua
Normal file
@@ -0,0 +1,5 @@
|
||||
return function()
|
||||
it("should load", function()
|
||||
require(script.Parent)
|
||||
end)
|
||||
end
|
||||
@@ -1,37 +0,0 @@
|
||||
--[[
|
||||
Loads the Rojo plugin and all of its dependencies.
|
||||
]]
|
||||
|
||||
local function loadEnvironment()
|
||||
-- If you add any dependencies, add them to this table so they'll be loaded!
|
||||
local LOAD_MODULES = {
|
||||
{"src", "Rojo"},
|
||||
{"modules/promise/lib", "Promise"},
|
||||
{"modules/testez/lib", "TestEZ"},
|
||||
}
|
||||
|
||||
-- This makes sure we can load Lemur and other libraries that depend on init.lua
|
||||
package.path = package.path .. ";?/init.lua"
|
||||
|
||||
-- If this fails, make sure you've run `lua bin/install-dependencies.lua` first!
|
||||
local lemur = require("modules.lemur")
|
||||
|
||||
-- Create a virtual Roblox tree
|
||||
local habitat = lemur.Habitat.new()
|
||||
|
||||
-- We'll put all of our library code and dependencies here
|
||||
local modules = lemur.Instance.new("Folder")
|
||||
modules.Name = "Modules"
|
||||
modules.Parent = habitat.game:GetService("ReplicatedStorage")
|
||||
|
||||
-- Load all of the modules specified above
|
||||
for _, module in ipairs(LOAD_MODULES) do
|
||||
local container = habitat:loadFromFs(module[1])
|
||||
container.Name = module[2]
|
||||
container.Parent = modules
|
||||
end
|
||||
|
||||
return habitat, modules
|
||||
end
|
||||
|
||||
return loadEnvironment
|
||||
@@ -1,49 +1,55 @@
|
||||
local DevSettings = require(script.Parent.DevSettings)
|
||||
local Fmt = require(script.Parent.Fmt)
|
||||
|
||||
local Level = {
|
||||
Error = 0,
|
||||
Warning = 1,
|
||||
Info = 2,
|
||||
Trace = 3,
|
||||
Debug = 3,
|
||||
Trace = 4,
|
||||
}
|
||||
|
||||
local testLogLevel = nil
|
||||
|
||||
local function getLogLevel()
|
||||
if testLogLevel ~= nil then
|
||||
return testLogLevel
|
||||
end
|
||||
|
||||
return DevSettings:getLogLevel()
|
||||
return Level.Info
|
||||
end
|
||||
|
||||
local function addTags(tag, message)
|
||||
return tag .. message:gsub("\n", "\n" .. tag)
|
||||
end
|
||||
|
||||
local INFO_TAG = (" "):rep(15) .. "[Rojo-Info] "
|
||||
local TRACE_TAG = (" "):rep(15) .. "[Rojo-Trace] "
|
||||
local INFO_TAG = (" "):rep(15) .. "[Rojo-Info] "
|
||||
local DEBUG_TAG = (" "):rep(15) .. "[Rojo-Debug] "
|
||||
local WARN_TAG = "[Rojo-Warn] "
|
||||
|
||||
local Log = {}
|
||||
|
||||
Log.Level = Level
|
||||
|
||||
function Log.setLogLevelThunk(thunk)
|
||||
getLogLevel = thunk
|
||||
end
|
||||
|
||||
function Log.trace(template, ...)
|
||||
if getLogLevel() >= Level.Trace then
|
||||
print(addTags(TRACE_TAG, string.format(template, ...)))
|
||||
print(addTags(TRACE_TAG, Fmt.fmt(template, ...)))
|
||||
end
|
||||
end
|
||||
|
||||
function Log.info(template, ...)
|
||||
if getLogLevel() >= Level.Info then
|
||||
print(addTags(INFO_TAG, string.format(template, ...)))
|
||||
print(addTags(INFO_TAG, Fmt.fmt(template, ...)))
|
||||
end
|
||||
end
|
||||
|
||||
function Log.debug(template, ...)
|
||||
if getLogLevel() >= Level.Debug then
|
||||
print(addTags(DEBUG_TAG, Fmt.fmt(template, ...)))
|
||||
end
|
||||
end
|
||||
|
||||
function Log.warn(template, ...)
|
||||
if getLogLevel() >= Level.Warning then
|
||||
warn(addTags(WARN_TAG, string.format(template, ...)))
|
||||
warn(addTags(WARN_TAG, Fmt.fmt(template, ...)))
|
||||
end
|
||||
end
|
||||
|
||||
5
plugin/log/init.spec.lua
Normal file
@@ -0,0 +1,5 @@
|
||||
return function()
|
||||
it("should load", function()
|
||||
require(script.Parent)
|
||||
end)
|
||||
end
|
||||
1
plugin/modules/rbx-dom
Submodule
1
plugin/modules/t
Submodule
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"name": "rojo",
|
||||
"tree": {
|
||||
"$className": "DataModel",
|
||||
|
||||
"ReplicatedStorage": {
|
||||
"$className": "ReplicatedStorage",
|
||||
|
||||
"Rojo": {
|
||||
"$className": "Folder",
|
||||
|
||||
"Plugin": {
|
||||
"$path": "src"
|
||||
},
|
||||
"Roact": {
|
||||
"$path": "modules/roact/lib"
|
||||
},
|
||||
"Rodux": {
|
||||
"$path": "modules/rodux/lib"
|
||||
},
|
||||
"RoactRodux": {
|
||||
"$path": "modules/roact-rodux/lib"
|
||||
},
|
||||
"Promise": {
|
||||
"$path": "modules/promise/lib"
|
||||
}
|
||||
},
|
||||
"TestEZ": {
|
||||
"$path": "modules/testez/lib"
|
||||
}
|
||||
},
|
||||
|
||||
"HttpService": {
|
||||
"$className": "HttpService",
|
||||
"$properties": {
|
||||
"HttpEnabled": {
|
||||
"Type": "Bool",
|
||||
"Value": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"TestService": {
|
||||
"$className": "TestService",
|
||||
|
||||
"TestBootstrap": {
|
||||
"$path": "testBootstrap.server.lua"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"name": "rojo",
|
||||
"servePort": 8000,
|
||||
"partitions": {
|
||||
"plugin": {
|
||||
"path": "src",
|
||||
"target": "ReplicatedStorage.Rojo.Plugin"
|
||||
},
|
||||
"modules/roact": {
|
||||
"path": "modules/roact/lib",
|
||||
"target": "ReplicatedStorage.Rojo.Roact"
|
||||
},
|
||||
"modules/rodux": {
|
||||
"path": "modules/rodux/lib",
|
||||
"target": "ReplicatedStorage.Rojo.Rodux"
|
||||
},
|
||||
"modules/roact-rodux": {
|
||||
"path": "modules/roact-rodux/lib",
|
||||
"target": "ReplicatedStorage.Rojo.RoactRodux"
|
||||
},
|
||||
"modules/promise": {
|
||||
"path": "modules/promise/lib",
|
||||
"target": "ReplicatedStorage.Rojo.Promise"
|
||||
},
|
||||
"modules/testez": {
|
||||
"path": "modules/testez/lib",
|
||||
"target": "ReplicatedStorage.TestEZ"
|
||||
},
|
||||
"tests": {
|
||||
"path": "testBootstrap.server.lua",
|
||||
"target": "TestService.testBootstrap"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
local loadEnvironment = require("loadEnvironment")
|
||||
|
||||
local testPath = assert((...), "Please specify a path to a test file.")
|
||||
|
||||
local habitat = loadEnvironment()
|
||||
|
||||
local testModule = habitat:loadFromFs(testPath)
|
||||
|
||||
if testModule == nil then
|
||||
error("Couldn't find test file at " .. testPath)
|
||||
end
|
||||
|
||||
print("Starting test module.")
|
||||
|
||||
habitat:require(testModule)
|
||||
@@ -1,17 +0,0 @@
|
||||
--[[
|
||||
Loads our library and all of its dependencies, then runs tests using TestEZ.
|
||||
]]
|
||||
|
||||
local loadEnvironment = require("loadEnvironment")
|
||||
|
||||
local habitat, modules = loadEnvironment()
|
||||
|
||||
-- Load TestEZ and run our tests
|
||||
local TestEZ = habitat:require(modules.TestEZ)
|
||||
|
||||
local results = TestEZ.TestBootstrap:run({modules.Rojo}, TestEZ.Reporters.TextReporter)
|
||||
|
||||
-- Did something go wrong?
|
||||
if results.failureCount > 0 then
|
||||
os.exit(1)
|
||||
end
|
||||
@@ -1,150 +1,216 @@
|
||||
local Http = require(script.Parent.Parent.Http)
|
||||
local Log = require(script.Parent.Parent.Log)
|
||||
local Promise = require(script.Parent.Parent.Promise)
|
||||
|
||||
local Config = require(script.Parent.Config)
|
||||
local Types = require(script.Parent.Types)
|
||||
local Version = require(script.Parent.Version)
|
||||
local Http = require(script.Parent.Http)
|
||||
local HttpError = require(script.Parent.HttpError)
|
||||
|
||||
local ApiContext = {}
|
||||
ApiContext.__index = ApiContext
|
||||
local validateApiInfo = Types.ifEnabled(Types.ApiInfoResponse)
|
||||
local validateApiRead = Types.ifEnabled(Types.ApiReadResponse)
|
||||
local validateApiSubscribe = Types.ifEnabled(Types.ApiSubscribeResponse)
|
||||
|
||||
-- TODO: Audit cases of errors and create enum values for each of them.
|
||||
ApiContext.Error = {
|
||||
ServerIdMismatch = "ServerIdMismatch",
|
||||
|
||||
-- The server gave an unexpected 400-category error, which may be the
|
||||
-- client's fault.
|
||||
ClientError = "ClientError",
|
||||
|
||||
-- The server gave an unexpected 500-category error, which may be the
|
||||
-- server's fault.
|
||||
ServerError = "ServerError",
|
||||
}
|
||||
|
||||
setmetatable(ApiContext.Error, {
|
||||
__index = function(_, key)
|
||||
error("Invalid ApiContext.Error name " .. key, 2)
|
||||
end
|
||||
})
|
||||
--[[
|
||||
Returns a promise that will never resolve nor reject.
|
||||
]]
|
||||
local function hangingPromise()
|
||||
return Promise.new(function() end)
|
||||
end
|
||||
|
||||
local function rejectFailedRequests(response)
|
||||
if response.code >= 400 then
|
||||
if response.code < 500 then
|
||||
return Promise.reject(ApiContext.Error.ClientError)
|
||||
else
|
||||
return Promise.reject(ApiContext.Error.ServerError)
|
||||
end
|
||||
local message = string.format("HTTP %s:\n%s", tostring(response.code), response.body)
|
||||
|
||||
return Promise.reject(message)
|
||||
end
|
||||
|
||||
return response
|
||||
end
|
||||
|
||||
local function rejectWrongProtocolVersion(infoResponseBody)
|
||||
if infoResponseBody.protocolVersion ~= Config.protocolVersion then
|
||||
local message = (
|
||||
"Found a Rojo dev server, but it's using a different protocol version, and is incompatible." ..
|
||||
"\nMake sure you have matching versions of both the Rojo plugin and server!" ..
|
||||
"\n\nYour client is version %s, with protocol version %s. It expects server version %s." ..
|
||||
"\nYour server is version %s, with protocol version %s." ..
|
||||
"\n\nGo to https://github.com/rojo-rbx/rojo for more details."
|
||||
):format(
|
||||
Version.display(Config.version), Config.protocolVersion,
|
||||
Config.expectedServerVersionString,
|
||||
infoResponseBody.serverVersion, infoResponseBody.protocolVersion
|
||||
)
|
||||
|
||||
return Promise.reject(message)
|
||||
end
|
||||
|
||||
return Promise.resolve(infoResponseBody)
|
||||
end
|
||||
|
||||
local function rejectWrongPlaceId(infoResponseBody)
|
||||
if infoResponseBody.expectedPlaceIds ~= nil then
|
||||
local foundId = false
|
||||
|
||||
for _, id in ipairs(infoResponseBody.expectedPlaceIds) do
|
||||
if id == game.PlaceId then
|
||||
foundId = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not foundId then
|
||||
local idList = {}
|
||||
for _, id in ipairs(infoResponseBody.expectedPlaceIds) do
|
||||
table.insert(idList, "- " .. tostring(id))
|
||||
end
|
||||
|
||||
local message = (
|
||||
"Found a Rojo server, but its project is set to only be used with a specific list of places." ..
|
||||
"\nYour place ID is %s, but needs to be one of these:" ..
|
||||
"\n%s" ..
|
||||
"\n\nTo change this list, edit 'servePlaceIds' in your .project.json file."
|
||||
):format(
|
||||
tostring(game.PlaceId),
|
||||
table.concat(idList, "\n")
|
||||
)
|
||||
|
||||
return Promise.reject(message)
|
||||
end
|
||||
end
|
||||
|
||||
return Promise.resolve(infoResponseBody)
|
||||
end
|
||||
|
||||
local ApiContext = {}
|
||||
ApiContext.__index = ApiContext
|
||||
|
||||
function ApiContext.new(baseUrl)
|
||||
assert(type(baseUrl) == "string")
|
||||
|
||||
local self = {
|
||||
baseUrl = baseUrl,
|
||||
serverId = nil,
|
||||
rootInstanceId = nil,
|
||||
messageCursor = -1,
|
||||
partitionRoutes = nil,
|
||||
__baseUrl = baseUrl,
|
||||
__sessionId = nil,
|
||||
__messageCursor = -1,
|
||||
__connected = true,
|
||||
}
|
||||
|
||||
setmetatable(self, ApiContext)
|
||||
|
||||
return self
|
||||
return setmetatable(self, ApiContext)
|
||||
end
|
||||
|
||||
function ApiContext:onMessage(callback)
|
||||
self.onMessageCallback = callback
|
||||
function ApiContext:__fmtDebug(output)
|
||||
output:writeLine("ApiContext {{")
|
||||
output:indent()
|
||||
|
||||
output:writeLine("Connected: {}", self.__connected)
|
||||
output:writeLine("Base URL: {}", self.__baseUrl)
|
||||
output:writeLine("Session ID: {}", self.__sessionId)
|
||||
output:writeLine("Message Cursor: {}", self.__messageCursor)
|
||||
|
||||
output:unindent()
|
||||
output:write("}")
|
||||
end
|
||||
|
||||
function ApiContext:disconnect()
|
||||
self.__connected = false
|
||||
end
|
||||
|
||||
function ApiContext:setMessageCursor(index)
|
||||
self.__messageCursor = index
|
||||
end
|
||||
|
||||
function ApiContext:connect()
|
||||
local url = ("%s/api/rojo"):format(self.baseUrl)
|
||||
local url = ("%s/api/rojo"):format(self.__baseUrl)
|
||||
|
||||
return Http.get(url)
|
||||
:andThen(rejectFailedRequests)
|
||||
:andThen(function(response)
|
||||
local body = response:json()
|
||||
:andThen(Http.Response.json)
|
||||
:andThen(rejectWrongProtocolVersion)
|
||||
:andThen(function(body)
|
||||
assert(validateApiInfo(body))
|
||||
|
||||
if body.protocolVersion ~= Config.protocolVersion then
|
||||
local message = (
|
||||
"Found a Rojo dev server, but it's using a different protocol version, and is incompatible." ..
|
||||
"\nMake sure you have matching versions of both the Rojo plugin and server!" ..
|
||||
"\n\nYour client is version %s, with protocol version %s. It expects server version %s." ..
|
||||
"\nYour server is version %s, with protocol version %s." ..
|
||||
"\n\nGo to https://github.com/LPGhatguy/rojo for more details."
|
||||
):format(
|
||||
Version.display(Config.version), Config.protocolVersion,
|
||||
Config.expectedApiContextVersionString,
|
||||
body.serverVersion, body.protocolVersion
|
||||
)
|
||||
return body
|
||||
end)
|
||||
:andThen(rejectWrongPlaceId)
|
||||
:andThen(function(body)
|
||||
self.__sessionId = body.sessionId
|
||||
|
||||
return Promise.reject(message)
|
||||
end
|
||||
|
||||
if body.expectedPlaceIds ~= nil then
|
||||
local foundId = false
|
||||
|
||||
for _, id in ipairs(body.expectedPlaceIds) do
|
||||
if id == game.PlaceId then
|
||||
foundId = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not foundId then
|
||||
local idList = {}
|
||||
for _, id in ipairs(body.expectedPlaceIds) do
|
||||
table.insert(idList, "- " .. tostring(id))
|
||||
end
|
||||
|
||||
local message = (
|
||||
"Found a Rojo server, but its project is set to only be used with a specific list of places." ..
|
||||
"\nYour place ID is %s, but needs to be one of these:" ..
|
||||
"\n%s" ..
|
||||
"\n\nTo change this list, edit 'servePlaceIds' in roblox-project.json"
|
||||
):format(
|
||||
tostring(game.PlaceId),
|
||||
table.concat(idList, "\n")
|
||||
)
|
||||
|
||||
return Promise.reject(message)
|
||||
end
|
||||
end
|
||||
|
||||
self.serverId = body.serverId
|
||||
self.partitionRoutes = body.partitions
|
||||
self.rootInstanceId = body.rootInstanceId
|
||||
return body
|
||||
end)
|
||||
end
|
||||
|
||||
function ApiContext:read(ids)
|
||||
local url = ("%s/api/read/%s"):format(self.baseUrl, table.concat(ids, ","))
|
||||
local url = ("%s/api/read/%s"):format(self.__baseUrl, table.concat(ids, ","))
|
||||
|
||||
return Http.get(url)
|
||||
:andThen(rejectFailedRequests)
|
||||
:andThen(function(response)
|
||||
local body = response:json()
|
||||
|
||||
if body.serverId ~= self.serverId then
|
||||
:andThen(Http.Response.json)
|
||||
:andThen(function(body)
|
||||
if body.sessionId ~= self.__sessionId then
|
||||
return Promise.reject("Server changed ID")
|
||||
end
|
||||
|
||||
self.messageCursor = body.messageCursor
|
||||
assert(validateApiRead(body))
|
||||
|
||||
return body
|
||||
end)
|
||||
end
|
||||
|
||||
function ApiContext:write(patch)
|
||||
local url = ("%s/api/write"):format(self.__baseUrl)
|
||||
|
||||
local updated = {}
|
||||
for _, update in ipairs(patch.updated) do
|
||||
local fixedUpdate = {
|
||||
id = update.id,
|
||||
changedName = update.changedName,
|
||||
}
|
||||
|
||||
if next(update.changedProperties) ~= nil then
|
||||
fixedUpdate.changedProperties = update.changedProperties
|
||||
end
|
||||
|
||||
table.insert(updated, fixedUpdate)
|
||||
end
|
||||
|
||||
-- Only add the 'added' field if the table is non-empty, or else Roblox's
|
||||
-- JSON implementation will turn the table into an array instead of an
|
||||
-- object, causing API validation to fail.
|
||||
local added
|
||||
if next(patch.added) ~= nil then
|
||||
added = patch.added
|
||||
end
|
||||
|
||||
local body = {
|
||||
sessionId = self.__sessionId,
|
||||
removed = patch.removed,
|
||||
updated = updated,
|
||||
added = added,
|
||||
}
|
||||
|
||||
body = Http.jsonEncode(body)
|
||||
|
||||
return Http.post(url, body)
|
||||
:andThen(rejectFailedRequests)
|
||||
:andThen(Http.Response.json)
|
||||
:andThen(function(body)
|
||||
Log.info("Write response: {:?}", body)
|
||||
|
||||
return body
|
||||
end)
|
||||
end
|
||||
|
||||
function ApiContext:retrieveMessages()
|
||||
local url = ("%s/api/subscribe/%s"):format(self.baseUrl, self.messageCursor)
|
||||
local url = ("%s/api/subscribe/%s"):format(self.__baseUrl, self.__messageCursor)
|
||||
|
||||
local function sendRequest()
|
||||
return Http.get(url)
|
||||
:catch(function(err)
|
||||
if err.type == HttpError.Error.Timeout then
|
||||
return sendRequest()
|
||||
if err.type == Http.Error.Kind.Timeout then
|
||||
if self.__connected then
|
||||
return sendRequest()
|
||||
else
|
||||
return hangingPromise()
|
||||
end
|
||||
end
|
||||
|
||||
return Promise.reject(err)
|
||||
@@ -153,17 +219,33 @@ function ApiContext:retrieveMessages()
|
||||
|
||||
return sendRequest()
|
||||
:andThen(rejectFailedRequests)
|
||||
:andThen(function(response)
|
||||
local body = response:json()
|
||||
|
||||
if body.serverId ~= self.serverId then
|
||||
:andThen(Http.Response.json)
|
||||
:andThen(function(body)
|
||||
if body.sessionId ~= self.__sessionId then
|
||||
return Promise.reject("Server changed ID")
|
||||
end
|
||||
|
||||
self.messageCursor = body.messageCursor
|
||||
assert(validateApiSubscribe(body))
|
||||
|
||||
self:setMessageCursor(body.messageCursor)
|
||||
|
||||
return body.messages
|
||||
end)
|
||||
end
|
||||
|
||||
function ApiContext:open(id)
|
||||
local url = ("%s/api/open/%s"):format(self.__baseUrl, id)
|
||||
|
||||
return Http.post(url, "")
|
||||
:andThen(rejectFailedRequests)
|
||||
:andThen(Http.Response.json)
|
||||
:andThen(function(body)
|
||||
if body.sessionId ~= self.__sessionId then
|
||||
return Promise.reject("Server changed ID")
|
||||
end
|
||||
|
||||
return nil
|
||||
end)
|
||||
end
|
||||
|
||||
return ApiContext
|
||||
@@ -1,11 +1,7 @@
|
||||
local strict = require(script.Parent.strict)
|
||||
|
||||
local Assets = {
|
||||
Sprites = {
|
||||
WhiteCross = {
|
||||
asset = "rbxassetid://2738712459",
|
||||
offset = Vector2.new(190, 318),
|
||||
size = Vector2.new(18, 18),
|
||||
},
|
||||
},
|
||||
Sprites = {},
|
||||
Slices = {
|
||||
RoundBox = {
|
||||
asset = "rbxassetid://2773204550",
|
||||
@@ -15,7 +11,8 @@ local Assets = {
|
||||
},
|
||||
},
|
||||
Images = {
|
||||
Logo = "rbxassetid://2773210620",
|
||||
Logo = "rbxassetid://3405346157",
|
||||
Icon = "rbxassetid://3405341609",
|
||||
},
|
||||
StartSession = "",
|
||||
SessionActive = "",
|
||||
@@ -23,11 +20,7 @@ local Assets = {
|
||||
}
|
||||
|
||||
local function guardForTypos(name, map)
|
||||
setmetatable(map, {
|
||||
__index = function(_, key)
|
||||
error(("%q is not a valid member of %s"):format(tostring(key), name), 2)
|
||||
end
|
||||
})
|
||||
strict(name, map)
|
||||
|
||||
for key, child in pairs(map) do
|
||||
if type(child) == "table" then
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
local Roact = require(script:FindFirstAncestor("Rojo").Roact)
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Plugin = script:FindFirstAncestor("Plugin")
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Log = require(Rojo.Log)
|
||||
|
||||
local ApiContext = require(Plugin.ApiContext)
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Session = require(Plugin.Session)
|
||||
local Config = require(Plugin.Config)
|
||||
local Version = require(Plugin.Version)
|
||||
local Logging = require(Plugin.Logging)
|
||||
local DevSettings = require(Plugin.DevSettings)
|
||||
local ServeSession = require(Plugin.ServeSession)
|
||||
local Version = require(Plugin.Version)
|
||||
local preloadAssets = require(Plugin.preloadAssets)
|
||||
local strict = require(Plugin.strict)
|
||||
|
||||
local ConnectPanel = require(Plugin.Components.ConnectPanel)
|
||||
local ConnectingPanel = require(Plugin.Components.ConnectingPanel)
|
||||
local ConnectionActivePanel = require(Plugin.Components.ConnectionActivePanel)
|
||||
local ErrorPanel = require(Plugin.Components.ErrorPanel)
|
||||
local SettingsPanel = require(Plugin.Components.SettingsPanel)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
@@ -25,7 +31,7 @@ local function showUpgradeMessage(lastVersion)
|
||||
Version.display(Config.version), Config.expectedServerVersionString
|
||||
)
|
||||
|
||||
Logging.info(message)
|
||||
Log.info(message)
|
||||
end
|
||||
|
||||
--[[
|
||||
@@ -51,147 +57,184 @@ local function checkUpgrade(plugin)
|
||||
plugin:SetSetting("LastRojoVersion", Config.version)
|
||||
end
|
||||
|
||||
local SessionStatus = {
|
||||
Disconnected = "Disconnected",
|
||||
local AppStatus = strict("AppStatus", {
|
||||
NotStarted = "NotStarted",
|
||||
Connecting = "Connecting",
|
||||
Connected = "Connected",
|
||||
ConfiguringSession = "ConfiguringSession",
|
||||
-- TODO: Error?
|
||||
}
|
||||
|
||||
setmetatable(SessionStatus, {
|
||||
__index = function(_, key)
|
||||
error(("%q is not a valid member of SessionStatus"):format(tostring(key)), 2)
|
||||
end,
|
||||
Error = "Error",
|
||||
Settings = "Settings",
|
||||
})
|
||||
|
||||
local App = Roact.Component:extend("App")
|
||||
|
||||
function App:init()
|
||||
self:setState({
|
||||
sessionStatus = SessionStatus.Disconnected,
|
||||
appStatus = AppStatus.NotStarted,
|
||||
errorMessage = nil,
|
||||
})
|
||||
|
||||
self.connectButton = nil
|
||||
self.currentSession = nil
|
||||
self.signals = {}
|
||||
self.serveSession = nil
|
||||
self.displayedVersion = Version.display(Config.version)
|
||||
|
||||
self.displayedVersion = DevSettings:isEnabled()
|
||||
and Config.codename
|
||||
or Version.display(Config.version)
|
||||
local toolbar = self.props.plugin:CreateToolbar("Rojo " .. self.displayedVersion)
|
||||
|
||||
self.toggleButton = toolbar:CreateButton(
|
||||
"Rojo",
|
||||
"Show or hide the Rojo panel",
|
||||
Assets.Images.Icon)
|
||||
self.toggleButton.ClickableWhenViewportHidden = true
|
||||
self.toggleButton.Click:Connect(function()
|
||||
self.dockWidget.Enabled = not self.dockWidget.Enabled
|
||||
end)
|
||||
|
||||
local widgetInfo = DockWidgetPluginGuiInfo.new(
|
||||
Enum.InitialDockState.Right,
|
||||
false, -- Initially enabled state
|
||||
false, -- Whether to override the widget's previous state
|
||||
360, 190, -- Floating size
|
||||
360, 190 -- Minimum size
|
||||
)
|
||||
|
||||
self.dockWidget = self.props.plugin:CreateDockWidgetPluginGui("Rojo-" .. self.displayedVersion, widgetInfo)
|
||||
self.dockWidget.Name = "Rojo " .. self.displayedVersion
|
||||
self.dockWidget.Title = "Rojo " .. self.displayedVersion
|
||||
self.dockWidget.AutoLocalize = false
|
||||
self.dockWidget.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
|
||||
|
||||
self.signals.dockWidgetEnabled = self.dockWidget:GetPropertyChangedSignal("Enabled"):Connect(function()
|
||||
self.toggleButton:SetActive(self.dockWidget.Enabled)
|
||||
end)
|
||||
end
|
||||
|
||||
function App:startSession(address, port, sessionOptions)
|
||||
Log.trace("Starting new session")
|
||||
|
||||
local baseUrl = ("http://%s:%s"):format(address, port)
|
||||
self.serveSession = ServeSession.new({
|
||||
apiContext = ApiContext.new(baseUrl),
|
||||
openScriptsExternally = sessionOptions.openScriptsExternally,
|
||||
twoWaySync = sessionOptions.twoWaySync,
|
||||
})
|
||||
|
||||
self.serveSession:onStatusChanged(function(status, details)
|
||||
if status == ServeSession.Status.Connecting then
|
||||
self:setState({
|
||||
appStatus = AppStatus.Connecting,
|
||||
})
|
||||
elseif status == ServeSession.Status.Connected then
|
||||
self:setState({
|
||||
appStatus = AppStatus.Connected,
|
||||
})
|
||||
elseif status == ServeSession.Status.Disconnected then
|
||||
self.serveSession = nil
|
||||
|
||||
-- Details being present indicates that this
|
||||
-- disconnection was from an error.
|
||||
if details ~= nil then
|
||||
Log.warn("Disconnected from an error: {}", details)
|
||||
|
||||
self:setState({
|
||||
appStatus = AppStatus.Error,
|
||||
errorMessage = tostring(details),
|
||||
})
|
||||
else
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotStarted,
|
||||
})
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
self.serveSession:start()
|
||||
end
|
||||
|
||||
function App:render()
|
||||
local children
|
||||
|
||||
if self.state.sessionStatus == SessionStatus.Connected then
|
||||
if self.state.appStatus == AppStatus.NotStarted then
|
||||
children = {
|
||||
ConnectionActivePanel = e(ConnectionActivePanel, {
|
||||
stopSession = function()
|
||||
Logging.trace("Disconnecting session")
|
||||
|
||||
self.currentSession:disconnect()
|
||||
self.currentSession = nil
|
||||
ConnectPanel = e(ConnectPanel, {
|
||||
startSession = function(address, port, settings)
|
||||
self:startSession(address, port, settings)
|
||||
end,
|
||||
openSettings = function()
|
||||
self:setState({
|
||||
sessionStatus = SessionStatus.Disconnected,
|
||||
appStatus = AppStatus.Settings,
|
||||
})
|
||||
end,
|
||||
cancel = function()
|
||||
Log.trace("Canceling session configuration")
|
||||
|
||||
Logging.trace("Session terminated by user")
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotStarted,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
}
|
||||
elseif self.state.sessionStatus == SessionStatus.ConfiguringSession then
|
||||
elseif self.state.appStatus == AppStatus.Connecting then
|
||||
children = {
|
||||
ConnectPanel = e(ConnectPanel, {
|
||||
startSession = function(address, port)
|
||||
Logging.trace("Starting new session")
|
||||
ConnectingPanel = e(ConnectingPanel),
|
||||
}
|
||||
elseif self.state.appStatus == AppStatus.Connected then
|
||||
children = {
|
||||
ConnectionActivePanel = e(ConnectionActivePanel, {
|
||||
stopSession = function()
|
||||
Log.trace("Disconnecting session")
|
||||
|
||||
local success, session = Session.new({
|
||||
address = address,
|
||||
port = port,
|
||||
onError = function(message)
|
||||
Logging.warn("Rojo session terminated because of an error:\n%s", tostring(message))
|
||||
self.currentSession = nil
|
||||
|
||||
self:setState({
|
||||
sessionStatus = SessionStatus.Disconnected,
|
||||
})
|
||||
end
|
||||
self.serveSession:stop()
|
||||
self.serveSession = nil
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotStarted,
|
||||
})
|
||||
|
||||
if success then
|
||||
self.currentSession = session
|
||||
self:setState({
|
||||
sessionStatus = SessionStatus.Connected,
|
||||
})
|
||||
end
|
||||
Log.trace("Session terminated by user")
|
||||
end,
|
||||
cancel = function()
|
||||
Logging.trace("Canceling session configuration")
|
||||
|
||||
}),
|
||||
}
|
||||
elseif self.state.appStatus == AppStatus.Settings then
|
||||
children = {
|
||||
e(SettingsPanel, {
|
||||
back = function()
|
||||
self:setState({
|
||||
sessionStatus = SessionStatus.Disconnected,
|
||||
appStatus = AppStatus.NotStarted,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
}
|
||||
elseif self.state.appStatus == AppStatus.Error then
|
||||
children = {
|
||||
ErrorPanel = e(ErrorPanel, {
|
||||
errorMessage = self.state.errorMessage,
|
||||
onDismiss = function()
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotStarted,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
}
|
||||
end
|
||||
|
||||
return e("ScreenGui", {
|
||||
AutoLocalize = false,
|
||||
ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
return e(Roact.Portal, {
|
||||
target = self.dockWidget,
|
||||
}, children)
|
||||
end
|
||||
|
||||
function App:didMount()
|
||||
Logging.trace("Rojo %s initializing", self.displayedVersion)
|
||||
|
||||
local toolbar = self.props.plugin:CreateToolbar("Rojo " .. self.displayedVersion)
|
||||
|
||||
self.connectButton = toolbar:CreateButton(
|
||||
"Connect",
|
||||
"Connect to a running Rojo session",
|
||||
Assets.StartSession)
|
||||
self.connectButton.ClickableWhenViewportHidden = false
|
||||
self.connectButton.Click:Connect(function()
|
||||
checkUpgrade(self.props.plugin)
|
||||
|
||||
if self.state.sessionStatus == SessionStatus.Connected then
|
||||
Logging.trace("Disconnecting session")
|
||||
|
||||
self.currentSession:disconnect()
|
||||
self.currentSession = nil
|
||||
self:setState({
|
||||
sessionStatus = SessionStatus.Disconnected,
|
||||
})
|
||||
|
||||
Logging.trace("Session terminated by user")
|
||||
elseif self.state.sessionStatus == SessionStatus.Disconnected then
|
||||
Logging.trace("Starting session configuration")
|
||||
|
||||
self:setState({
|
||||
sessionStatus = SessionStatus.ConfiguringSession,
|
||||
})
|
||||
elseif self.state.sessionStatus == SessionStatus.ConfiguringSession then
|
||||
Logging.trace("Canceling session configuration")
|
||||
|
||||
self:setState({
|
||||
sessionStatus = SessionStatus.Disconnected,
|
||||
})
|
||||
end
|
||||
end)
|
||||
Log.trace("Rojo {} initializing", self.displayedVersion)
|
||||
|
||||
checkUpgrade(self.props.plugin)
|
||||
preloadAssets()
|
||||
end
|
||||
|
||||
function App:didUpdate()
|
||||
local connectActive = self.state.sessionStatus == SessionStatus.ConfiguringSession
|
||||
or self.state.sessionStatus == SessionStatus.Connected
|
||||
function App:willUnmount()
|
||||
if self.serveSession ~= nil then
|
||||
self.serveSession:stop()
|
||||
self.serveSession = nil
|
||||
end
|
||||
|
||||
self.connectButton:SetActive(connectActive)
|
||||
|
||||
if self.state.sessionStatus == SessionStatus.Connected then
|
||||
self.connectButton.Icon = Assets.SessionActive
|
||||
else
|
||||
self.connectButton.Icon = Assets.StartSession
|
||||
for _, signal in pairs(self.signals) do
|
||||
signal:Disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
39
plugin/src/Components/Checkbox.lua
Normal file
@@ -0,0 +1,39 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Theme = require(Plugin.Components.Theme)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function Checkbox(props)
|
||||
local checked = props.checked
|
||||
local layoutOrder = props.layoutOrder
|
||||
local onChange = props.onChange
|
||||
|
||||
return Theme.with(function(theme)
|
||||
return e("ImageButton", {
|
||||
LayoutOrder = layoutOrder,
|
||||
Size = UDim2.new(0, 20, 0, 20),
|
||||
BorderSizePixel = 2,
|
||||
BorderColor3 = theme.Text2,
|
||||
BackgroundColor3 = theme.Background2,
|
||||
|
||||
[Roact.Event.Activated] = function()
|
||||
onChange(not checked)
|
||||
end,
|
||||
}, {
|
||||
Indicator = e("Frame", {
|
||||
Size = UDim2.new(0, 18, 0, 18),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
BorderSizePixel = 0,
|
||||
BackgroundColor3 = theme.Brand1,
|
||||
BackgroundTransparency = checked and 0 or 1,
|
||||
})
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return Checkbox
|
||||
@@ -4,39 +4,20 @@ local Plugin = Rojo.Plugin
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Config = require(Plugin.Config)
|
||||
local Version = require(Plugin.Version)
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.Theme)
|
||||
local joinBindings = require(Plugin.joinBindings)
|
||||
|
||||
local Theme = require(Plugin.Components.Theme)
|
||||
local Panel = require(Plugin.Components.Panel)
|
||||
local FitList = require(Plugin.Components.FitList)
|
||||
local FitText = require(Plugin.Components.FitText)
|
||||
local FormButton = require(Plugin.Components.FormButton)
|
||||
local FormTextInput = require(Plugin.Components.FormTextInput)
|
||||
|
||||
local RoundBox = Assets.Slices.RoundBox
|
||||
local PluginSettings = require(Plugin.Components.PluginSettings)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local ConnectPanel = Roact.Component:extend("ConnectPanel")
|
||||
|
||||
function ConnectPanel:init()
|
||||
self.footerSize, self.setFooterSize = Roact.createBinding(Vector2.new())
|
||||
self.footerVersionSize, self.setFooterVersionSize = Roact.createBinding(Vector2.new())
|
||||
|
||||
-- This is constructed in init because 'joinBindings' is a hack and we'd
|
||||
-- leak memory constructing it every render. When this kind of feature lands
|
||||
-- in Roact properly, we can do this inline in render without fear.
|
||||
self.footerRestSize = joinBindings(
|
||||
{
|
||||
self.footerSize,
|
||||
self.footerVersionSize,
|
||||
},
|
||||
function(container, other)
|
||||
return UDim2.new(0, container.X - other.X - 16, 0, 32)
|
||||
end
|
||||
)
|
||||
|
||||
self:setState({
|
||||
address = "",
|
||||
port = "",
|
||||
@@ -45,217 +26,158 @@ end
|
||||
|
||||
function ConnectPanel:render()
|
||||
local startSession = self.props.startSession
|
||||
local cancel = self.props.cancel
|
||||
local openSettings = self.props.openSettings
|
||||
|
||||
return e(FitList, {
|
||||
containerKind = "ImageLabel",
|
||||
containerProps = {
|
||||
Image = RoundBox.asset,
|
||||
ImageRectOffset = RoundBox.offset,
|
||||
ImageRectSize = RoundBox.size,
|
||||
SliceCenter = RoundBox.center,
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
BackgroundTransparency = 1,
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
},
|
||||
layoutProps = {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
},
|
||||
}, {
|
||||
Inputs = e(FitList, {
|
||||
containerProps = {
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
},
|
||||
layoutProps = {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
Padding = UDim.new(0, 8),
|
||||
},
|
||||
paddingProps = {
|
||||
PaddingTop = UDim.new(0, 20),
|
||||
PaddingBottom = UDim.new(0, 10),
|
||||
PaddingLeft = UDim.new(0, 24),
|
||||
PaddingRight = UDim.new(0, 24),
|
||||
},
|
||||
}, {
|
||||
Address = e(FitList, {
|
||||
containerProps = {
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
},
|
||||
layoutProps = {
|
||||
Padding = UDim.new(0, 4),
|
||||
},
|
||||
}, {
|
||||
Label = e(FitText, {
|
||||
Kind = "TextLabel",
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = Theme.TitleFont,
|
||||
TextSize = 20,
|
||||
Text = "Address",
|
||||
TextColor3 = Theme.AccentColor,
|
||||
return Theme.with(function(theme)
|
||||
return PluginSettings.with(function(settings)
|
||||
return e(Panel, nil, {
|
||||
Layout = e("UIListLayout", {
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
}),
|
||||
|
||||
Input = e(FormTextInput, {
|
||||
layoutOrder = 2,
|
||||
width = UDim.new(0, 220),
|
||||
value = self.state.address,
|
||||
placeholderValue = Config.defaultHost,
|
||||
onValueChange = function(newValue)
|
||||
self:setState({
|
||||
address = newValue,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
Inputs = e(FitList, {
|
||||
containerProps = {
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
},
|
||||
layoutProps = {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
Padding = UDim.new(0, 8),
|
||||
},
|
||||
paddingProps = {
|
||||
PaddingTop = UDim.new(0, 20),
|
||||
PaddingBottom = UDim.new(0, 10),
|
||||
PaddingLeft = UDim.new(0, 24),
|
||||
PaddingRight = UDim.new(0, 24),
|
||||
},
|
||||
}, {
|
||||
Address = e(FitList, {
|
||||
containerProps = {
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
},
|
||||
layoutProps = {
|
||||
Padding = UDim.new(0, 4),
|
||||
},
|
||||
}, {
|
||||
Label = e(FitText, {
|
||||
Kind = "TextLabel",
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = theme.TitleFont,
|
||||
TextSize = 20,
|
||||
Text = "Address",
|
||||
TextColor3 = theme.Text1,
|
||||
}),
|
||||
|
||||
Port = e(FitList, {
|
||||
containerProps = {
|
||||
LayoutOrder = 2,
|
||||
BackgroundTransparency = 1,
|
||||
},
|
||||
layoutProps = {
|
||||
Padding = UDim.new(0, 4),
|
||||
},
|
||||
}, {
|
||||
Label = e(FitText, {
|
||||
Kind = "TextLabel",
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = Theme.TitleFont,
|
||||
TextSize = 20,
|
||||
Text = "Port",
|
||||
TextColor3 = Theme.AccentColor,
|
||||
Input = e(FormTextInput, {
|
||||
layoutOrder = 2,
|
||||
width = UDim.new(0, 220),
|
||||
value = self.state.address,
|
||||
placeholderValue = Config.defaultHost,
|
||||
onValueChange = function(newValue)
|
||||
self:setState({
|
||||
address = newValue,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
|
||||
Port = e(FitList, {
|
||||
containerProps = {
|
||||
LayoutOrder = 2,
|
||||
BackgroundTransparency = 1,
|
||||
},
|
||||
layoutProps = {
|
||||
Padding = UDim.new(0, 4),
|
||||
},
|
||||
}, {
|
||||
Label = e(FitText, {
|
||||
Kind = "TextLabel",
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = theme.TitleFont,
|
||||
TextSize = 20,
|
||||
Text = "Port",
|
||||
TextColor3 = theme.Text1,
|
||||
}),
|
||||
|
||||
Input = e(FormTextInput, {
|
||||
layoutOrder = 2,
|
||||
width = UDim.new(0, 80),
|
||||
value = self.state.port,
|
||||
placeholderValue = Config.defaultPort,
|
||||
onValueChange = function(newValue)
|
||||
self:setState({
|
||||
port = newValue,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
Input = e(FormTextInput, {
|
||||
layoutOrder = 2,
|
||||
width = UDim.new(0, 80),
|
||||
value = self.state.port,
|
||||
placeholderValue = Config.defaultPort,
|
||||
onValueChange = function(newValue)
|
||||
self:setState({
|
||||
port = newValue,
|
||||
})
|
||||
end,
|
||||
Buttons = e(FitList, {
|
||||
fitAxes = "Y",
|
||||
containerProps = {
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 2,
|
||||
Size = UDim2.new(1, 0, 0, 0),
|
||||
},
|
||||
layoutProps = {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||
Padding = UDim.new(0, 8),
|
||||
},
|
||||
paddingProps = {
|
||||
PaddingTop = UDim.new(0, 0),
|
||||
PaddingBottom = UDim.new(0, 20),
|
||||
PaddingLeft = UDim.new(0, 24),
|
||||
PaddingRight = UDim.new(0, 24),
|
||||
},
|
||||
}, {
|
||||
e(FormButton, {
|
||||
layoutOrder = 1,
|
||||
text = "Settings",
|
||||
secondary = true,
|
||||
onClick = function()
|
||||
if openSettings ~= nil then
|
||||
openSettings()
|
||||
end
|
||||
end,
|
||||
}),
|
||||
|
||||
e(FormButton, {
|
||||
layoutOrder = 2,
|
||||
text = "Connect",
|
||||
onClick = function()
|
||||
if startSession ~= nil then
|
||||
local address = self.state.address
|
||||
if address:len() == 0 then
|
||||
address = Config.defaultHost
|
||||
end
|
||||
|
||||
local port = self.state.port
|
||||
if port:len() == 0 then
|
||||
port = Config.defaultPort
|
||||
end
|
||||
|
||||
local sessionOptions = {
|
||||
openScriptsExternally = settings:get("openScriptsExternally"),
|
||||
twoWaySync = settings:get("twoWaySync"),
|
||||
}
|
||||
|
||||
startSession(address, port, sessionOptions)
|
||||
end
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
Buttons = e(FitList, {
|
||||
fitAxes = "Y",
|
||||
containerProps = {
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 2,
|
||||
Size = UDim2.new(1, 0, 0, 0),
|
||||
},
|
||||
layoutProps = {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||
Padding = UDim.new(0, 8),
|
||||
},
|
||||
paddingProps = {
|
||||
PaddingTop = UDim.new(0, 0),
|
||||
PaddingBottom = UDim.new(0, 20),
|
||||
PaddingLeft = UDim.new(0, 24),
|
||||
PaddingRight = UDim.new(0, 24),
|
||||
},
|
||||
}, {
|
||||
e(FormButton, {
|
||||
layoutOrder = 1,
|
||||
text = "Cancel",
|
||||
onClick = function()
|
||||
if cancel ~= nil then
|
||||
cancel()
|
||||
end
|
||||
end,
|
||||
secondary = true,
|
||||
}),
|
||||
|
||||
e(FormButton, {
|
||||
layoutOrder = 2,
|
||||
text = "Connect",
|
||||
onClick = function()
|
||||
if startSession ~= nil then
|
||||
local address = self.state.address
|
||||
if address:len() == 0 then
|
||||
address = Config.defaultHost
|
||||
end
|
||||
|
||||
local port = self.state.port
|
||||
if port:len() == 0 then
|
||||
port = Config.defaultPort
|
||||
end
|
||||
|
||||
startSession(address, port)
|
||||
end
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
|
||||
Footer = e(FitList, {
|
||||
fitAxes = "Y",
|
||||
containerKind = "ImageLabel",
|
||||
containerProps = {
|
||||
Image = RoundBox.asset,
|
||||
ImageRectOffset = RoundBox.offset + Vector2.new(0, RoundBox.size.Y / 2),
|
||||
ImageRectSize = RoundBox.size * Vector2.new(1, 0.5),
|
||||
SliceCenter = RoundBox.center,
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
ImageColor3 = Theme.SecondaryColor,
|
||||
Size = UDim2.new(1, 0, 0, 0),
|
||||
LayoutOrder = 3,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Change.AbsoluteSize] = function(rbx)
|
||||
self.setFooterSize(rbx.AbsoluteSize)
|
||||
end,
|
||||
},
|
||||
layoutProps = {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
},
|
||||
paddingProps = {
|
||||
PaddingTop = UDim.new(0, 4),
|
||||
PaddingBottom = UDim.new(0, 4),
|
||||
PaddingLeft = UDim.new(0, 8),
|
||||
PaddingRight = UDim.new(0, 8),
|
||||
},
|
||||
}, {
|
||||
LogoContainer = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
Size = self.footerRestSize,
|
||||
}, {
|
||||
Logo = e("ImageLabel", {
|
||||
Image = Assets.Images.Logo,
|
||||
Size = UDim2.new(0, 80, 0, 40),
|
||||
ScaleType = Enum.ScaleType.Fit,
|
||||
BackgroundTransparency = 1,
|
||||
Position = UDim2.new(0, 0, 1, -10),
|
||||
AnchorPoint = Vector2.new(0, 1),
|
||||
}),
|
||||
}),
|
||||
|
||||
Version = e(FitText, {
|
||||
Font = Theme.TitleFont,
|
||||
TextSize = 18,
|
||||
Text = Version.display(Config.version),
|
||||
TextXAlignment = Enum.TextXAlignment.Right,
|
||||
TextColor3 = Theme.LightTextColor,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Change.AbsoluteSize] = function(rbx)
|
||||
self.setFooterVersionSize(rbx.AbsoluteSize)
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
return ConnectPanel
|
||||
35
plugin/src/Components/ConnectingPanel.lua
Normal file
@@ -0,0 +1,35 @@
|
||||
local Roact = require(script:FindFirstAncestor("Rojo").Roact)
|
||||
|
||||
local Plugin = script:FindFirstAncestor("Plugin")
|
||||
|
||||
local Theme = require(Plugin.Components.Theme)
|
||||
local Panel = require(Plugin.Components.Panel)
|
||||
local FitText = require(Plugin.Components.FitText)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local ConnectingPanel = Roact.Component:extend("ConnectingPanel")
|
||||
|
||||
function ConnectingPanel:render()
|
||||
return Theme.with(function(theme)
|
||||
return e(Panel, nil, {
|
||||
Layout = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 8),
|
||||
}),
|
||||
|
||||
Text = e(FitText, {
|
||||
Padding = Vector2.new(12, 6),
|
||||
Font = theme.ButtonFont,
|
||||
TextSize = 18,
|
||||
Text = "Connecting...",
|
||||
TextColor3 = theme.Text1,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return ConnectingPanel
|
||||