mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
735 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68576ace72 | ||
|
|
ac21e24f33 | ||
|
|
843b8e69ef | ||
|
|
a827bbec2b | ||
|
|
a5be8e723e | ||
|
|
bac3ff969b | ||
|
|
bfabc8e51a | ||
|
|
c116cd6d8b | ||
|
|
30f39dfb66 | ||
|
|
6b81c283f5 | ||
|
|
c91170b50e | ||
|
|
831dd0e5bb | ||
|
|
028dea2819 | ||
|
|
9117f8c776 | ||
|
|
a46c539462 | ||
|
|
c9cbeadc9e | ||
|
|
8f1386cb19 | ||
|
|
3b902e0557 | ||
|
|
3142dcbfda | ||
|
|
29ffd07712 | ||
|
|
afd687e392 | ||
|
|
72341a25af | ||
|
|
6d03840fbd | ||
|
|
ea2d63b404 | ||
|
|
42a137d106 | ||
|
|
8f8c1eccc3 | ||
|
|
b91fa6ba75 | ||
|
|
e270f4f06c | ||
|
|
56b7957878 | ||
|
|
209eb5fea0 | ||
|
|
417954e66c | ||
|
|
e4fb860985 | ||
|
|
d70481aedd | ||
|
|
52eb581b56 | ||
|
|
918fa8e3b9 | ||
|
|
1c7f19bf67 | ||
|
|
0cbff8dee1 | ||
|
|
670ff54ef0 | ||
|
|
8f519c52b6 | ||
|
|
fbd29afbd8 | ||
|
|
2bbdb23716 | ||
|
|
e2dda67eef | ||
|
|
699615df97 | ||
|
|
e804994a5b | ||
|
|
24f7ecccc0 | ||
|
|
21d43350f0 | ||
|
|
63eb7847a1 | ||
|
|
4a4f07a10c | ||
|
|
1c816941ec | ||
|
|
2143c9abc3 | ||
|
|
4b6ceed586 | ||
|
|
49c6c0c9d8 | ||
|
|
54f6143be3 | ||
|
|
b83e09c214 | ||
|
|
caa9b442d5 | ||
|
|
69921da034 | ||
|
|
552893f1b7 | ||
|
|
c5c10caaf1 | ||
|
|
98de046977 | ||
|
|
6328bddb52 | ||
|
|
003c910c66 | ||
|
|
1d7f4b4501 | ||
|
|
2f6fb22473 | ||
|
|
96d66c57c1 | ||
|
|
35741524a3 | ||
|
|
d5855fa805 | ||
|
|
36a9d06381 | ||
|
|
3bfb3a5ef7 | ||
|
|
83b031cab4 | ||
|
|
6197352fca | ||
|
|
0fd210481a | ||
|
|
74eea847e2 | ||
|
|
6938787863 | ||
|
|
4ba5654253 | ||
|
|
15ff120b97 | ||
|
|
942a550687 | ||
|
|
acff35f4bf | ||
|
|
4e7039e09b | ||
|
|
4a0b3793e4 | ||
|
|
928ae19249 | ||
|
|
a7f85b53f8 | ||
|
|
5fac83c697 | ||
|
|
577b2b0f62 | ||
|
|
cd3a7ef91e | ||
|
|
051d47f4ff | ||
|
|
b982b94c83 | ||
|
|
84b7742656 | ||
|
|
1585cd111f | ||
|
|
766f6dc93d | ||
|
|
59af9451be | ||
|
|
80e4f69f29 | ||
|
|
98060f2056 | ||
|
|
f0bfb6a69a | ||
|
|
ac38748280 | ||
|
|
18fe34c041 | ||
|
|
6a0cb6a5af | ||
|
|
310aebaee2 | ||
|
|
f25d67da55 | ||
|
|
eac36e917f | ||
|
|
c64fa8f2cc | ||
|
|
63c1707581 | ||
|
|
914a7fb130 | ||
|
|
41d3495e29 | ||
|
|
2ae426b8e5 | ||
|
|
eb45e16739 | ||
|
|
79c952a871 | ||
|
|
827c4b32fd | ||
|
|
033e20cbf1 | ||
|
|
3ff46d40fa | ||
|
|
5ab6aba20b | ||
|
|
e6be04cb83 | ||
|
|
627b2f4d0d | ||
|
|
ffff42cc95 | ||
|
|
ce420ac8ab | ||
|
|
d3cdfbe3cb | ||
|
|
726bfd58f6 | ||
|
|
05827d01d0 | ||
|
|
6cb7641c07 | ||
|
|
d72185933a | ||
|
|
80a9a82278 | ||
|
|
99c8ce2cc8 | ||
|
|
f137a6e3a5 | ||
|
|
73e62414a7 | ||
|
|
ef7ef3ca9f | ||
|
|
da26e86f5e | ||
|
|
d2a1815d7b | ||
|
|
4894888339 | ||
|
|
3fd55733bb | ||
|
|
bebd638412 | ||
|
|
328b3cc715 | ||
|
|
b523cfddb7 | ||
|
|
4fea277541 | ||
|
|
3417f00650 | ||
|
|
db4b190498 | ||
|
|
7d2bbc2ca7 | ||
|
|
553278fdb3 | ||
|
|
e5013d9e3f | ||
|
|
679f8f24c9 | ||
|
|
1a547b946c | ||
|
|
5594bd7203 | ||
|
|
8cbe48c94f | ||
|
|
b04b9fc9cd | ||
|
|
820d988e0a | ||
|
|
1ad5122b5d | ||
|
|
456ac03870 | ||
|
|
f647d00dc4 | ||
|
|
2fd3bbf58b | ||
|
|
5ed49d51d3 | ||
|
|
758d5eedef | ||
|
|
96d15929e6 | ||
|
|
2a062dbf53 | ||
|
|
6d66bc66e4 | ||
|
|
5cc056100b | ||
|
|
5eee22b034 | ||
|
|
7f5beab259 | ||
|
|
46aead639b | ||
|
|
488d060595 | ||
|
|
b1bab1f38e | ||
|
|
feba2a23ad | ||
|
|
e1ff29744e | ||
|
|
f6216e63b0 | ||
|
|
0b3ecf2168 | ||
|
|
9f29bf6349 | ||
|
|
d12a238ecc | ||
|
|
d62a64029d | ||
|
|
ac46a7845d | ||
|
|
17662aaad9 | ||
|
|
60b526f653 | ||
|
|
d58e380dd9 | ||
|
|
96a098a0c2 | ||
|
|
c0d4e34089 | ||
|
|
e16e0f4bd0 | ||
|
|
d31ffd2794 | ||
|
|
04d94f87fc | ||
|
|
f809375389 | ||
|
|
530ea5f4e6 | ||
|
|
590a23d540 | ||
|
|
52351e8b11 | ||
|
|
f75a764ff3 | ||
|
|
d14f4f0ce3 | ||
|
|
5367570c7f | ||
|
|
e523a733c8 | ||
|
|
06af61106c | ||
|
|
2239c30924 | ||
|
|
f9f55b6862 | ||
|
|
83a5560850 | ||
|
|
9a8f139fb9 | ||
|
|
20dae60fd4 | ||
|
|
b4098668bb | ||
|
|
9397cc4a6b | ||
|
|
1601e75879 | ||
|
|
a7b9c87926 | ||
|
|
8fea42ff9a | ||
|
|
86d20a0ef1 | ||
|
|
09012ea4ff | ||
|
|
e68297eb93 | ||
|
|
47af668cda | ||
|
|
c1a2e23ce2 | ||
|
|
f208f6bfa9 | ||
|
|
ae526e2e10 | ||
|
|
25549869b1 | ||
|
|
f71e81d204 | ||
|
|
757143be84 | ||
|
|
e67812fdb4 | ||
|
|
aa44b1cb8a | ||
|
|
8ec75be244 | ||
|
|
48746b7bd3 | ||
|
|
a9791d2033 | ||
|
|
709f1f4284 | ||
|
|
907094a5c8 | ||
|
|
a35a5e1645 | ||
|
|
ad8a59a72f | ||
|
|
e93c0f76a9 | ||
|
|
7bac32d18e | ||
|
|
b6b1d46892 | ||
|
|
30fcc6b729 | ||
|
|
6f0bc3822e | ||
|
|
b7c8452285 | ||
|
|
49e2d567cd | ||
|
|
8c1e075c91 | ||
|
|
b340e40c99 | ||
|
|
c4b124f48d | ||
|
|
7efae8fbc1 | ||
|
|
7feeeb2f6f | ||
|
|
f90462cf82 | ||
|
|
b19ae9e69e | ||
|
|
9bf69db0ef | ||
|
|
2132d6cbae | ||
|
|
d2d6f9d08e | ||
|
|
4b58fcbff2 | ||
|
|
f83f6a8cd6 | ||
|
|
dfd7711506 | ||
|
|
78f9d92c07 | ||
|
|
3a86c827ea | ||
|
|
325f25c547 | ||
|
|
be57b5d20b | ||
|
|
7124d86f94 | ||
|
|
229380a71d | ||
|
|
e9eb536df5 | ||
|
|
22297ef6d8 | ||
|
|
7f2e433087 | ||
|
|
18c32a0258 | ||
|
|
72314a102d | ||
|
|
719ea26a31 | ||
|
|
5cb8fe1897 | ||
|
|
f35a52fc24 | ||
|
|
6bdb0cef47 | ||
|
|
fe3c9fe28f | ||
|
|
6085671f22 | ||
|
|
a2398da324 | ||
|
|
b27304cc58 | ||
|
|
3bf851a6cf | ||
|
|
cef92efd0f | ||
|
|
c5961a5ab1 | ||
|
|
8ddd92993d | ||
|
|
da253a5f34 | ||
|
|
ca9400a1ff | ||
|
|
f232195ceb | ||
|
|
b54a803519 | ||
|
|
a0d3d2108f | ||
|
|
977e4a017b | ||
|
|
2d8b159016 | ||
|
|
9caa0dde4b | ||
|
|
7a5a8c5eb1 | ||
|
|
95ba58f0a4 | ||
|
|
f780f04784 | ||
|
|
695b4ce8f2 | ||
|
|
85782bda92 | ||
|
|
14a01df5b1 | ||
|
|
644da60bfc | ||
|
|
8c83999ad2 | ||
|
|
24b9fc9eec | ||
|
|
ba40185179 | ||
|
|
8b013cb424 | ||
|
|
b67d24efee | ||
|
|
d992e47f30 | ||
|
|
dadd7b4cc3 | ||
|
|
baef2bc7f8 | ||
|
|
e0b1a7d64a | ||
|
|
aea5f83002 | ||
|
|
7df2d1f430 | ||
|
|
d7ecc0883f | ||
|
|
d216c3a1f6 | ||
|
|
44b17edff6 | ||
|
|
6366c2383a | ||
|
|
ddbbe22bda | ||
|
|
6880af0d8b | ||
|
|
6f258b1822 | ||
|
|
2cf3ed8a70 | ||
|
|
c6f10a1321 | ||
|
|
41ab9b106f | ||
|
|
0d97569576 | ||
|
|
0a21d402b2 | ||
|
|
c6827fe8ae | ||
|
|
0c94956be1 | ||
|
|
1e7a193d5e | ||
|
|
eb4ea8aa63 | ||
|
|
af1a3d965d | ||
|
|
26fed40ad4 | ||
|
|
c6c6309eff | ||
|
|
7188eeb0c8 | ||
|
|
d74b22a4e8 | ||
|
|
75de717b4e | ||
|
|
a00fc75b61 | ||
|
|
dcbb6608df | ||
|
|
e4d0e50000 | ||
|
|
e5a2ab284b | ||
|
|
986ec3ef06 | ||
|
|
3ac1506f17 | ||
|
|
60cec9cb84 | ||
|
|
c06707d519 | ||
|
|
63128324ab | ||
|
|
c85bb81606 | ||
|
|
abea3024b4 | ||
|
|
07dafeb6cd | ||
|
|
a726d42ae3 | ||
|
|
d02d186a2f | ||
|
|
a6be66949d | ||
|
|
0dfd3b7443 | ||
|
|
6f90e9a76e | ||
|
|
9246b88560 | ||
|
|
61af9db362 | ||
|
|
5aa950e7f7 | ||
|
|
b8903af52f | ||
|
|
5b35c4e82b | ||
|
|
06db80780c | ||
|
|
e5fd4f5700 | ||
|
|
e1a199e060 | ||
|
|
22ac34c7a1 | ||
|
|
04fa6901b1 | ||
|
|
8f7d4211e3 | ||
|
|
1147d6fd9a | ||
|
|
e4449c4901 | ||
|
|
dc8963faa5 | ||
|
|
f9cf9a8fd4 | ||
|
|
393bdc04bc | ||
|
|
db4d787b49 | ||
|
|
cd802d6a66 | ||
|
|
be2281a5b3 | ||
|
|
e717396b33 | ||
|
|
db0e49f5dd | ||
|
|
d3b94aa6af | ||
|
|
713f702064 | ||
|
|
d0b6a9b28c | ||
|
|
4003781a1b | ||
|
|
40586a8f0e | ||
|
|
f4d427f5c5 | ||
|
|
ce2a4282f3 | ||
|
|
0cc78c1402 | ||
|
|
fa0d4da6d1 | ||
|
|
33334d6f5c | ||
|
|
55aba93faf | ||
|
|
4f49296a94 | ||
|
|
65357ee8da | ||
|
|
bfcad4001b | ||
|
|
5a3000febb | ||
|
|
2d236670ab | ||
|
|
796f1480a8 | ||
|
|
a8ee7be844 | ||
|
|
60b506cb2a | ||
|
|
66117e35ba | ||
|
|
22cfee4f01 | ||
|
|
d9355e576f | ||
|
|
e54e33edb0 | ||
|
|
25881ce343 | ||
|
|
f64197c189 | ||
|
|
a7ec907f1d | ||
|
|
81272b0bc8 | ||
|
|
25c1c6ef91 | ||
|
|
d1a83134e3 | ||
|
|
71c2993d20 | ||
|
|
14fa616723 | ||
|
|
d46ea9c9ba | ||
|
|
ea00ccb34f | ||
|
|
8e416bb3cb | ||
|
|
8e0a26073d | ||
|
|
9fa24948ea | ||
|
|
b9a9cc4b0f | ||
|
|
a67345f9bf | ||
|
|
2a022f39bd | ||
|
|
a3ba969589 | ||
|
|
21e1b75f5d | ||
|
|
1f6ddd96a6 | ||
|
|
b4d2cd26aa | ||
|
|
ee6aee57bd | ||
|
|
ac1705e41f | ||
|
|
196a6047f1 | ||
|
|
b98b36a461 | ||
|
|
9980a98a94 | ||
|
|
5a1a1d0420 | ||
|
|
1c99678fe9 | ||
|
|
4049f10faa | ||
|
|
331c0bd43f | ||
|
|
604cd2f93d | ||
|
|
a87db2e57c | ||
|
|
4c7f0a8d6b | ||
|
|
9eaf52886a | ||
|
|
fce6f6c714 | ||
|
|
c04d51d489 | ||
|
|
d310871aa6 | ||
|
|
5fa422865f | ||
|
|
c137823355 | ||
|
|
0a0026b9ae | ||
|
|
97a2a5cfae | ||
|
|
a266e21d9e | ||
|
|
ad8bbe6401 | ||
|
|
71ce4749fe | ||
|
|
1e33c3c843 | ||
|
|
b3f3ca9725 | ||
|
|
33c37393ba | ||
|
|
ae36529744 | ||
|
|
ed2be48864 | ||
|
|
7ad5ce302e | ||
|
|
647f1a6808 | ||
|
|
6c44dd9665 | ||
|
|
c81413b0b4 | ||
|
|
88b3a557da | ||
|
|
572eb01290 | ||
|
|
9dab74c9d5 | ||
|
|
e1cb1e1b9c | ||
|
|
a23da702b1 | ||
|
|
ae9c2423ff | ||
|
|
a6dae8e30a | ||
|
|
96c0a4ae1f | ||
|
|
c26ebcbc78 | ||
|
|
8334050411 | ||
|
|
cc67edfc2c | ||
|
|
943ea9e6c8 | ||
|
|
3aa5cefe03 | ||
|
|
c5b34bab69 | ||
|
|
e4f24ec125 | ||
|
|
250971ade7 | ||
|
|
718adf9740 | ||
|
|
5d63aa8c95 | ||
|
|
17af3612a5 | ||
|
|
d2ecf6b9b1 | ||
|
|
2a1eda0d38 | ||
|
|
f0180abeb0 | ||
|
|
720f1b1d05 | ||
|
|
ae45a96753 | ||
|
|
74257c72ee | ||
|
|
cea088f4b4 | ||
|
|
88678e7d58 | ||
|
|
d222e25d22 | ||
|
|
f0d7fbb6f2 | ||
|
|
adec0e71ec | ||
|
|
86d9067d62 | ||
|
|
b989c9dbee | ||
|
|
3101ec1985 | ||
|
|
9ec2da1777 | ||
|
|
0acd28e8f4 | ||
|
|
c340f50ee5 | ||
|
|
2b589215aa | ||
|
|
490a567ad4 | ||
|
|
32f0c49484 | ||
|
|
61113d2434 | ||
|
|
6ba1baa88c | ||
|
|
07867acb9a | ||
|
|
3e28b083b9 | ||
|
|
68d9e13edf | ||
|
|
a0c0a722c9 | ||
|
|
bf4aead1e8 | ||
|
|
39f0d2e974 | ||
|
|
b20ae98f21 | ||
|
|
7899780543 | ||
|
|
0c9e322b3e | ||
|
|
6005208285 | ||
|
|
2b15831349 | ||
|
|
1b2450d1cb | ||
|
|
5f31036ab2 | ||
|
|
8efffc471d | ||
|
|
eb9e842027 | ||
|
|
f9cd9ac12a | ||
|
|
8fd98c75a9 | ||
|
|
7c008e857d | ||
|
|
4de2e35e66 | ||
|
|
d4467acf93 | ||
|
|
f3babcc39f | ||
|
|
f491bb5571 | ||
|
|
b201f10c76 | ||
|
|
a9208c0d29 | ||
|
|
b8cc01d872 | ||
|
|
2d827890e9 | ||
|
|
f86d6ccd3c | ||
|
|
967b76483a | ||
|
|
ef2c0ad8cf | ||
|
|
9ae1352030 | ||
|
|
d8d9b271cc | ||
|
|
122acc5fd5 | ||
|
|
32dea84196 | ||
|
|
91d58dbca4 | ||
|
|
c54b1572f5 | ||
|
|
1fa979c0f6 | ||
|
|
760599171d | ||
|
|
10d295d535 | ||
|
|
0047c5000f | ||
|
|
810a6d190f | ||
|
|
197227dcf6 | ||
|
|
fa23ec8fc6 | ||
|
|
6506171ea0 | ||
|
|
8bd1e72e9f | ||
|
|
4ce6629ace | ||
|
|
f9ef605903 | ||
|
|
c6b74e998f | ||
|
|
c4946b8466 | ||
|
|
ffa908bf27 | ||
|
|
0d37ff3f20 | ||
|
|
7aecdcf70a | ||
|
|
70f82d6db8 | ||
|
|
20b7870739 | ||
|
|
172639baea | ||
|
|
6038483b1e | ||
|
|
39d98d591c | ||
|
|
01c2fc0730 | ||
|
|
1884bb0067 | ||
|
|
1c368bbaa8 | ||
|
|
d16078a35f | ||
|
|
4dd04207ac | ||
|
|
02af42da30 | ||
|
|
2c75c8b36d | ||
|
|
013e6f7ce4 | ||
|
|
cbd7b62ad7 | ||
|
|
c1396f1c50 | ||
|
|
3ec9e7a734 | ||
|
|
3a1e6e84b1 | ||
|
|
7224419f77 | ||
|
|
056e4de0c1 | ||
|
|
aa90f22e23 | ||
|
|
071234095d | ||
|
|
5b06391159 | ||
|
|
8edd44086b | ||
|
|
ccf212e9cb | ||
|
|
493011d1f9 | ||
|
|
40e193df33 | ||
|
|
5068294d38 | ||
|
|
24054b5e2f | ||
|
|
17869c16cd | ||
|
|
d8aad89c2f | ||
|
|
2a349eb023 | ||
|
|
47ad07b3d2 | ||
|
|
aacf6522b4 | ||
|
|
c73d27b9ae | ||
|
|
f068b30a7c | ||
|
|
5400dddcfc | ||
|
|
6cf5fdc5d6 | ||
|
|
5d46663881 | ||
|
|
8e0f227940 | ||
|
|
73a13fff9a | ||
|
|
de2e505a12 | ||
|
|
a9f7c7a76f | ||
|
|
37401c26c9 | ||
|
|
528cd1e0e5 | ||
|
|
2959456bec | ||
|
|
8951712495 | ||
|
|
d8612aff64 | ||
|
|
e16732eb7b | ||
|
|
91f61bb9de | ||
|
|
ddc91d05ec | ||
|
|
ef22842b90 | ||
|
|
303e2152d2 | ||
|
|
37fc0d0d2a | ||
|
|
53987e1e5d | ||
|
|
3216d7770b | ||
|
|
3203ca2ff4 | ||
|
|
e22254cd51 | ||
|
|
7ed722f669 | ||
|
|
4864096b2a | ||
|
|
5161385de4 | ||
|
|
98e009b38f | ||
|
|
3863ab8f62 | ||
|
|
f576eb5125 | ||
|
|
314742ccd8 | ||
|
|
f9074811f9 | ||
|
|
5f3e1eb378 | ||
|
|
3c1ee20ca1 | ||
|
|
3768f5e68e | ||
|
|
765a560380 | ||
|
|
39ae3ac653 | ||
|
|
e48f4027e5 | ||
|
|
2fa1e98faf | ||
|
|
cedfa0ee2f | ||
|
|
92f44b390e | ||
|
|
65a42f9209 | ||
|
|
ebf53248cf | ||
|
|
289f637e8a | ||
|
|
d7c13f30c8 | ||
|
|
0dac17ae5e | ||
|
|
9a19a774fa | ||
|
|
81f49d5eb2 | ||
|
|
4f3b4ac2d2 | ||
|
|
e428056b52 | ||
|
|
8dc9d2989a | ||
|
|
fd8c90dcbb | ||
|
|
ffe4e5a8ab | ||
|
|
6e5026d270 | ||
|
|
946c4166dc | ||
|
|
7d2fb85a04 | ||
|
|
d6ec078519 | ||
|
|
32256fc4d9 | ||
|
|
37bbdfe7ff | ||
|
|
c906675cdf | ||
|
|
90bb5574c1 | ||
|
|
7b50dcd969 | ||
|
|
8d82f48a8f | ||
|
|
469f9fd219 | ||
|
|
1a5783ab4e | ||
|
|
3d25886d79 | ||
|
|
516b2cd372 | ||
|
|
3cfcfa0be2 | ||
|
|
69328087bd | ||
|
|
1bf8b2a52b | ||
|
|
fc6dc6f4e1 | ||
|
|
31c1feca4e | ||
|
|
3ed1eef2ab | ||
|
|
1394a017bb | ||
|
|
6b0670d5f1 | ||
|
|
f573331541 | ||
|
|
a7218cd3b8 | ||
|
|
f7e8178736 | ||
|
|
31f921e4aa | ||
|
|
aa1c25637c | ||
|
|
71f2c48463 | ||
|
|
d65f4ca898 | ||
|
|
b35568ffe5 | ||
|
|
a0d241e551 | ||
|
|
33a6934582 | ||
|
|
f237a8bbbc | ||
|
|
4bc775c01c | ||
|
|
93b4d81505 | ||
|
|
0afb85a09e | ||
|
|
7b9315cea4 | ||
|
|
dc3af45096 | ||
|
|
00ce0179ae | ||
|
|
81947ba3d8 | ||
|
|
49327279d0 | ||
|
|
0936cf3c7f | ||
|
|
43b75a69c2 | ||
|
|
c17c8d7a11 | ||
|
|
223fd8126f | ||
|
|
1d5559be4a | ||
|
|
0b749ff8bb | ||
|
|
069fa89fcb | ||
|
|
80f9f24243 | ||
|
|
93018c9843 | ||
|
|
e2675271d0 | ||
|
|
d1f7edecef | ||
|
|
b5a3c0b988 | ||
|
|
06e62b031a | ||
|
|
24707b7385 | ||
|
|
ab95f39f9f | ||
|
|
cdd38abab5 | ||
|
|
d751c0b3ab | ||
|
|
2ace0e9e5a | ||
|
|
31716f5104 | ||
|
|
fefcc7cba3 | ||
|
|
30df989e8d | ||
|
|
86bfea6bd4 | ||
|
|
d890f168c2 | ||
|
|
f888a810bf | ||
|
|
16249a4dde | ||
|
|
e33488ba55 | ||
|
|
bfe6eeddb1 | ||
|
|
7f540e741c | ||
|
|
b7855f5af5 | ||
|
|
91391e1205 | ||
|
|
d5199ec459 | ||
|
|
e1e6f4fd10 | ||
|
|
e5b6fccf67 | ||
|
|
95a912c329 | ||
|
|
2b4833fc4e | ||
|
|
b814fc851a | ||
|
|
e87863203b | ||
|
|
33b66d9e18 | ||
|
|
fd406f7897 | ||
|
|
7a836d1018 | ||
|
|
393c15c44a | ||
|
|
a7f31f9ebf | ||
|
|
b332644d48 | ||
|
|
510f7c0e7c | ||
|
|
fdd05e3d3a | ||
|
|
6d41958853 | ||
|
|
cecc4dfcf2 | ||
|
|
4ac40f2e90 | ||
|
|
3e12d44173 | ||
|
|
a42b39bd84 | ||
|
|
22affccf24 | ||
|
|
028724c47b | ||
|
|
0114bff2fc | ||
|
|
4ddbd644eb | ||
|
|
f0366531ef | ||
|
|
6bd5814f4a | ||
|
|
78c132fdab | ||
|
|
460cf57d7c | ||
|
|
a3190a8aca | ||
|
|
6921fb2fbf | ||
|
|
9954d571de | ||
|
|
17fe000a1e | ||
|
|
fba415e765 | ||
|
|
583b7ebf38 | ||
|
|
771a256925 | ||
|
|
ae79e89347 | ||
|
|
6c7eeb95eb | ||
|
|
de0bd1887f | ||
|
|
eb3a815d48 | ||
|
|
e2a4dcdff1 | ||
|
|
68b0d7bf2e | ||
|
|
a9b163992b | ||
|
|
2409965cf8 | ||
|
|
eada37378a | ||
|
|
0f1da1ba2a | ||
|
|
e0cdcd228e | ||
|
|
fdb5e014b5 | ||
|
|
cefcad775b | ||
|
|
e40feac1f1 | ||
|
|
3ef4ac7452 | ||
|
|
93bf1b09e7 | ||
|
|
a1e557e870 | ||
|
|
864adb7445 | ||
|
|
9e3f3f0c1c | ||
|
|
a40c4a435c | ||
|
|
17182dd0e8 | ||
|
|
d8b50044a2 | ||
|
|
4dc396e73d | ||
|
|
6ae0b0e892 | ||
|
|
7162ca3456 | ||
|
|
1b44c1a1b8 | ||
|
|
5b80b33e00 | ||
|
|
f05c1b2395 | ||
|
|
d9b2c73440 | ||
|
|
29a39f8e0a | ||
|
|
2d72a2bdb5 | ||
|
|
91da635631 | ||
|
|
68ab3d504a | ||
|
|
5187040a64 | ||
|
|
e0c63e7ce6 |
17
.github/CODEOWNERS
vendored
17
.github/CODEOWNERS
vendored
@@ -1,14 +1,7 @@
|
||||
# Lines starting with '#' are comments.
|
||||
# Each line is a file pattern followed by one or more owners.
|
||||
# Last match in file takes precedence.
|
||||
|
||||
# These owners will be the default owners for everything in the repo.
|
||||
# * @defunkt
|
||||
* @Acruid @PJB3005 @Silvertorch5
|
||||
# Be they Fluent translations or Freemarker templates, I know them both!
|
||||
*.ftl @RemieRichards
|
||||
|
||||
# Order is important. The last matching pattern has the most precedence.
|
||||
# So if a pull request only touches javascript files, only these owners
|
||||
# will be requested to review.
|
||||
# *.js @octocat @github/js
|
||||
|
||||
# You can also use email addresses if you prefer.
|
||||
# docs/* docs@example.com
|
||||
# Ping for all PRs
|
||||
* @Acruid @PJB3005 @Silvertorch5
|
||||
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
||||
2
.github/workflows/test-content.yml
vendored
2
.github/workflows/test-content.yml
vendored
@@ -38,4 +38,4 @@ jobs:
|
||||
- name: Content.Tests
|
||||
run: dotnet test --no-build Content.Tests/Content.Tests.csproj -v n
|
||||
- name: Content.IntegrationTests
|
||||
run: dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
|
||||
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -13,3 +13,9 @@
|
||||
[submodule "ManagedHttpListener"]
|
||||
path = ManagedHttpListener
|
||||
url = https://github.com/space-wizards/ManagedHttpListener.git
|
||||
[submodule "Linguini"]
|
||||
path = Linguini
|
||||
url = https://github.com/space-wizards/Linguini
|
||||
[submodule "cefglue"]
|
||||
path = cefglue
|
||||
url = https://gitlab.com/xiliumhq/chromiumembedded/cefglue/
|
||||
|
||||
7
Avalonia.Base/Avalonia.Base.csproj
Normal file
7
Avalonia.Base/Avalonia.Base.csproj
Normal file
@@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
2
Avalonia.Base/README.md
Normal file
2
Avalonia.Base/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
See `Robust.Client/UserInterface/XAML/RiderNotes.md` for why this project exists.
|
||||
We are not actually using Avalonia (yet).
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: 73554e6061...4b36b36f39
1
Linguini
Submodule
1
Linguini
Submodule
Submodule Linguini added at b3c05c2f31
@@ -4,5 +4,6 @@
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>9</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
Submodule NetSerializer updated: 1e103e8e29...ab1b651292
@@ -129,5 +129,7 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
/// for controlling sRGB rendering and a created OpenGL ES context will always have sRGB rendering enabled.
|
||||
/// </summary>
|
||||
SrgbCapable = 0x0002100E,
|
||||
|
||||
ScaleToMonitor = 0x0002200C,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5551,5 +5551,15 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
{
|
||||
return glfwGetX11Window(window);
|
||||
}
|
||||
|
||||
public static unsafe IntPtr GetX11Display(Window* window)
|
||||
{
|
||||
return glfwGetX11Display(window);
|
||||
}
|
||||
|
||||
public static unsafe IntPtr GetWin32Window(Window* window)
|
||||
{
|
||||
return glfwGetWin32Window(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return NativeLibrary.Load("libglfw.so.3", assembly, path);
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return NativeLibrary.Load("libglfw.3.dylib", assembly, path);
|
||||
}
|
||||
@@ -406,5 +406,11 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern uint glfwGetX11Window(Window* window);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern IntPtr glfwGetX11Display(Window* window);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern IntPtr glfwGetWin32Window(Window* window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,6 @@
|
||||
<PackageReference Condition="'$(TargetFramework)' == 'net472'" Include="System.Memory" Version="4.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="OpenToolkit.GraphicsLibraryFramework.dll.config">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<configuration>
|
||||
<!-- I actually have no idea whether this works on FreeBSD but it can't hurt to set it as such. -->
|
||||
<dllmap os="linux,freebsd" dll="glfw3.dll" target="glfw.so.3" />
|
||||
<dllmap os="osx" dll="glfw3.dll" target="glfw.3.dylib" />
|
||||
</configuration>
|
||||
25
README.md
25
README.md
@@ -1,25 +1,16 @@
|
||||

|
||||
|
||||
[](https://ci.appveyor.com/project/Silvertorch5/space-station-14/branch/master)
|
||||
[](https://sonarcloud.io/dashboard?id=ss14)
|
||||
Robust Toolbox is an engine primarily being developed for [Space Station 14](https://github.com/space-wizards/space-station-14), although we're working on making it usable for both [singleplayer](https://github.com/space-wizards/RobustToolboxTemplateSingleplayer) and [multiplayer](https://github.com/space-wizards/RobustToolboxTemplate) projects.
|
||||
|
||||
Robust Toolbox is a client/server backend for [Space Station 14](https://github.com/space-wizards/space-station-14).
|
||||
Use the [content repo](https://github.com/space-wizards/space-station-14) for actual development, even if you're modifying the engine itself.
|
||||
|
||||
### *This repository is the *engine* section of SS14. This is the base engine all SS14 servers will be built on. As such, it does not start on its own: it needs the [content repo](https://github.com/space-wizards/space-station-14). Use said repo for actual development, even if you're modifying the engine itself. Think of Robust Toolbox as BYOND in the context of Spacestation 13.*
|
||||
## Project Links
|
||||
|
||||
## Getting in Touch
|
||||
[Website](https://spacestation14.io/) | [Discord](https://discord.gg/t2jac3p) | [Forum](https://forum.spacestation14.io/) | [Steam](https://store.steampowered.com/app/1255460/Space_Station_14/) | [Standalone Download](https://spacestation14.io/about/nightlies/)
|
||||
|
||||
* Website: [spacestation14.io](https://spacestation14.io/)
|
||||
* Discord: [Invite Link](https://discord.gg/t2jac3p)
|
||||
* IRC: `irc.rizon.net#spacebus`
|
||||
* Code Analysis: [Sonar Cloud](https://sonarcloud.io/dashboard?id=ss14)
|
||||
* Automatic Content Builds: [builds.spacestation14.io](https://builds.spacestation14.io/jenkins/)
|
||||
## Documentation/Wiki
|
||||
|
||||
The IRC is setup to relay back and forth to the Discord server so [IRC nerds](https://xkcd.com/1782/) will not be left out.
|
||||
|
||||
## Documentation
|
||||
|
||||
We have various technical documentation articles on the [HackMD Wiki](https://hackmd.io/@ss14/docs/%2F%40ss14%2Ftechnical-documentation-overview).
|
||||
The [HackMD Wiki](https://hackmd.io/@ss14/docs/wiki) has documentation on SS14s content, engine, game design and more. We also have lots of resources for new contributors to the project.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -27,8 +18,8 @@ We are happy to accept contributions from anybody. Get in Discord or IRC if you
|
||||
|
||||
## Building
|
||||
|
||||
**In practice, you usually don't build this repository directly.**
|
||||
This repository is the **engine** part of SS14. It's the base engine all SS14 servers will be built on. As such, it does not start on its own: it needs the [content repo](https://github.com/space-wizards/space-station-14). Think of Robust Toolbox as BYOND in the context of Spacestation 13.
|
||||
|
||||
## Legal Info
|
||||
|
||||
See `legal.md` for licenses and copyright.
|
||||
See [legal.md](https://github.com/space-wizards/RobustToolbox/blob/master/legal.md) for licenses and copyright.
|
||||
|
||||
@@ -23,6 +23,48 @@
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
- name: Box2D
|
||||
license: |
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Erin Catto
|
||||
|
||||
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.
|
||||
|
||||
- name: Bullet Physics SDK
|
||||
license: |
|
||||
The files in this repository are licensed under the zlib license, except for the files under 'Extras' and examples/ThirdPartyLibs.
|
||||
|
||||
Bullet Continuous Collision Detection and Physics Library
|
||||
http://bulletphysics.org
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it freely,
|
||||
subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
|
||||
If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
- name: Castle Core
|
||||
license: |
|
||||
Copyright 2004-2016 Castle Project - http://www.castleproject.org/
|
||||
@@ -317,6 +359,43 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
- name: Farseer Physics Engine
|
||||
license: |
|
||||
Microsoft Permissive License (Ms-PL)
|
||||
|
||||
This license governs use of the accompanying software.
|
||||
If you use the software, you accept this license.
|
||||
If you do not accept the license, do not use the software.
|
||||
|
||||
1. Definitions
|
||||
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
|
||||
A "contribution" is the original software, or any additions or changes to the software.
|
||||
A "contributor" is any person that distributes its contribution under this license.
|
||||
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
||||
|
||||
2. Grant of Rights
|
||||
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution,
|
||||
prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
||||
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to
|
||||
make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or
|
||||
derivative works of the contribution in the software.
|
||||
|
||||
3. Conditions and Limitations
|
||||
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
||||
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
|
||||
your patent license from such contributor to the software ends automatically.
|
||||
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
|
||||
and attribution notices that are present in the software.
|
||||
(D) If you distribute any portion of the software in source code form, you may do so only under this license by
|
||||
including a complete copy of this license with your distribution. If you distribute any portion of the software in
|
||||
compiled or object code form, you may only do so under a license that complies with this license.
|
||||
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties,
|
||||
guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change.
|
||||
To the extent permitted under your local laws, the contributors exclude the implied warranties of
|
||||
merchantability, fitness for a particular purpose and non-infringement.
|
||||
|
||||
- name: Mono.Cecil
|
||||
license: |
|
||||
Copyright (c) 2008 - 2015 Jb Evain
|
||||
@@ -574,29 +653,22 @@
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
- name: NGetText
|
||||
- name: Fluent.Net
|
||||
license: |
|
||||
The MIT License (MIT)
|
||||
blushingpenguin and Contributors
|
||||
|
||||
Copyright (c) 2012 Vitaly Zilnik
|
||||
All rights reserved.
|
||||
|
||||
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:
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
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.
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
|
||||
- name: NetSerializer
|
||||
license: |
|
||||
|
||||
11
Resources/Locale/en-US/custom-controls.ftl
Normal file
11
Resources/Locale/en-US/custom-controls.ftl
Normal file
@@ -0,0 +1,11 @@
|
||||
## EntitySpawnWindow
|
||||
|
||||
entity-spawn-window-title = Entity Spawn Panel
|
||||
entity-spawn-window-search-bar-placeholder = search
|
||||
entity-spawn-window-clear-button = Clear
|
||||
entity-spawn-window-erase-button-text = Erase Mode
|
||||
entity-spawn-window-override-menu-tooltip = Override placement
|
||||
|
||||
## Console
|
||||
|
||||
console-line-edit-placeholder = Command Here
|
||||
54
Resources/Locale/en-US/input.ftl
Normal file
54
Resources/Locale/en-US/input.ftl
Normal file
@@ -0,0 +1,54 @@
|
||||
input-key-Escape = Escape
|
||||
input-key-Control = Control
|
||||
input-key-Shift = Shift
|
||||
input-key-Alt = Alt
|
||||
input-key-Menu = Menu
|
||||
input-key-F1 = F1
|
||||
input-key-F2 = F2
|
||||
input-key-F3 = F3
|
||||
input-key-F4 = F4
|
||||
input-key-F5 = F5
|
||||
input-key-F6 = F6
|
||||
input-key-F7 = F7
|
||||
input-key-F8 = F8
|
||||
input-key-F9 = F9
|
||||
input-key-F10 = F10
|
||||
input-key-F11 = F11
|
||||
input-key-F12 = F12
|
||||
input-key-F13 = F13
|
||||
input-key-F14 = F14
|
||||
input-key-F15 = F15
|
||||
input-key-Pause = Pause
|
||||
input-key-Left = Left
|
||||
input-key-Up = Up
|
||||
input-key-Down = Down
|
||||
input-key-Right = Right
|
||||
input-key-Space = Space
|
||||
input-key-Return = Return
|
||||
input-key-NumpadEnter = Num Enter
|
||||
input-key-BackSpace = Backspace
|
||||
input-key-Tab = Tab
|
||||
input-key-PageUp = Page Up
|
||||
input-key-PageDown = Page Down
|
||||
input-key-End = End
|
||||
input-key-Home = Home
|
||||
input-key-Insert = Insert
|
||||
input-key-Delete = Delete
|
||||
input-key-MouseLeft = Mouse Left
|
||||
input-key-MouseRight = Mouse Right
|
||||
input-key-MouseMiddle = Mouse Middle
|
||||
input-key-MouseButton4 = Mouse 4
|
||||
input-key-MouseButton5 = Mouse 5
|
||||
input-key-MouseButton6 = Mouse 6
|
||||
input-key-MouseButton7 = Mouse 7
|
||||
input-key-MouseButton8 = Mouse 8
|
||||
input-key-MouseButton9 = Mouse 9
|
||||
|
||||
input-key-LSystem-win = Left Win
|
||||
input-key-RSystem-win = Right Win
|
||||
input-key-LSystem-mac = Left Cmd
|
||||
input-key-RSystem-mac = Right Cmd
|
||||
input-key-LSystem-linux = Left Meta
|
||||
input-key-RSystem-linux = Right Meta
|
||||
|
||||
input-key-unknown = <unknown key>
|
||||
1
Resources/Locale/en-US/ss14window.ftl
Normal file
1
Resources/Locale/en-US/ss14window.ftl
Normal file
@@ -0,0 +1 @@
|
||||
ss14window-placeholder-title = Exemplary Window Title Here
|
||||
1
Resources/Locale/en-US/tab-container.ftl
Normal file
1
Resources/Locale/en-US/tab-container.ftl
Normal file
@@ -0,0 +1 @@
|
||||
tab-container-not-tab-title-provided = No title
|
||||
11
Resources/Locale/en-US/view-variables.ftl
Normal file
11
Resources/Locale/en-US/view-variables.ftl
Normal file
@@ -0,0 +1,11 @@
|
||||
## ViewVariablesInstanceEntity
|
||||
|
||||
view-variable-instance-entity-server-components-add-component-button-placeholder = Add Component
|
||||
view-variable-instance-entity-client-variables-tab-title = Client Variables
|
||||
view-variable-instance-entity-client-components-tab-title = Client Components
|
||||
view-variable-instance-entity-server-variables-tab-title = Server Variables
|
||||
view-variable-instance-entity-server-components-tab-title = Server Components
|
||||
view-variable-instance-entity-client-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-server-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-add-window-server-components = Add Component [S]
|
||||
view-variable-instance-entity-add-window-client-components = Add Component [C]
|
||||
@@ -28,5 +28,10 @@ void fragment()
|
||||
|
||||
highp float occlusion = ChebyshevUpperBound(occlDist, ourDist);
|
||||
|
||||
if (occlusion >= 1.0)
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
COLOR = vec4(0.0, 0.0, 0.0, 1.0 - occlusion);
|
||||
}
|
||||
|
||||
41
Resources/Shaders/Internal/light-blur.swsl
Normal file
41
Resources/Shaders/Internal/light-blur.swsl
Normal file
@@ -0,0 +1,41 @@
|
||||
preset raw;
|
||||
|
||||
uniform highp vec2 direction;
|
||||
uniform highp vec2 size;
|
||||
uniform highp float radius;
|
||||
|
||||
varying highp vec2 pos;
|
||||
varying highp vec4 blurPos1;
|
||||
varying highp vec4 blurPos2;
|
||||
|
||||
|
||||
void vertex()
|
||||
{
|
||||
highp float aspect = size.y / size.x;
|
||||
highp float horRadius = aspect * radius;
|
||||
|
||||
highp vec2 offset = vec2(horRadius, radius) * direction;
|
||||
|
||||
VERTEX = apply_mvp(VERTEX);
|
||||
|
||||
pos = (VERTEX + vec2(1.0)) / 2.0;
|
||||
|
||||
blurPos1.xy = pos + offset;
|
||||
blurPos1.zw = pos - offset;
|
||||
blurPos2.xy = pos + offset * 2.0;
|
||||
blurPos2.zw = pos - offset * 2.0;
|
||||
}
|
||||
|
||||
|
||||
void fragment()
|
||||
{
|
||||
// Very simple gaussian blur.
|
||||
highp vec4 sum = zTexture(pos) * 0.375;
|
||||
|
||||
sum += zTexture(blurPos1.xy) * 0.25;
|
||||
sum += zTexture(blurPos1.zw) * 0.25;
|
||||
sum += zTexture(blurPos2.xy) * 0.0625;
|
||||
sum += zTexture(blurPos2.zw) * 0.0625;
|
||||
|
||||
COLOR = sum;
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
#include "/Shaders/Internal/light_shared.swsl"
|
||||
|
||||
highp vec4 calcGaussianWeights(highp float sigma, highp vec4 offset)
|
||||
{
|
||||
highp vec4 eExp = offset * offset / (2.0 * sigma * sigma);
|
||||
return exp(-eExp) / (sigma * sqrt(2.0 * PI));
|
||||
}
|
||||
|
||||
highp float createOcclusion(highp vec2 diff)
|
||||
{
|
||||
// Calculate vector perpendicular to light vector.
|
||||
@@ -8,23 +14,57 @@ highp float createOcclusion(highp vec2 diff)
|
||||
|
||||
highp float ourDist = length(diff);
|
||||
|
||||
highp vec2 occlDist = occludeDepth(diff, shadowMap, lightIndex);
|
||||
// Sample 7 points on a line perpendicular to the light source.
|
||||
// Depending on the closest point, we change the gaussian weights down below
|
||||
// to change the "narrowness" of the samples.
|
||||
perpendicular *= lightSoftness * 1.5;
|
||||
|
||||
// Get all the samples we need.
|
||||
highp vec2 sample1 = occludeDepth(diff, shadowMap, lightIndex);
|
||||
highp vec2 sample2 = occludeDepth(diff + perpendicular, shadowMap, lightIndex);
|
||||
highp vec2 sample3 = occludeDepth(diff - perpendicular, shadowMap, lightIndex);
|
||||
highp vec2 sample4 = occludeDepth(diff + perpendicular * 2.0, shadowMap, lightIndex);
|
||||
highp vec2 sample5 = occludeDepth(diff - perpendicular * 2.0, shadowMap, lightIndex);
|
||||
highp vec2 sample6 = occludeDepth(diff + perpendicular * 3.0, shadowMap, lightIndex);
|
||||
highp vec2 sample7 = occludeDepth(diff - perpendicular * 3.0, shadowMap, lightIndex);
|
||||
|
||||
highp float mindist =
|
||||
min(sample1.x,
|
||||
min(sample2.x,
|
||||
min(sample3.x,
|
||||
min(sample4.x,
|
||||
min(sample5.x,
|
||||
min(sample6.x,
|
||||
sample7.x))))));
|
||||
|
||||
mindist = max(0.001, mindist);
|
||||
|
||||
// Change soft shadow size based on distance from primary occluder.
|
||||
highp float distRatio = (ourDist - occlDist.x) / occlDist.x / 2.0;
|
||||
highp float distRatio = (ourDist - mindist);
|
||||
|
||||
perpendicular *= distRatio * lightSoftness;
|
||||
// Sigma can never be zero so make sure to clamp.
|
||||
// TODO: Scaling the dist ratio here in a more sane way might make shadows look better buuuut I'm lazy.
|
||||
// Shadows look pretty nice already.
|
||||
highp float sigma = max(0.001, distRatio * 0.75);
|
||||
highp vec4 weights = calcGaussianWeights(sigma, vec4(0.0, 1.0, 2.0, 3.0));
|
||||
|
||||
// Totally not hacky PCF on top of VSM.
|
||||
highp float occlusion = smoothstep(0.1, 1.0, ChebyshevUpperBound(occlDist, ourDist));
|
||||
// Calculation of gaussian weights here is broken because it doesn't add up to 1.
|
||||
// Fixing this is hard and if I had to guess too expensive for GPU shaders.
|
||||
// So instead we add up the total weights and scale the result with that,
|
||||
// so that we still end up with 0-1.
|
||||
highp float totalWeigths = weights.x + weights.y * 2.0 + weights.z * 2.0 + weights.w * 2.0;
|
||||
|
||||
occlusion += shadowContrib(diff + perpendicular);
|
||||
occlusion += shadowContrib(diff - perpendicular);
|
||||
occlusion += shadowContrib(diff + perpendicular * 2.0);
|
||||
occlusion += shadowContrib(diff - perpendicular * 2.0);
|
||||
occlusion += shadowContrib(diff + perpendicular * 3.0);
|
||||
occlusion += shadowContrib(diff - perpendicular * 3.0);
|
||||
highp float occlusion = 0.0;
|
||||
|
||||
return occlusion / 7.0;
|
||||
// Calculate actual occlusion with new weights.
|
||||
occlusion += ChebyshevUpperBound(sample1, ourDist) * weights.x;
|
||||
occlusion += ChebyshevUpperBound(sample2, ourDist) * weights.y;
|
||||
occlusion += ChebyshevUpperBound(sample3, ourDist) * weights.y;
|
||||
occlusion += ChebyshevUpperBound(sample4, ourDist) * weights.z;
|
||||
occlusion += ChebyshevUpperBound(sample5, ourDist) * weights.z;
|
||||
occlusion += ChebyshevUpperBound(sample6, ourDist) * weights.w;
|
||||
occlusion += ChebyshevUpperBound(sample7, ourDist) * weights.w;
|
||||
|
||||
return occlusion / totalWeigths;
|
||||
}
|
||||
|
||||
|
||||
BIN
Resources/Textures/debugRotation.rsi/direction1.png
Normal file
BIN
Resources/Textures/debugRotation.rsi/direction1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 359 B |
BIN
Resources/Textures/debugRotation.rsi/direction1.xcf
Normal file
BIN
Resources/Textures/debugRotation.rsi/direction1.xcf
Normal file
Binary file not shown.
BIN
Resources/Textures/debugRotation.rsi/direction4.png
Normal file
BIN
Resources/Textures/debugRotation.rsi/direction4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Resources/Textures/debugRotation.rsi/direction4.xcf
Normal file
BIN
Resources/Textures/debugRotation.rsi/direction4.xcf
Normal file
Binary file not shown.
17
Resources/Textures/debugRotation.rsi/meta.json
Normal file
17
Resources/Textures/debugRotation.rsi/meta.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [{
|
||||
"name": "direction1",
|
||||
"directions": 1,
|
||||
"delays": [[1.0]]
|
||||
}, {
|
||||
"name": "direction4",
|
||||
"directions": 4,
|
||||
"delays": [[1.0], [1.0], [1.0], [1.0]]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Resources/Textures/debugRotation.rsi/uvmap.xcf
Normal file
BIN
Resources/Textures/debugRotation.rsi/uvmap.xcf
Normal file
Binary file not shown.
10
Robust.Analyzers/Diagnostics.cs
Normal file
10
Robust.Analyzers/Diagnostics.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Robust.Generators
|
||||
{
|
||||
public static class Diagnostics
|
||||
{
|
||||
public static SuppressionDescriptor MeansImplicitAssignment =>
|
||||
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
|
||||
}
|
||||
}
|
||||
107
Robust.Analyzers/FriendAnalyzer.cs
Normal file
107
Robust.Analyzers/FriendAnalyzer.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public class FriendAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
const string FriendAttribute = "Robust.Shared.Analyzers.FriendAttribute";
|
||||
|
||||
public const string DiagnosticId = "RA0002";
|
||||
|
||||
private const string Title = "Tried to access friend-only member";
|
||||
private const string MessageFormat = "Tried to access member \"{0}\" in class \"{1}\" which can only be accessed by friend classes";
|
||||
private const string Description = "Make sure to specify the accessing class in the friends attribute.";
|
||||
private const string Category = "Usage";
|
||||
|
||||
[SuppressMessage("ReSharper", "RS2008")]
|
||||
private static readonly DiagnosticDescriptor Rule = new (DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, true, Description);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterSyntaxNodeAction(CheckFriendship, SyntaxKind.SimpleMemberAccessExpression);
|
||||
}
|
||||
|
||||
private void CheckFriendship(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not MemberAccessExpressionSyntax memberAccess)
|
||||
return;
|
||||
|
||||
// We only do something if our parent is one of a few types.
|
||||
switch (context.Node.Parent)
|
||||
{
|
||||
// If we're being assigned...
|
||||
case AssignmentExpressionSyntax assignParent:
|
||||
{
|
||||
if (assignParent.Left != memberAccess)
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
// If we're being invoked...
|
||||
case InvocationExpressionSyntax:
|
||||
break;
|
||||
|
||||
// Otherwise, do nothing.
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the friend attribute
|
||||
var friendAttr = context.Compilation.GetTypeByMetadataName(FriendAttribute);
|
||||
|
||||
// Get the type that is containing this expression, or, the class where this is happening.
|
||||
if (context.ContainingSymbol?.ContainingType is not { } containingType)
|
||||
return;
|
||||
|
||||
// We check all of our children and get only the identifiers.
|
||||
foreach (var identifier in memberAccess.ChildNodes().Select(node => node as IdentifierNameSyntax))
|
||||
{
|
||||
if (identifier == null) continue;
|
||||
|
||||
// Get the type info of the identifier, so we can check the attributes...
|
||||
if (context.SemanticModel.GetTypeInfo(identifier).ConvertedType is not { } type)
|
||||
continue;
|
||||
|
||||
// Same-type access is always fine.
|
||||
if (SymbolEqualityComparer.Default.Equals(type, containingType))
|
||||
continue;
|
||||
|
||||
// Finally, get all attributes of the type, to check if we have any friend classes.
|
||||
foreach (var attribute in type.GetAttributes())
|
||||
{
|
||||
// If the attribute isn't the friend attribute, continue.
|
||||
if (!SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, friendAttr))
|
||||
continue;
|
||||
|
||||
// Check all types allowed in the friend attribute. (We assume there's only one constructor arg.)
|
||||
foreach (var constant in attribute.ConstructorArguments[0].Values)
|
||||
{
|
||||
// Check if the value is a type...
|
||||
if (constant.Value is not INamedTypeSymbol t)
|
||||
continue;
|
||||
|
||||
// If we find that the containing class is specified in the attribute, return! All is good.
|
||||
if (SymbolEqualityComparer.Default.Equals(containingType, t))
|
||||
return;
|
||||
}
|
||||
|
||||
// Not in a friend class! Report an error.
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(Rule, context.Node.GetLocation(),
|
||||
$"{context.Node.ToString().Split('.').LastOrDefault()}", $"{type.Name}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Robust.Analyzers/MeansImplicitAssigmentSuppressor.cs
Normal file
42
Robust.Analyzers/MeansImplicitAssigmentSuppressor.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Generators;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public class MeansImplicitAssigmentSuppressor : DiagnosticSuppressor
|
||||
{
|
||||
const string MeansImplicitAssignmentAttribute = "Robust.Shared.MeansImplicitAssignmentAttribute";
|
||||
|
||||
public override void ReportSuppressions(SuppressionAnalysisContext context)
|
||||
{
|
||||
var implAttr = context.Compilation.GetTypeByMetadataName(MeansImplicitAssignmentAttribute);
|
||||
foreach (var reportedDiagnostic in context.ReportedDiagnostics)
|
||||
{
|
||||
if(reportedDiagnostic.Id != Diagnostics.MeansImplicitAssignment.SuppressedDiagnosticId) continue;
|
||||
|
||||
var node = reportedDiagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(reportedDiagnostic.Location.SourceSpan);
|
||||
if (node == null) continue;
|
||||
|
||||
var symbol = context.GetSemanticModel(reportedDiagnostic.Location.SourceTree).GetDeclaredSymbol(node);
|
||||
|
||||
if (symbol == null || !symbol.GetAttributes().Any(a =>
|
||||
a.AttributeClass?.GetAttributes().Any(attr =>
|
||||
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, implAttr)) == true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
context.ReportSuppression(Suppression.Create(
|
||||
Diagnostics.MeansImplicitAssignment,
|
||||
reportedDiagnostic));
|
||||
}
|
||||
}
|
||||
|
||||
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(Diagnostics.MeansImplicitAssignment);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>9</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
28
Robust.Benchmarks/NumericsHelpers/AddBenchmark.cs
Normal file
28
Robust.Benchmarks/NumericsHelpers/AddBenchmark.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Robust.Benchmarks.NumericsHelpers
|
||||
{
|
||||
public class AddBenchmark
|
||||
{
|
||||
[Params(32, 128)]
|
||||
public int N { get; set; }
|
||||
|
||||
private float[] _inputA = default!;
|
||||
private float[] _inputB = default!;
|
||||
private float[] _output = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_inputA = new float[N];
|
||||
_inputB = new float[N];
|
||||
_output = new float[N];
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Bench()
|
||||
{
|
||||
Shared.Maths.NumericsHelpers.Add(_inputA, _inputB, _output);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Robust.Benchmarks/Program.cs
Normal file
14
Robust.Benchmarks/Program.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using BenchmarkDotNet.Running;
|
||||
|
||||
namespace Robust.Benchmarks
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
// --allCategories=ctg1,ctg2
|
||||
// --anyCategories=ctg1,ctg2
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Robust.Benchmarks/Robust.Benchmarks.csproj
Normal file
18
Robust.Benchmarks/Robust.Benchmarks.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\MSBuild\Robust.Engine.props" />
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputPath>../bin/Benchmarks</OutputPath>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robust.Server\Robust.Server.csproj" />
|
||||
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\MSBuild\Robust.Engine.targets" />
|
||||
</Project>
|
||||
40
Robust.Benchmarks/Serialization/BenchmarkIntSerializer.cs
Normal file
40
Robust.Benchmarks/Serialization/BenchmarkIntSerializer.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Globalization;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Result;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization
|
||||
{
|
||||
public class BenchmarkIntSerializer : ITypeSerializer<int, ValueDataNode>
|
||||
{
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,
|
||||
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
{
|
||||
return int.TryParse(node.Value, out _)
|
||||
? new ValidatedValueNode(node)
|
||||
: new ErrorNode(node, $"Failed parsing int value: {node.Value}");
|
||||
}
|
||||
|
||||
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
|
||||
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
|
||||
{
|
||||
return new DeserializedValue<int>(int.Parse(node.Value, CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, int value, bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return new ValueDataNode(value.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public int Copy(ISerializationManager serializationManager, int source, int target, bool skipHook,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return source;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Copy
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class SerializationCopyBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationCopyBenchmark()
|
||||
{
|
||||
InitializeSerialization();
|
||||
|
||||
DataDefinitionWithString = new DataDefinitionWithString {StringField = "ABC"};
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));
|
||||
|
||||
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
|
||||
|
||||
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
|
||||
}
|
||||
|
||||
private const string String = "ABC";
|
||||
|
||||
private const int Integer = 1;
|
||||
|
||||
private DataDefinitionWithString DataDefinitionWithString { get; }
|
||||
|
||||
private SeedDataDefinition Seed { get; }
|
||||
|
||||
private BenchmarkFlagsEnum FlagZero = BenchmarkFlagsEnum.Zero;
|
||||
|
||||
private BenchmarkFlagsEnum FlagThirtyOne = BenchmarkFlagsEnum.ThirtyOne;
|
||||
|
||||
[Benchmark]
|
||||
public string? CreateCopyString()
|
||||
{
|
||||
return SerializationManager.CreateCopy(String);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int? CreateCopyInteger()
|
||||
{
|
||||
return SerializationManager.CreateCopy(Integer);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataDefinitionWithString? CreateCopyDataDefinitionWithString()
|
||||
{
|
||||
return SerializationManager.CreateCopy(DataDefinitionWithString);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public SeedDataDefinition? CreateCopySeedDataDefinition()
|
||||
{
|
||||
return SerializationManager.CreateCopy(Seed);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public SeedDataDefinition BaselineCreateCopySeedDataDefinition()
|
||||
{
|
||||
// ReSharper disable once UseObjectOrCollectionInitializer
|
||||
var copy = new SeedDataDefinition();
|
||||
|
||||
copy.ID = Seed.ID;
|
||||
copy.Name = Seed.Name;
|
||||
copy.SeedName = Seed.SeedName;
|
||||
copy.SeedNoun = Seed.SeedNoun;
|
||||
copy.DisplayName = Seed.DisplayName;
|
||||
copy.RoundStart = Seed.RoundStart;
|
||||
copy.Mysterious = Seed.Mysterious;
|
||||
copy.Immutable = Seed.Immutable;
|
||||
|
||||
copy.ProductPrototypes = Seed.ProductPrototypes.ToList();
|
||||
copy.Chemicals = Seed.Chemicals.ToDictionary(p => p.Key, p => p.Value);
|
||||
copy.ConsumeGasses = Seed.ConsumeGasses.ToDictionary(p => p.Key, p => p.Value);
|
||||
copy.ExudeGasses = Seed.ExudeGasses.ToDictionary(p => p.Key, p => p.Value);
|
||||
|
||||
copy.NutrientConsumption = Seed.NutrientConsumption;
|
||||
copy.WaterConsumption = Seed.WaterConsumption;
|
||||
copy.IdealHeat = Seed.IdealHeat;
|
||||
copy.HeatTolerance = Seed.HeatTolerance;
|
||||
copy.IdealLight = Seed.IdealLight;
|
||||
copy.LightTolerance = Seed.LightTolerance;
|
||||
copy.ToxinsTolerance = Seed.ToxinsTolerance;
|
||||
copy.LowPressureTolerance = Seed.LowPressureTolerance;
|
||||
copy.HighPressureTolerance = Seed.HighPressureTolerance;
|
||||
copy.PestTolerance = Seed.PestTolerance;
|
||||
copy.WeedTolerance = Seed.WeedTolerance;
|
||||
|
||||
copy.Endurance = Seed.Endurance;
|
||||
copy.Yield = Seed.Yield;
|
||||
copy.Lifespan = Seed.Lifespan;
|
||||
copy.Maturation = Seed.Maturation;
|
||||
copy.Production = Seed.Production;
|
||||
copy.GrowthStages = Seed.GrowthStages;
|
||||
copy.HarvestRepeat = Seed.HarvestRepeat;
|
||||
copy.Potency = Seed.Potency;
|
||||
copy.Ligneous = Seed.Ligneous;
|
||||
|
||||
copy.PlantRsi = Seed.PlantRsi == null
|
||||
? null!
|
||||
: new ResourcePath(Seed.PlantRsi.ToString(), Seed.PlantRsi.Separator);
|
||||
copy.PlantIconState = Seed.PlantIconState;
|
||||
copy.Bioluminescent = Seed.Bioluminescent;
|
||||
copy.BioluminescentColor = Seed.BioluminescentColor;
|
||||
copy.SplatPrototype = Seed.SplatPrototype;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public object? CopyFlagZero()
|
||||
{
|
||||
return SerializationManager.CopyWithTypeSerializer(
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
(int) FlagZero,
|
||||
(int) FlagZero);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public object? CopyFlagThirtyOne()
|
||||
{
|
||||
return SerializationManager.CopyWithTypeSerializer(
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
(int) FlagThirtyOne,
|
||||
(int) FlagThirtyOne);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("customTypeSerializer")]
|
||||
public object? CopyIntegerCustomSerializer()
|
||||
{
|
||||
return SerializationManager.CopyWithTypeSerializer(
|
||||
typeof(BenchmarkIntSerializer),
|
||||
Integer,
|
||||
Integer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Definitions
|
||||
{
|
||||
public class BenchmarkFlags
|
||||
{
|
||||
public const int Zero = 1 << 0;
|
||||
public const int ThirtyOne = 1 << 31;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
[FlagsFor(typeof(BenchmarkFlags))]
|
||||
public enum BenchmarkFlagsEnum
|
||||
{
|
||||
Zero = BenchmarkFlags.Zero,
|
||||
ThirtyOne = BenchmarkFlags.ThirtyOne
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Definitions
|
||||
{
|
||||
[DataDefinition]
|
||||
public class DataDefinitionWithString
|
||||
{
|
||||
[DataField("string")]
|
||||
public string StringField { get; init; } = default!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Definitions
|
||||
{
|
||||
[DataDefinition]
|
||||
public sealed class SealedDataDefinitionWithString
|
||||
{
|
||||
[DataField("string")]
|
||||
public string StringField { get; init; } = default!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Definitions
|
||||
{
|
||||
/// <summary>
|
||||
/// Arbitrarily large data definition for benchmarks.
|
||||
/// Taken from content.
|
||||
/// </summary>
|
||||
[Prototype("seed")]
|
||||
public class SeedDataDefinition : IPrototype
|
||||
{
|
||||
public const string Prototype = @"
|
||||
- type: seed
|
||||
id: tobacco
|
||||
name: tobacco
|
||||
seedName: tobacco
|
||||
displayName: tobacco plant
|
||||
productPrototypes:
|
||||
- LeavesTobacco
|
||||
harvestRepeat: Repeat
|
||||
lifespan: 75
|
||||
maturation: 5
|
||||
production: 5
|
||||
yield: 2
|
||||
potency: 20
|
||||
growthStages: 3
|
||||
idealLight: 9
|
||||
idealHeat: 298
|
||||
chemicals:
|
||||
chem.Nicotine:
|
||||
Min: 1
|
||||
Max: 10
|
||||
PotencyDivisor: 10";
|
||||
|
||||
[DataField("id", required: true)] public string ID { get; set; } = default!;
|
||||
|
||||
#region Tracking
|
||||
[DataField("name")] public string Name { get; set; } = string.Empty;
|
||||
[DataField("seedName")] public string SeedName { get; set; } = string.Empty;
|
||||
[DataField("seedNoun")] public string SeedNoun { get; set; } = "seeds";
|
||||
[DataField("displayName")] public string DisplayName { get; set; } = string.Empty;
|
||||
[DataField("roundStart")] public bool RoundStart { get; set; } = true;
|
||||
[DataField("mysterious")] public bool Mysterious { get; set; }
|
||||
[DataField("immutable")] public bool Immutable { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Output
|
||||
[DataField("productPrototypes")]
|
||||
public List<string> ProductPrototypes { get; set; } = new();
|
||||
[DataField("chemicals")]
|
||||
public Dictionary<string, SeedChemQuantity> Chemicals { get; set; } = new();
|
||||
[DataField("consumeGasses")]
|
||||
public Dictionary<Gas, float> ConsumeGasses { get; set; } = new();
|
||||
[DataField("exudeGasses")]
|
||||
public Dictionary<Gas, float> ExudeGasses { get; set; } = new();
|
||||
#endregion
|
||||
|
||||
#region Tolerances
|
||||
[DataField("nutrientConsumption")] public float NutrientConsumption { get; set; } = 0.25f;
|
||||
[DataField("waterConsumption")] public float WaterConsumption { get; set; } = 3f;
|
||||
[DataField("idealHeat")] public float IdealHeat { get; set; } = 293f;
|
||||
[DataField("heatTolerance")] public float HeatTolerance { get; set; } = 20f;
|
||||
[DataField("idealLight")] public float IdealLight { get; set; } = 7f;
|
||||
[DataField("lightTolerance")] public float LightTolerance { get; set; } = 5f;
|
||||
[DataField("toxinsTolerance")] public float ToxinsTolerance { get; set; } = 4f;
|
||||
[DataField("lowPressureTolerance")] public float LowPressureTolerance { get; set; } = 25f;
|
||||
[DataField("highPressureTolerance")] public float HighPressureTolerance { get; set; } = 200f;
|
||||
[DataField("pestTolerance")] public float PestTolerance { get; set; } = 5f;
|
||||
[DataField("weedTolerance")] public float WeedTolerance { get; set; } = 5f;
|
||||
#endregion
|
||||
|
||||
#region General traits
|
||||
[DataField("endurance")] public float Endurance { get; set; } = 100f;
|
||||
[DataField("yield")] public int Yield { get; set; }
|
||||
[DataField("lifespan")] public float Lifespan { get; set; }
|
||||
[DataField("maturation")] public float Maturation { get; set; }
|
||||
[DataField("production")] public float Production { get; set; }
|
||||
[DataField("growthStages")] public int GrowthStages { get; set; } = 6;
|
||||
[DataField("harvestRepeat")] public HarvestType HarvestRepeat { get; set; } = HarvestType.NoRepeat;
|
||||
[DataField("potency")] public float Potency { get; set; } = 1f;
|
||||
[DataField("ligneous")] public bool Ligneous { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Cosmetics
|
||||
[DataField("plantRsi", required: true)] public ResourcePath PlantRsi { get; set; } = default!;
|
||||
[DataField("plantIconState")] public string PlantIconState { get; set; } = "produce";
|
||||
[DataField("bioluminescent")] public bool Bioluminescent { get; set; }
|
||||
[DataField("bioluminescentColor")] public Color BioluminescentColor { get; set; } = Color.White;
|
||||
[DataField("splatPrototype")] public string? SplatPrototype { get; set; }
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum HarvestType
|
||||
{
|
||||
NoRepeat,
|
||||
Repeat
|
||||
}
|
||||
|
||||
public enum Gas
|
||||
{
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public struct SeedChemQuantity
|
||||
{
|
||||
[DataField("Min")]
|
||||
public int Min;
|
||||
|
||||
[DataField("Max")]
|
||||
public int Max;
|
||||
|
||||
[DataField("PotencyDivisor")]
|
||||
public int PotencyDivisor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Initialize
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class SerializationInitializeBenchmark : SerializationBenchmark
|
||||
{
|
||||
[IterationCleanup]
|
||||
public void IterationCleanup()
|
||||
{
|
||||
SerializationManager.Shutdown();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ISerializationManager Initialize()
|
||||
{
|
||||
InitializeSerialization();
|
||||
return SerializationManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System.IO;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Manager.Result;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Read
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class SerializationReadBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationReadBenchmark()
|
||||
{
|
||||
InitializeSerialization();
|
||||
|
||||
StringDataDefNode = new MappingDataNode();
|
||||
StringDataDefNode.Add(new ValueDataNode("string"), new ValueDataNode("ABC"));
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));
|
||||
|
||||
SeedNode = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
|
||||
}
|
||||
|
||||
private ValueDataNode StringNode { get; } = new("ABC");
|
||||
|
||||
private ValueDataNode IntNode { get; } = new("1");
|
||||
|
||||
private MappingDataNode StringDataDefNode { get; }
|
||||
|
||||
private MappingDataNode SeedNode { get; }
|
||||
|
||||
private ValueDataNode FlagZero { get; } = new("Zero");
|
||||
|
||||
private ValueDataNode FlagThirtyOne { get; } = new("ThirtyOne");
|
||||
|
||||
[Benchmark]
|
||||
public string? ReadString()
|
||||
{
|
||||
return SerializationManager.ReadValue<string>(StringNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int? ReadInteger()
|
||||
{
|
||||
return SerializationManager.ReadValue<int>(IntNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataDefinitionWithString? ReadDataDefinitionWithString()
|
||||
{
|
||||
return SerializationManager.ReadValue<DataDefinitionWithString>(StringDataDefNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public SeedDataDefinition? ReadSeedDataDefinition()
|
||||
{
|
||||
return SerializationManager.ReadValue<SeedDataDefinition>(SeedNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DeserializationResult ReadFlagZero()
|
||||
{
|
||||
return SerializationManager.ReadWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
FlagZero);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DeserializationResult ReadThirtyOne()
|
||||
{
|
||||
return SerializationManager.ReadWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
FlagThirtyOne);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("customTypeSerializer")]
|
||||
public DeserializationResult ReadIntegerCustomSerializer()
|
||||
{
|
||||
return SerializationManager.ReadWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(BenchmarkIntSerializer),
|
||||
IntNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Robust.Benchmarks/Serialization/SerializationArrayBenchmark.cs
Normal file
127
Robust.Benchmarks/Serialization/SerializationArrayBenchmark.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class SerializationArrayBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationArrayBenchmark()
|
||||
{
|
||||
InitializeSerialization();
|
||||
|
||||
OneStringDefNode = new SequenceDataNode();
|
||||
OneStringDefNode.Add(new MappingDataNode
|
||||
{
|
||||
["string"] = new ValueDataNode("ABC")
|
||||
});
|
||||
|
||||
TenStringDefsNode = new SequenceDataNode();
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
TenStringDefsNode.Add(new MappingDataNode
|
||||
{
|
||||
["string"] = new ValueDataNode("ABC")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private SequenceDataNode EmptyNode { get; } = new();
|
||||
|
||||
private SequenceDataNode OneIntNode { get; } = new("1");
|
||||
|
||||
private SequenceDataNode TenIntsNode { get; } = new("1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
|
||||
|
||||
private SequenceDataNode OneStringDefNode { get; }
|
||||
|
||||
private SequenceDataNode TenStringDefsNode { get; }
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public string[]? ReadEmptyString()
|
||||
{
|
||||
return SerializationManager.ReadValue<string[]>(EmptyNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public string[]? ReadOneString()
|
||||
{
|
||||
return SerializationManager.ReadValue<string[]>(OneIntNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public string[]? ReadTenStrings()
|
||||
{
|
||||
return SerializationManager.ReadValue<string[]>(TenIntsNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public int[]? ReadEmptyInt()
|
||||
{
|
||||
return SerializationManager.ReadValue<int[]>(EmptyNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public int[]? ReadOneInt()
|
||||
{
|
||||
return SerializationManager.ReadValue<int[]>(OneIntNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public int[]? ReadTenInts()
|
||||
{
|
||||
return SerializationManager.ReadValue<int[]>(TenIntsNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public DataDefinitionWithString[]? ReadEmptyStringDataDef()
|
||||
{
|
||||
return SerializationManager.ReadValue<DataDefinitionWithString[]>(EmptyNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public DataDefinitionWithString[]? ReadOneStringDataDef()
|
||||
{
|
||||
return SerializationManager.ReadValue<DataDefinitionWithString[]>(OneStringDefNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public DataDefinitionWithString[]? ReadTenStringDataDefs()
|
||||
{
|
||||
return SerializationManager.ReadValue<DataDefinitionWithString[]>(TenStringDefsNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public SealedDataDefinitionWithString[]? ReadEmptySealedStringDataDef()
|
||||
{
|
||||
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(EmptyNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public SealedDataDefinitionWithString[]? ReadOneSealedStringDataDef()
|
||||
{
|
||||
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(OneStringDefNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public SealedDataDefinitionWithString[]? ReadTenSealedStringDataDefs()
|
||||
{
|
||||
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(TenStringDefsNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Robust.Benchmarks/Serialization/SerializationBenchmark.cs
Normal file
43
Robust.Benchmarks/Serialization/SerializationBenchmark.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Robust.Server;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization
|
||||
{
|
||||
public abstract class SerializationBenchmark
|
||||
{
|
||||
public SerializationBenchmark()
|
||||
{
|
||||
IoCManager.InitThread();
|
||||
ServerIoC.RegisterIoC();
|
||||
IoCManager.BuildGraph();
|
||||
|
||||
var assemblies = new[]
|
||||
{
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Shared"),
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Server"),
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Benchmarks")
|
||||
};
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
IoCManager.Resolve<IConfigurationManagerInternal>().LoadCVarsFromAssembly(assembly);
|
||||
}
|
||||
|
||||
IoCManager.Resolve<IReflectionManager>().LoadAssemblies(assemblies);
|
||||
|
||||
SerializationManager = IoCManager.Resolve<ISerializationManager>();
|
||||
}
|
||||
|
||||
protected ISerializationManager SerializationManager { get; }
|
||||
|
||||
public void InitializeSerialization()
|
||||
{
|
||||
SerializationManager.Initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Write
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class SerializationWriteBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationWriteBenchmark()
|
||||
{
|
||||
InitializeSerialization();
|
||||
|
||||
DataDefinitionWithString = new DataDefinitionWithString {StringField = "ABC"};
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));
|
||||
|
||||
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
|
||||
|
||||
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
|
||||
}
|
||||
|
||||
private const string String = "ABC";
|
||||
|
||||
private const int Integer = 1;
|
||||
|
||||
private DataDefinitionWithString DataDefinitionWithString { get; }
|
||||
|
||||
private SeedDataDefinition Seed { get; }
|
||||
|
||||
private BenchmarkFlagsEnum FlagZero = BenchmarkFlagsEnum.Zero;
|
||||
|
||||
private BenchmarkFlagsEnum FlagThirtyOne = BenchmarkFlagsEnum.ThirtyOne;
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteString()
|
||||
{
|
||||
return SerializationManager.WriteValue(String);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteInteger()
|
||||
{
|
||||
return SerializationManager.WriteValue(Integer);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteDataDefinitionWithString()
|
||||
{
|
||||
return SerializationManager.WriteValue(DataDefinitionWithString);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteSeedDataDefinition()
|
||||
{
|
||||
return SerializationManager.WriteValue(Seed);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataNode BaselineWriteSeedDataDefinition()
|
||||
{
|
||||
var mapping = new MappingDataNode();
|
||||
|
||||
mapping.Add("id", Seed.ID);
|
||||
mapping.Add("name", Seed.Name);
|
||||
mapping.Add("seedName", Seed.SeedName);
|
||||
mapping.Add("displayName", Seed.DisplayName);
|
||||
mapping.Add("productPrototypes", Seed.ProductPrototypes);
|
||||
mapping.Add("harvestRepeat", Seed.HarvestRepeat.ToString());
|
||||
mapping.Add("lifespan", Seed.Lifespan.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("maturation", Seed.Maturation.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("production", Seed.Production.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("yield", Seed.Yield.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("potency", Seed.Potency.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("growthStages", Seed.GrowthStages.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("idealLight", Seed.IdealLight.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("idealHeat", Seed.IdealHeat.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
var chemicals = new MappingDataNode();
|
||||
foreach (var (name, quantity) in Seed.Chemicals)
|
||||
{
|
||||
chemicals.Add(name, new MappingDataNode
|
||||
{
|
||||
["Min"] = new ValueDataNode(quantity.Min.ToString(CultureInfo.InvariantCulture)),
|
||||
["Max"] = new ValueDataNode(quantity.Max.ToString(CultureInfo.InvariantCulture)),
|
||||
["PotencyDivisor"] = new ValueDataNode(quantity.PotencyDivisor.ToString(CultureInfo.InvariantCulture))
|
||||
});
|
||||
}
|
||||
|
||||
mapping.Add("chemicals", chemicals);
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DataNode WriteFlagZero()
|
||||
{
|
||||
return SerializationManager.WriteWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
FlagZero);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DataNode WriteThirtyOne()
|
||||
{
|
||||
return SerializationManager.WriteWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
FlagThirtyOne);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("customTypeSerializer")]
|
||||
public DataNode WriteIntegerCustomSerializer()
|
||||
{
|
||||
return SerializationManager.WriteWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(BenchmarkIntSerializer),
|
||||
Integer);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Robust.Client.CEF/BeforeBrowseContext.cs
Normal file
32
Robust.Client.CEF/BeforeBrowseContext.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public sealed class BeforeBrowseContext
|
||||
{
|
||||
internal readonly CefRequest CefRequest;
|
||||
|
||||
public string Url => CefRequest.Url;
|
||||
public string Method => CefRequest.Method;
|
||||
|
||||
public bool IsRedirect { get; }
|
||||
public bool UserGesture { get; }
|
||||
|
||||
public bool IsCancelled { get; private set; }
|
||||
|
||||
internal BeforeBrowseContext(
|
||||
bool isRedirect,
|
||||
bool userGesture,
|
||||
CefRequest cefRequest)
|
||||
{
|
||||
CefRequest = cefRequest;
|
||||
IsRedirect = isRedirect;
|
||||
UserGesture = userGesture;
|
||||
}
|
||||
|
||||
public void DoCancel()
|
||||
{
|
||||
IsCancelled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
593
Robust.Client.CEF/BrowserControl.cs
Normal file
593
Robust.Client.CEF/BrowserControl.cs
Normal file
@@ -0,0 +1,593 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Xilium.CefGlue;
|
||||
using static Robust.Client.CEF.CefKeyCodes;
|
||||
using static Robust.Client.CEF.CefKeyCodes.ChromiumKeyboardCode;
|
||||
using static Robust.Client.Input.Keyboard;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
// Funny browser control to integrate in UI.
|
||||
public class BrowserControl : Control, IBrowserControl, IRawInputControl
|
||||
{
|
||||
private const int ScrollSpeed = 50;
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IInputManager _inputMgr = default!;
|
||||
[Dependency] private readonly CefManager _cef = default!;
|
||||
|
||||
private RobustRequestHandler _requestHandler = new RobustRequestHandler(Logger.GetSawmill("root"));
|
||||
private LiveData? _data;
|
||||
private string _startUrl = "about:blank";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Url
|
||||
{
|
||||
get => _data == null ? _startUrl : _data.Browser.GetMainFrame().Url;
|
||||
set
|
||||
{
|
||||
if (_data == null)
|
||||
_startUrl = value;
|
||||
else
|
||||
_data.Browser.GetMainFrame().LoadUrl(value);
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables] public bool IsLoading => _data?.Browser.IsLoading ?? false;
|
||||
|
||||
private readonly Dictionary<Key, ChromiumKeyboardCode> _keyMap = new()
|
||||
{
|
||||
[Key.A] = VKEY_A,
|
||||
[Key.B] = VKEY_B,
|
||||
[Key.C] = VKEY_C,
|
||||
[Key.D] = VKEY_D,
|
||||
[Key.E] = VKEY_E,
|
||||
[Key.F] = VKEY_F,
|
||||
[Key.G] = VKEY_G,
|
||||
[Key.H] = VKEY_H,
|
||||
[Key.I] = VKEY_I,
|
||||
[Key.J] = VKEY_J,
|
||||
[Key.K] = VKEY_K,
|
||||
[Key.L] = VKEY_L,
|
||||
[Key.M] = VKEY_M,
|
||||
[Key.N] = VKEY_N,
|
||||
[Key.O] = VKEY_O,
|
||||
[Key.P] = VKEY_P,
|
||||
[Key.Q] = VKEY_Q,
|
||||
[Key.R] = VKEY_R,
|
||||
[Key.S] = VKEY_S,
|
||||
[Key.T] = VKEY_T,
|
||||
[Key.U] = VKEY_U,
|
||||
[Key.V] = VKEY_V,
|
||||
[Key.W] = VKEY_W,
|
||||
[Key.X] = VKEY_X,
|
||||
[Key.Y] = VKEY_Y,
|
||||
[Key.Z] = VKEY_Z,
|
||||
[Key.Num0] = VKEY_0,
|
||||
[Key.Num1] = VKEY_1,
|
||||
[Key.Num2] = VKEY_2,
|
||||
[Key.Num3] = VKEY_3,
|
||||
[Key.Num4] = VKEY_4,
|
||||
[Key.Num5] = VKEY_5,
|
||||
[Key.Num6] = VKEY_6,
|
||||
[Key.Num7] = VKEY_7,
|
||||
[Key.Num8] = VKEY_8,
|
||||
[Key.Num9] = VKEY_9,
|
||||
[Key.NumpadNum0] = VKEY_NUMPAD0,
|
||||
[Key.NumpadNum1] = VKEY_NUMPAD1,
|
||||
[Key.NumpadNum2] = VKEY_NUMPAD2,
|
||||
[Key.NumpadNum3] = VKEY_NUMPAD3,
|
||||
[Key.NumpadNum4] = VKEY_NUMPAD4,
|
||||
[Key.NumpadNum5] = VKEY_NUMPAD5,
|
||||
[Key.NumpadNum6] = VKEY_NUMPAD6,
|
||||
[Key.NumpadNum7] = VKEY_NUMPAD7,
|
||||
[Key.NumpadNum8] = VKEY_NUMPAD8,
|
||||
[Key.NumpadNum9] = VKEY_NUMPAD9,
|
||||
[Key.Escape] = VKEY_ESCAPE,
|
||||
[Key.Control] = VKEY_CONTROL,
|
||||
[Key.Shift] = VKEY_SHIFT,
|
||||
[Key.Alt] = VKEY_MENU,
|
||||
[Key.LSystem] = VKEY_LWIN,
|
||||
[Key.RSystem] = VKEY_RWIN,
|
||||
[Key.LBracket] = VKEY_OEM_4,
|
||||
[Key.RBracket] = VKEY_OEM_6,
|
||||
[Key.SemiColon] = VKEY_OEM_1,
|
||||
[Key.Comma] = VKEY_OEM_COMMA,
|
||||
[Key.Period] = VKEY_OEM_PERIOD,
|
||||
[Key.Apostrophe] = VKEY_OEM_7,
|
||||
[Key.Slash] = VKEY_OEM_2,
|
||||
[Key.BackSlash] = VKEY_OEM_5,
|
||||
[Key.Tilde] = VKEY_OEM_3,
|
||||
[Key.Equal] = VKEY_OEM_PLUS,
|
||||
[Key.Space] = VKEY_SPACE,
|
||||
[Key.Return] = VKEY_RETURN,
|
||||
[Key.BackSpace] = VKEY_BACK,
|
||||
[Key.Tab] = VKEY_TAB,
|
||||
[Key.PageUp] = VKEY_PRIOR,
|
||||
[Key.PageDown] = VKEY_NEXT,
|
||||
[Key.End] = VKEY_END,
|
||||
[Key.Home] = VKEY_HOME,
|
||||
[Key.Insert] = VKEY_INSERT,
|
||||
[Key.Delete] = VKEY_DELETE,
|
||||
[Key.Minus] = VKEY_OEM_MINUS,
|
||||
[Key.NumpadAdd] = VKEY_ADD,
|
||||
[Key.NumpadSubtract] = VKEY_SUBTRACT,
|
||||
[Key.NumpadDivide] = VKEY_DIVIDE,
|
||||
[Key.NumpadMultiply] = VKEY_MULTIPLY,
|
||||
[Key.NumpadDecimal] = VKEY_DECIMAL,
|
||||
[Key.Left] = VKEY_LEFT,
|
||||
[Key.Right] = VKEY_RIGHT,
|
||||
[Key.Up] = VKEY_UP,
|
||||
[Key.Down] = VKEY_DOWN,
|
||||
[Key.F1] = VKEY_F1,
|
||||
[Key.F2] = VKEY_F2,
|
||||
[Key.F3] = VKEY_F3,
|
||||
[Key.F4] = VKEY_F4,
|
||||
[Key.F5] = VKEY_F5,
|
||||
[Key.F6] = VKEY_F6,
|
||||
[Key.F7] = VKEY_F7,
|
||||
[Key.F8] = VKEY_F8,
|
||||
[Key.F9] = VKEY_F9,
|
||||
[Key.F10] = VKEY_F10,
|
||||
[Key.F11] = VKEY_F11,
|
||||
[Key.F12] = VKEY_F12,
|
||||
[Key.F13] = VKEY_F13,
|
||||
[Key.F14] = VKEY_F14,
|
||||
[Key.F15] = VKEY_F15,
|
||||
[Key.Pause] = VKEY_PAUSE,
|
||||
};
|
||||
|
||||
public BrowserControl()
|
||||
{
|
||||
CanKeyboardFocus = true;
|
||||
KeyboardFocusOnClick = true;
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
base.EnteredTree();
|
||||
|
||||
_cef.CheckInitialized();
|
||||
|
||||
DebugTools.AssertNull(_data);
|
||||
|
||||
// A funny render handler that will allow us to render to the control.
|
||||
var renderer = new ControlRenderHandler(this);
|
||||
|
||||
// A funny web cef client. This can actually be shared by multiple browsers, but I'm not sure how the
|
||||
// rendering would work in that case? TODO CEF: Investigate a way to share the web client?
|
||||
var client = new RobustCefClient(renderer, _requestHandler, new RobustLoadHandler());
|
||||
|
||||
var info = CefWindowInfo.Create();
|
||||
|
||||
// FUNFACT: If you DO NOT set these below and set info.Width/info.Height instead, you get an external window
|
||||
// Good to know, huh? Setup is the same, except you can pass a dummy render handler to the CEF client.
|
||||
info.SetAsWindowless(IntPtr.Zero, false); // TODO CEF: Pass parent handle?
|
||||
info.WindowlessRenderingEnabled = true;
|
||||
|
||||
var settings = new CefBrowserSettings()
|
||||
{
|
||||
WindowlessFrameRate = 60
|
||||
};
|
||||
|
||||
// Create the web browser! And by default, we go to about:blank.
|
||||
var browser = CefBrowserHost.CreateBrowserSync(info, client, settings, _startUrl);
|
||||
|
||||
var texture = _clyde.CreateBlankTexture<Bgra32>(Vector2i.One);
|
||||
|
||||
_data = new LiveData(texture, client, browser, renderer);
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
DebugTools.AssertNotNull(_data);
|
||||
|
||||
_data!.Texture.Dispose();
|
||||
_data.Browser.GetHost().CloseBrowser(true);
|
||||
_data = null;
|
||||
}
|
||||
|
||||
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
|
||||
{
|
||||
base.MouseMove(args);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
// Logger.Debug();
|
||||
var modifiers = CalcMouseModifiers();
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
(int) args.RelativePosition.X, (int) args.RelativePosition.Y,
|
||||
modifiers);
|
||||
|
||||
_data.Browser.GetHost().SendMouseMoveEvent(mouseEvent, false);
|
||||
}
|
||||
|
||||
protected internal override void MouseExited()
|
||||
{
|
||||
base.MouseExited();
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var modifiers = CalcMouseModifiers();
|
||||
|
||||
_data.Browser.GetHost().SendMouseMoveEvent(new CefMouseEvent(0, 0, modifiers), true);
|
||||
}
|
||||
|
||||
protected internal override void MouseWheel(GUIMouseWheelEventArgs args)
|
||||
{
|
||||
base.MouseWheel(args);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var modifiers = CalcMouseModifiers();
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
(int) args.RelativePosition.X, (int) args.RelativePosition.Y,
|
||||
modifiers);
|
||||
|
||||
_data.Browser.GetHost().SendMouseWheelEvent(
|
||||
mouseEvent,
|
||||
(int) args.Delta.X * ScrollSpeed,
|
||||
(int) args.Delta.Y * ScrollSpeed);
|
||||
}
|
||||
|
||||
bool IRawInputControl.RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
|
||||
{
|
||||
if (_data == null)
|
||||
return false;
|
||||
|
||||
var host = _data.Browser.GetHost();
|
||||
|
||||
if (guiRawEvent.Key is Key.MouseLeft or Key.MouseMiddle or Key.MouseRight)
|
||||
{
|
||||
var key = guiRawEvent.Key switch
|
||||
{
|
||||
Key.MouseLeft => CefMouseButtonType.Left,
|
||||
Key.MouseMiddle => CefMouseButtonType.Middle,
|
||||
Key.MouseRight => CefMouseButtonType.Right,
|
||||
_ => default // not possible
|
||||
};
|
||||
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
guiRawEvent.MouseRelative.X, guiRawEvent.MouseRelative.Y,
|
||||
CefEventFlags.None);
|
||||
|
||||
// Logger.Debug($"MOUSE: {guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {key}");
|
||||
|
||||
// TODO: double click support?
|
||||
host.SendMouseClickEvent(mouseEvent, key, guiRawEvent.Action == RawKeyAction.Up, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Handle left/right modifier keys??
|
||||
if (!_keyMap.TryGetValue(guiRawEvent.Key, out var vkKey))
|
||||
vkKey = default;
|
||||
|
||||
// Logger.Debug($"{guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {vkKey}");
|
||||
|
||||
var lParam = 0;
|
||||
lParam |= (guiRawEvent.ScanCode & 0xFF) << 16;
|
||||
if (guiRawEvent.Action != RawKeyAction.Down)
|
||||
lParam |= 1 << 30;
|
||||
|
||||
if (guiRawEvent.Action == RawKeyAction.Up)
|
||||
lParam |= 1 << 31;
|
||||
|
||||
var modifiers = CalcModifiers(guiRawEvent.Key);
|
||||
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
// Repeats are sent as key downs, I guess?
|
||||
EventType = guiRawEvent.Action == RawKeyAction.Up
|
||||
? CefKeyEventType.KeyUp
|
||||
: CefKeyEventType.RawKeyDown,
|
||||
NativeKeyCode = lParam,
|
||||
// NativeKeyCode = guiRawEvent.ScanCode,
|
||||
WindowsKeyCode = (int) vkKey,
|
||||
IsSystemKey = false, // TODO
|
||||
Modifiers = modifiers
|
||||
});
|
||||
|
||||
if (guiRawEvent.Action != RawKeyAction.Up && guiRawEvent.Key == Key.Return)
|
||||
{
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
EventType = CefKeyEventType.Char,
|
||||
WindowsKeyCode = '\r',
|
||||
NativeKeyCode = lParam,
|
||||
Modifiers = modifiers
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private CefEventFlags CalcModifiers(Key key)
|
||||
{
|
||||
CefEventFlags modifiers = default;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Control))
|
||||
modifiers |= CefEventFlags.ControlDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Alt))
|
||||
modifiers |= CefEventFlags.AltDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
private CefEventFlags CalcMouseModifiers()
|
||||
{
|
||||
CefEventFlags modifiers = default;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Control))
|
||||
modifiers |= CefEventFlags.ControlDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Alt))
|
||||
modifiers |= CefEventFlags.AltDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseLeft))
|
||||
modifiers |= CefEventFlags.LeftMouseButton;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseMiddle))
|
||||
modifiers |= CefEventFlags.MiddleMouseButton;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseRight))
|
||||
modifiers |= CefEventFlags.RightMouseButton;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
protected internal override void TextEntered(GUITextEventArgs args)
|
||||
{
|
||||
base.TextEntered(args);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var host = _data.Browser.GetHost();
|
||||
|
||||
Span<char> buf = stackalloc char[2];
|
||||
var written = args.AsRune.EncodeToUtf16(buf);
|
||||
|
||||
for (var i = 0; i < written; i++)
|
||||
{
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
EventType = CefKeyEventType.Char,
|
||||
WindowsKeyCode = buf[i],
|
||||
Character = buf[i],
|
||||
UnmodifiedCharacter = buf[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Resized()
|
||||
{
|
||||
base.Resized();
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
_data.Browser.GetHost().NotifyMoveOrResizeStarted();
|
||||
_data.Browser.GetHost().WasResized();
|
||||
_data.Texture.Dispose();
|
||||
_data.Texture = _clyde.CreateBlankTexture<Bgra32>((PixelWidth, PixelHeight));
|
||||
}
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var bufImg = _data.Renderer.Buffer.Buffer;
|
||||
|
||||
_data.Texture.SetSubImage(
|
||||
Vector2i.Zero,
|
||||
bufImg,
|
||||
new UIBox2i(
|
||||
0, 0,
|
||||
Math.Min(PixelWidth, bufImg.Width),
|
||||
Math.Min(PixelHeight, bufImg.Height)));
|
||||
|
||||
handle.DrawTexture(_data.Texture, Vector2.Zero);
|
||||
}
|
||||
|
||||
public void StopLoad()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
_data.Browser.StopLoad();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
_data.Browser.Reload();
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (!_data.Browser.CanGoBack)
|
||||
return false;
|
||||
|
||||
_data.Browser.GoBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (!_data.Browser.CanGoForward)
|
||||
return false;
|
||||
|
||||
_data.Browser.GoForward();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ExecuteJavaScript(string code)
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
// TODO: this should not run until the browser is done loading seriously does this even work?
|
||||
_data.Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
_requestHandler.AddResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
_requestHandler.RemoveResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void AddBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
_requestHandler.AddBeforeBrowseHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
_requestHandler.RemoveBeforeBrowseHandler(handler);
|
||||
}
|
||||
|
||||
private sealed class LiveData
|
||||
{
|
||||
public OwnedTexture Texture;
|
||||
public readonly RobustCefClient Client;
|
||||
public readonly CefBrowser Browser;
|
||||
public readonly ControlRenderHandler Renderer;
|
||||
|
||||
public LiveData(
|
||||
OwnedTexture texture,
|
||||
RobustCefClient client,
|
||||
CefBrowser browser,
|
||||
ControlRenderHandler renderer)
|
||||
{
|
||||
Texture = texture;
|
||||
Client = client;
|
||||
Browser = browser;
|
||||
Renderer = renderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ControlRenderHandler : CefRenderHandler
|
||||
{
|
||||
public ImageBuffer Buffer { get; }
|
||||
private Control _control;
|
||||
|
||||
internal ControlRenderHandler(Control control)
|
||||
{
|
||||
Buffer = new ImageBuffer(control);
|
||||
_control = control;
|
||||
}
|
||||
|
||||
protected override CefAccessibilityHandler? GetAccessibilityHandler() => null;
|
||||
|
||||
protected override void GetViewRect(CefBrowser browser, out CefRectangle rect)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
{
|
||||
rect = new CefRectangle();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO CEF: Do we need to pass real screen coords? Cause what we do already works...
|
||||
//var screenCoords = _control.ScreenCoordinates;
|
||||
//rect = new CefRectangle((int) screenCoords.X, (int) screenCoords.Y, (int)Math.Max(_control.Size.X, 1), (int)Math.Max(_control.Size.Y, 1));
|
||||
|
||||
// We do the max between size and 1 because it will LITERALLY CRASH WITHOUT AN ERROR otherwise.
|
||||
rect = new CefRectangle(0, 0, (int) Math.Max(_control.Size.X, 1), (int) Math.Max(_control.Size.Y, 1));
|
||||
}
|
||||
|
||||
protected override bool GetScreenInfo(CefBrowser browser, CefScreenInfo screenInfo)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return false;
|
||||
|
||||
// TODO CEF: Get actual scale factor?
|
||||
screenInfo.DeviceScaleFactor = 1.0f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnPopupSize(CefBrowser browser, CefRectangle rect)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void OnPaint(CefBrowser browser, CefPaintElementType type, CefRectangle[] dirtyRects,
|
||||
IntPtr buffer, int width, int height)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
|
||||
foreach (var dirtyRect in dirtyRects)
|
||||
{
|
||||
Buffer.UpdateBuffer(width, height, buffer, dirtyRect);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAcceleratedPaint(CefBrowser browser, CefPaintElementType type,
|
||||
CefRectangle[] dirtyRects, IntPtr sharedHandle)
|
||||
{
|
||||
// Unused, but we're forced to implement it so.. NOOP.
|
||||
}
|
||||
|
||||
protected override void OnScrollOffsetChanged(CefBrowser browser, double x, double y)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void OnImeCompositionRangeChanged(CefBrowser browser, CefRange selectedRange,
|
||||
CefRectangle[] characterBounds)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Robust.Client.CEF/BrowserWindowCreateParameters.cs
Normal file
16
Robust.Client.CEF/BrowserWindowCreateParameters.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public sealed class BrowserWindowCreateParameters
|
||||
{
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
|
||||
public string Url { get; set; } = "about:blank";
|
||||
|
||||
public BrowserWindowCreateParameters(int width, int height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
225
Robust.Client.CEF/CefKeyCodes.cs
Normal file
225
Robust.Client.CEF/CefKeyCodes.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo")]
|
||||
[SuppressMessage("ReSharper", "CA1069")]
|
||||
[SuppressMessage("ReSharper", "CommentTypo")]
|
||||
internal static class CefKeyCodes
|
||||
{
|
||||
// Taken from https://chromium.googlesource.com/chromium/src/+/refs/heads/main/ui/events/keycodes/keyboard_codes_posix.h
|
||||
// See also https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||
public enum ChromiumKeyboardCode
|
||||
{
|
||||
VKEY_CANCEL = 0x03,
|
||||
VKEY_BACK = 0x08,
|
||||
VKEY_TAB = 0x09,
|
||||
VKEY_BACKTAB = 0x0A,
|
||||
VKEY_CLEAR = 0x0C,
|
||||
VKEY_RETURN = 0x0D,
|
||||
VKEY_SHIFT = 0x10,
|
||||
VKEY_CONTROL = 0x11,
|
||||
VKEY_MENU = 0x12,
|
||||
VKEY_PAUSE = 0x13,
|
||||
VKEY_CAPITAL = 0x14,
|
||||
VKEY_KANA = 0x15,
|
||||
VKEY_HANGUL = 0x15,
|
||||
VKEY_PASTE = 0x16,
|
||||
VKEY_JUNJA = 0x17,
|
||||
VKEY_FINAL = 0x18,
|
||||
VKEY_HANJA = 0x19,
|
||||
VKEY_KANJI = 0x19,
|
||||
VKEY_ESCAPE = 0x1B,
|
||||
VKEY_CONVERT = 0x1C,
|
||||
VKEY_NONCONVERT = 0x1D,
|
||||
VKEY_ACCEPT = 0x1E,
|
||||
VKEY_MODECHANGE = 0x1F,
|
||||
VKEY_SPACE = 0x20,
|
||||
VKEY_PRIOR = 0x21,
|
||||
VKEY_NEXT = 0x22,
|
||||
VKEY_END = 0x23,
|
||||
VKEY_HOME = 0x24,
|
||||
VKEY_LEFT = 0x25,
|
||||
VKEY_UP = 0x26,
|
||||
VKEY_RIGHT = 0x27,
|
||||
VKEY_DOWN = 0x28,
|
||||
VKEY_SELECT = 0x29,
|
||||
VKEY_PRINT = 0x2A,
|
||||
VKEY_EXECUTE = 0x2B,
|
||||
VKEY_SNAPSHOT = 0x2C, // Print Screen / SysRq
|
||||
VKEY_INSERT = 0x2D,
|
||||
VKEY_DELETE = 0x2E,
|
||||
VKEY_HELP = 0x2F,
|
||||
VKEY_0 = 0x30,
|
||||
VKEY_1 = 0x31,
|
||||
VKEY_2 = 0x32,
|
||||
VKEY_3 = 0x33,
|
||||
VKEY_4 = 0x34,
|
||||
VKEY_5 = 0x35,
|
||||
VKEY_6 = 0x36,
|
||||
VKEY_7 = 0x37,
|
||||
VKEY_8 = 0x38,
|
||||
VKEY_9 = 0x39,
|
||||
VKEY_A = 0x41,
|
||||
VKEY_B = 0x42,
|
||||
VKEY_C = 0x43,
|
||||
VKEY_D = 0x44,
|
||||
VKEY_E = 0x45,
|
||||
VKEY_F = 0x46,
|
||||
VKEY_G = 0x47,
|
||||
VKEY_H = 0x48,
|
||||
VKEY_I = 0x49,
|
||||
VKEY_J = 0x4A,
|
||||
VKEY_K = 0x4B,
|
||||
VKEY_L = 0x4C,
|
||||
VKEY_M = 0x4D,
|
||||
VKEY_N = 0x4E,
|
||||
VKEY_O = 0x4F,
|
||||
VKEY_P = 0x50,
|
||||
VKEY_Q = 0x51,
|
||||
VKEY_R = 0x52,
|
||||
VKEY_S = 0x53,
|
||||
VKEY_T = 0x54,
|
||||
VKEY_U = 0x55,
|
||||
VKEY_V = 0x56,
|
||||
VKEY_W = 0x57,
|
||||
VKEY_X = 0x58,
|
||||
VKEY_Y = 0x59,
|
||||
VKEY_Z = 0x5A,
|
||||
VKEY_LWIN = 0x5B,
|
||||
VKEY_COMMAND = VKEY_LWIN, // Provide the Mac name for convenience.
|
||||
VKEY_RWIN = 0x5C,
|
||||
VKEY_APPS = 0x5D,
|
||||
VKEY_SLEEP = 0x5F,
|
||||
VKEY_NUMPAD0 = 0x60,
|
||||
VKEY_NUMPAD1 = 0x61,
|
||||
VKEY_NUMPAD2 = 0x62,
|
||||
VKEY_NUMPAD3 = 0x63,
|
||||
VKEY_NUMPAD4 = 0x64,
|
||||
VKEY_NUMPAD5 = 0x65,
|
||||
VKEY_NUMPAD6 = 0x66,
|
||||
VKEY_NUMPAD7 = 0x67,
|
||||
VKEY_NUMPAD8 = 0x68,
|
||||
VKEY_NUMPAD9 = 0x69,
|
||||
VKEY_MULTIPLY = 0x6A,
|
||||
VKEY_ADD = 0x6B,
|
||||
VKEY_SEPARATOR = 0x6C,
|
||||
VKEY_SUBTRACT = 0x6D,
|
||||
VKEY_DECIMAL = 0x6E,
|
||||
VKEY_DIVIDE = 0x6F,
|
||||
VKEY_F1 = 0x70,
|
||||
VKEY_F2 = 0x71,
|
||||
VKEY_F3 = 0x72,
|
||||
VKEY_F4 = 0x73,
|
||||
VKEY_F5 = 0x74,
|
||||
VKEY_F6 = 0x75,
|
||||
VKEY_F7 = 0x76,
|
||||
VKEY_F8 = 0x77,
|
||||
VKEY_F9 = 0x78,
|
||||
VKEY_F10 = 0x79,
|
||||
VKEY_F11 = 0x7A,
|
||||
VKEY_F12 = 0x7B,
|
||||
VKEY_F13 = 0x7C,
|
||||
VKEY_F14 = 0x7D,
|
||||
VKEY_F15 = 0x7E,
|
||||
VKEY_F16 = 0x7F,
|
||||
VKEY_F17 = 0x80,
|
||||
VKEY_F18 = 0x81,
|
||||
VKEY_F19 = 0x82,
|
||||
VKEY_F20 = 0x83,
|
||||
VKEY_F21 = 0x84,
|
||||
VKEY_F22 = 0x85,
|
||||
VKEY_F23 = 0x86,
|
||||
VKEY_F24 = 0x87,
|
||||
VKEY_NUMLOCK = 0x90,
|
||||
VKEY_SCROLL = 0x91,
|
||||
VKEY_LSHIFT = 0xA0,
|
||||
VKEY_RSHIFT = 0xA1,
|
||||
VKEY_LCONTROL = 0xA2,
|
||||
VKEY_RCONTROL = 0xA3,
|
||||
VKEY_LMENU = 0xA4,
|
||||
VKEY_RMENU = 0xA5,
|
||||
VKEY_BROWSER_BACK = 0xA6,
|
||||
VKEY_BROWSER_FORWARD = 0xA7,
|
||||
VKEY_BROWSER_REFRESH = 0xA8,
|
||||
VKEY_BROWSER_STOP = 0xA9,
|
||||
VKEY_BROWSER_SEARCH = 0xAA,
|
||||
VKEY_BROWSER_FAVORITES = 0xAB,
|
||||
VKEY_BROWSER_HOME = 0xAC,
|
||||
VKEY_VOLUME_MUTE = 0xAD,
|
||||
VKEY_VOLUME_DOWN = 0xAE,
|
||||
VKEY_VOLUME_UP = 0xAF,
|
||||
VKEY_MEDIA_NEXT_TRACK = 0xB0,
|
||||
VKEY_MEDIA_PREV_TRACK = 0xB1,
|
||||
VKEY_MEDIA_STOP = 0xB2,
|
||||
VKEY_MEDIA_PLAY_PAUSE = 0xB3,
|
||||
VKEY_MEDIA_LAUNCH_MAIL = 0xB4,
|
||||
VKEY_MEDIA_LAUNCH_MEDIA_SELECT = 0xB5,
|
||||
VKEY_MEDIA_LAUNCH_APP1 = 0xB6,
|
||||
VKEY_MEDIA_LAUNCH_APP2 = 0xB7,
|
||||
VKEY_OEM_1 = 0xBA,
|
||||
VKEY_OEM_PLUS = 0xBB,
|
||||
VKEY_OEM_COMMA = 0xBC,
|
||||
VKEY_OEM_MINUS = 0xBD,
|
||||
VKEY_OEM_PERIOD = 0xBE,
|
||||
VKEY_OEM_2 = 0xBF,
|
||||
VKEY_OEM_3 = 0xC0,
|
||||
VKEY_OEM_4 = 0xDB,
|
||||
VKEY_OEM_5 = 0xDC,
|
||||
VKEY_OEM_6 = 0xDD,
|
||||
VKEY_OEM_7 = 0xDE,
|
||||
VKEY_OEM_8 = 0xDF,
|
||||
VKEY_OEM_102 = 0xE2,
|
||||
VKEY_OEM_103 = 0xE3, // GTV KEYCODE_MEDIA_REWIND
|
||||
VKEY_OEM_104 = 0xE4, // GTV KEYCODE_MEDIA_FAST_FORWARD
|
||||
VKEY_PROCESSKEY = 0xE5,
|
||||
VKEY_PACKET = 0xE7,
|
||||
VKEY_OEM_ATTN = 0xF0, // JIS DomKey::ALPHANUMERIC
|
||||
VKEY_OEM_FINISH = 0xF1, // JIS DomKey::KATAKANA
|
||||
VKEY_OEM_COPY = 0xF2, // JIS DomKey::HIRAGANA
|
||||
VKEY_DBE_SBCSCHAR = 0xF3, // JIS DomKey::HANKAKU
|
||||
VKEY_DBE_DBCSCHAR = 0xF4, // JIS DomKey::ZENKAKU
|
||||
VKEY_OEM_BACKTAB = 0xF5, // JIS DomKey::ROMAJI
|
||||
VKEY_ATTN = 0xF6, // DomKey::ATTN or JIS DomKey::KANA_MODE
|
||||
VKEY_CRSEL = 0xF7,
|
||||
VKEY_EXSEL = 0xF8,
|
||||
VKEY_EREOF = 0xF9,
|
||||
VKEY_PLAY = 0xFA,
|
||||
VKEY_ZOOM = 0xFB,
|
||||
VKEY_NONAME = 0xFC,
|
||||
VKEY_PA1 = 0xFD,
|
||||
VKEY_OEM_CLEAR = 0xFE,
|
||||
VKEY_UNKNOWN = 0,
|
||||
|
||||
// POSIX specific VKEYs. Note that as of Windows SDK 7.1, 0x97-9F, 0xD8-DA,
|
||||
// and 0xE8 are unassigned.
|
||||
VKEY_WLAN = 0x97,
|
||||
VKEY_POWER = 0x98,
|
||||
VKEY_ASSISTANT = 0x99,
|
||||
VKEY_SETTINGS = 0x9A,
|
||||
VKEY_PRIVACY_SCREEN_TOGGLE = 0x9B,
|
||||
VKEY_BRIGHTNESS_DOWN = 0xD8,
|
||||
VKEY_BRIGHTNESS_UP = 0xD9,
|
||||
VKEY_KBD_BRIGHTNESS_DOWN = 0xDA,
|
||||
VKEY_KBD_BRIGHTNESS_UP = 0xE8,
|
||||
|
||||
// Windows does not have a specific key code for AltGr. We use the unused 0xE1
|
||||
// (VK_OEM_AX) code to represent AltGr, matching the behaviour of Firefox on
|
||||
// Linux.
|
||||
VKEY_ALTGR = 0xE1,
|
||||
|
||||
// Windows does not have a specific key code for Compose. We use the unused
|
||||
// 0xE6 (VK_ICO_CLEAR) code to represent Compose.
|
||||
VKEY_COMPOSE = 0xE6,
|
||||
|
||||
// Windows does not have specific key codes for Media Play and Media Pause. We
|
||||
// use the unused 0xE9 (VK_OEM_RESET) and 0xEA (VK_OEM_JUMP) codes to
|
||||
// represent them.
|
||||
VKEY_MEDIA_PLAY = 0xE9,
|
||||
VKEY_MEDIA_PAUSE = 0xEA,
|
||||
};
|
||||
}
|
||||
}
|
||||
186
Robust.Client.CEF/CefManager.BrowserWindow.cs
Normal file
186
Robust.Client.CEF/CefManager.BrowserWindow.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public partial class CefManager
|
||||
{
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
|
||||
private readonly List<BrowserWindowImpl> _browserWindows = new();
|
||||
|
||||
public IEnumerable<IBrowserWindow> AllBrowserWindows => _browserWindows;
|
||||
|
||||
public IBrowserWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
|
||||
{
|
||||
var mainHWnd = (_clyde.MainWindow as IClydeWindowInternal)?.WindowsHWnd ?? 0;
|
||||
|
||||
var info = CefWindowInfo.Create();
|
||||
info.Width = createParams.Width;
|
||||
info.Height = createParams.Height;
|
||||
info.SetAsPopup(mainHWnd, "ss14cef");
|
||||
|
||||
var impl = new BrowserWindowImpl(this);
|
||||
|
||||
var lifeSpanHandler = new WindowLifeSpanHandler(impl);
|
||||
var reqHandler = new RobustRequestHandler(Logger.GetSawmill("root"));
|
||||
var client = new WindowCefClient(lifeSpanHandler, reqHandler);
|
||||
var settings = new CefBrowserSettings();
|
||||
|
||||
impl.Browser = CefBrowserHost.CreateBrowserSync(info, client, settings, createParams.Url);
|
||||
impl.RequestHandler = reqHandler;
|
||||
|
||||
_browserWindows.Add(impl);
|
||||
|
||||
return impl;
|
||||
}
|
||||
|
||||
private sealed class BrowserWindowImpl : IBrowserWindow
|
||||
{
|
||||
private readonly CefManager _manager;
|
||||
internal CefBrowser Browser = default!;
|
||||
internal RobustRequestHandler RequestHandler = default!;
|
||||
|
||||
public Action<RequestHandlerContext>? OnResourceRequest { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Url
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckClosed();
|
||||
return Browser.GetMainFrame().Url;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckClosed();
|
||||
Browser.GetMainFrame().LoadUrl(value);
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsLoading
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckClosed();
|
||||
return Browser.IsLoading;
|
||||
}
|
||||
}
|
||||
|
||||
public BrowserWindowImpl(CefManager manager)
|
||||
{
|
||||
_manager = manager;
|
||||
}
|
||||
|
||||
public void StopLoad()
|
||||
{
|
||||
CheckClosed();
|
||||
Browser.StopLoad();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
CheckClosed();
|
||||
Browser.Reload();
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
CheckClosed();
|
||||
if (!Browser.CanGoBack)
|
||||
return false;
|
||||
|
||||
Browser.GoBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
CheckClosed();
|
||||
if (!Browser.CanGoForward)
|
||||
return false;
|
||||
|
||||
Browser.GoForward();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ExecuteJavaScript(string code)
|
||||
{
|
||||
CheckClosed();
|
||||
Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
RequestHandler.AddResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
RequestHandler.RemoveResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Closed)
|
||||
return;
|
||||
|
||||
Browser.GetHost().CloseBrowser(true);
|
||||
Closed = true;
|
||||
}
|
||||
|
||||
public bool Closed { get; private set; }
|
||||
|
||||
public void OnClose()
|
||||
{
|
||||
Closed = true;
|
||||
_manager._browserWindows.Remove(this);
|
||||
Logger.Debug("Removing window");
|
||||
}
|
||||
|
||||
private void CheckClosed()
|
||||
{
|
||||
if (Closed)
|
||||
throw new ObjectDisposedException("BrowserWindow");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WindowCefClient : CefClient
|
||||
{
|
||||
private readonly CefLifeSpanHandler _lifeSpanHandler;
|
||||
private readonly CefRequestHandler _requestHandler;
|
||||
|
||||
public WindowCefClient(CefLifeSpanHandler lifeSpanHandler, CefRequestHandler requestHandler)
|
||||
{
|
||||
_lifeSpanHandler = lifeSpanHandler;
|
||||
_requestHandler = requestHandler;
|
||||
}
|
||||
|
||||
protected override CefLifeSpanHandler GetLifeSpanHandler() => _lifeSpanHandler;
|
||||
protected override CefRequestHandler GetRequestHandler() => _requestHandler;
|
||||
}
|
||||
|
||||
private sealed class WindowLifeSpanHandler : CefLifeSpanHandler
|
||||
{
|
||||
private readonly BrowserWindowImpl _windowImpl;
|
||||
|
||||
public WindowLifeSpanHandler(BrowserWindowImpl windowImpl)
|
||||
{
|
||||
_windowImpl = windowImpl;
|
||||
}
|
||||
|
||||
protected override void OnBeforeClose(CefBrowser browser)
|
||||
{
|
||||
base.OnBeforeClose(browser);
|
||||
|
||||
_windowImpl.OnClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
104
Robust.Client.CEF/CefManager.cs
Normal file
104
Robust.Client.CEF/CefManager.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
// The library we're using right now. TODO CEF: Do we want to use something else? We will need to ship it ourselves if so.
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
// Register this with IoC.
|
||||
// TODO CEF: think if making this inherit CefApp is a good idea...
|
||||
// TODO CEF: A way to handle external window browsers...
|
||||
[UsedImplicitly]
|
||||
public partial class CefManager
|
||||
{
|
||||
private CefApp _app = default!;
|
||||
private bool _initialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Call this to initialize CEF.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
DebugTools.Assert(!_initialized);
|
||||
|
||||
string subProcessName;
|
||||
if (OperatingSystem.IsWindows())
|
||||
subProcessName = "Robust.Client.CEF.exe";
|
||||
else if (OperatingSystem.IsLinux())
|
||||
subProcessName = "Robust.Client.CEF";
|
||||
else
|
||||
throw new NotSupportedException("Unsupported platform for CEF!");
|
||||
|
||||
var subProcessPath = PathHelpers.ExecutableRelativeFile(subProcessName);
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
WindowlessRenderingEnabled = true, // So we can render to our UI controls.
|
||||
ExternalMessagePump = false, // Unsure, honestly. TODO CEF: Research this?
|
||||
NoSandbox = true, // Not disabling the sandbox crashes CEF.
|
||||
BrowserSubprocessPath = subProcessPath,
|
||||
LocalesDirPath = Path.Combine(PathHelpers.GetExecutableDirectory(), "locales"),
|
||||
ResourcesDirPath = PathHelpers.GetExecutableDirectory(),
|
||||
RemoteDebuggingPort = 9222
|
||||
};
|
||||
|
||||
Logger.Info($"CEF Version: {CefRuntime.ChromeVersion}");
|
||||
|
||||
// --------------------------- README --------------------------------------------------
|
||||
// By the way! You're gonna need the CEF binaries in your client's bin folder.
|
||||
// More specifically, version cef_binary_91.1.21+g9dd45fe+chromium-91.0.4472.114
|
||||
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_windows64_minimal.tar.bz2
|
||||
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_linux64_minimal.tar.bz2
|
||||
// Here's how to get it to work:
|
||||
// 1. Copy all the contents of "Release" to the bin folder.
|
||||
// 2. Copy all the contents of "Resources" to the bin folder.
|
||||
// Supposedly, you should just need libcef.so in Release and icudtl.dat in Resources...
|
||||
// The rest might be optional.
|
||||
// Maybe. Good luck! If you get odd crashes with no info and a weird exit code, use GDB!
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
_app = new RobustCefApp();
|
||||
|
||||
// We pass no main arguments...
|
||||
CefRuntime.Initialize(new CefMainArgs(null), settings, _app, IntPtr.Zero);
|
||||
|
||||
// TODO CEF: After this point, debugging breaks. No, literally. My client crashes but ONLY with the debugger.
|
||||
// I have tried using the DEBUG and RELEASE versions of libcef.so, stripped or non-stripped...
|
||||
// And nothing seemed to work. Odd.
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public void CheckInitialized()
|
||||
{
|
||||
if (!_initialized)
|
||||
throw new InvalidOperationException("CefManager has not been initialized!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Needs to be called regularly for CEF to keep working.
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
DebugTools.Assert(_initialized);
|
||||
|
||||
// Calling this makes CEF do its work, without using its own update loop.
|
||||
CefRuntime.DoMessageLoopWork();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call before program shutdown.
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
{
|
||||
DebugTools.Assert(_initialized);
|
||||
|
||||
CefRuntime.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Robust.Client.CEF/IBrowserControl.cs
Normal file
48
Robust.Client.CEF/IBrowserControl.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public interface IBrowserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Current URL of the browser. Set to load a new page.
|
||||
/// </summary>
|
||||
string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the browser is currently loading a page.
|
||||
/// </summary>
|
||||
bool IsLoading { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Stops loading the current page.
|
||||
/// </summary>
|
||||
void StopLoad();
|
||||
|
||||
/// <summary>
|
||||
/// Reload the current page.
|
||||
/// </summary>
|
||||
void Reload();
|
||||
|
||||
/// <summary>
|
||||
/// Navigate back.
|
||||
/// </summary>
|
||||
/// <returns>Whether the browser could navigate back.</returns>
|
||||
bool GoBack();
|
||||
|
||||
/// <summary>
|
||||
/// Navigate forward.
|
||||
/// </summary>
|
||||
/// <returns>Whether the browser could navigate forward.</returns>
|
||||
bool GoForward();
|
||||
|
||||
/// <summary>
|
||||
/// Execute arbitrary JavaScript on the current page.
|
||||
/// </summary>
|
||||
/// <param name="code">JavaScript code.</param>
|
||||
void ExecuteJavaScript(string code);
|
||||
|
||||
void AddResourceRequestHandler(Action<RequestHandlerContext> handler);
|
||||
void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler);
|
||||
}
|
||||
}
|
||||
9
Robust.Client.CEF/IBrowserWindow.cs
Normal file
9
Robust.Client.CEF/IBrowserWindow.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public interface IBrowserWindow : IBrowserControl, IDisposable
|
||||
{
|
||||
bool Closed { get; }
|
||||
}
|
||||
}
|
||||
42
Robust.Client.CEF/ImageBuffer.cs
Normal file
42
Robust.Client.CEF/ImageBuffer.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
internal sealed class ImageBuffer
|
||||
{
|
||||
private readonly Control _control;
|
||||
|
||||
public ImageBuffer(Control control)
|
||||
{
|
||||
_control = control;
|
||||
}
|
||||
|
||||
public Image<Bgra32> Buffer { get; private set; } = new(1, 1);
|
||||
|
||||
public unsafe void UpdateBuffer(int width, int height, IntPtr buffer, CefRectangle dirtyRect)
|
||||
{
|
||||
if (width != Buffer.Width || height != Buffer.Height)
|
||||
UpdateSize(width, height);
|
||||
|
||||
var span = new ReadOnlySpan<Bgra32>((void*) buffer, width * height);
|
||||
|
||||
ImageSharpExt.Blit(
|
||||
span,
|
||||
width,
|
||||
UIBox2i.FromDimensions(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height),
|
||||
Buffer,
|
||||
(dirtyRect.X, dirtyRect.Y));
|
||||
}
|
||||
|
||||
private void UpdateSize(int width, int height)
|
||||
{
|
||||
Buffer = new Image<Bgra32>(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Robust.Client.CEF/Program.cs
Normal file
33
Robust.Client.CEF/Program.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
// This is a workaround for this to work on UNIX.
|
||||
var argv = args;
|
||||
if (CefRuntime.Platform != CefRuntimePlatform.Windows)
|
||||
{
|
||||
argv = new string[args.Length + 1];
|
||||
Array.Copy(args, 0, argv, 1, args.Length);
|
||||
argv[0] = "-";
|
||||
}
|
||||
|
||||
var mainArgs = new CefMainArgs(argv);
|
||||
|
||||
// This will block executing until the subprocess is shut down.
|
||||
var code = CefRuntime.ExecuteProcess(mainArgs, null, IntPtr.Zero);
|
||||
|
||||
if (code != 0)
|
||||
{
|
||||
System.Console.WriteLine($"CEF Subprocess exited unsuccessfully with exit code {code}! Arguments: {string.Join(' ', argv)}");
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Robust.Client.CEF/RequestHandlerContext.cs
Normal file
56
Robust.Client.CEF/RequestHandlerContext.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public sealed class RequestHandlerContext
|
||||
{
|
||||
internal readonly CefRequest CefRequest;
|
||||
|
||||
public bool IsNavigation { get; }
|
||||
public bool IsDownload { get; }
|
||||
public string RequestInitiator { get; }
|
||||
|
||||
public string Url => CefRequest.Url;
|
||||
public string Method => CefRequest.Method;
|
||||
|
||||
public bool IsHandled { get; private set; }
|
||||
|
||||
public bool IsCancelled { get; private set; }
|
||||
|
||||
internal IRequestResult? Result { get; private set; }
|
||||
|
||||
internal RequestHandlerContext(
|
||||
bool isNavigation,
|
||||
bool isDownload,
|
||||
string requestInitiator,
|
||||
CefRequest cefRequest)
|
||||
{
|
||||
CefRequest = cefRequest;
|
||||
IsNavigation = isNavigation;
|
||||
IsDownload = isDownload;
|
||||
RequestInitiator = requestInitiator;
|
||||
}
|
||||
|
||||
public void DoCancel()
|
||||
{
|
||||
CheckNotHandled();
|
||||
|
||||
IsHandled = true;
|
||||
IsCancelled = true;
|
||||
}
|
||||
|
||||
public void DoRespondStream(Stream stream, string contentType, HttpStatusCode code = HttpStatusCode.OK)
|
||||
{
|
||||
Result = new RequestResultStream(stream, contentType, code);
|
||||
}
|
||||
|
||||
private void CheckNotHandled()
|
||||
{
|
||||
if (IsHandled)
|
||||
throw new InvalidOperationException("Request has already been handled");
|
||||
}
|
||||
}
|
||||
}
|
||||
93
Robust.Client.CEF/RequestResults.cs
Normal file
93
Robust.Client.CEF/RequestResults.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
internal interface IRequestResult
|
||||
{
|
||||
CefResourceHandler MakeHandler();
|
||||
}
|
||||
|
||||
internal sealed class RequestResultStream : IRequestResult
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private readonly HttpStatusCode _code;
|
||||
private readonly string _contentType;
|
||||
|
||||
public RequestResultStream(Stream stream, string contentType, HttpStatusCode code)
|
||||
{
|
||||
_stream = stream;
|
||||
_code = code;
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
public CefResourceHandler MakeHandler()
|
||||
{
|
||||
return new Handler(_stream, _contentType, _code);
|
||||
}
|
||||
|
||||
private sealed class Handler : CefResourceHandler
|
||||
{
|
||||
// TODO: async
|
||||
// TODO: exception handling
|
||||
|
||||
private readonly Stream _stream;
|
||||
private readonly HttpStatusCode _code;
|
||||
private readonly string _contentType;
|
||||
|
||||
public Handler(Stream stream, string contentType, HttpStatusCode code)
|
||||
{
|
||||
_stream = stream;
|
||||
_code = code;
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
protected override bool Open(CefRequest request, out bool handleRequest, CefCallback callback)
|
||||
{
|
||||
handleRequest = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void GetResponseHeaders(CefResponse response, out long responseLength, out string? redirectUrl)
|
||||
{
|
||||
response.Status = (int) _code;
|
||||
response.StatusText = _code.ToString();
|
||||
response.MimeType = _contentType;
|
||||
|
||||
if (_stream.CanSeek)
|
||||
responseLength = _stream.Length;
|
||||
else
|
||||
responseLength = -1;
|
||||
|
||||
redirectUrl = default;
|
||||
}
|
||||
|
||||
protected override bool Skip(long bytesToSkip, out long bytesSkipped, CefResourceSkipCallback callback)
|
||||
{
|
||||
if (!_stream.CanSeek)
|
||||
{
|
||||
bytesSkipped = -2;
|
||||
return false;
|
||||
}
|
||||
|
||||
bytesSkipped = _stream.Seek(bytesToSkip, SeekOrigin.Begin);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override unsafe bool Read(IntPtr dataOut, int bytesToRead, out int bytesRead, CefResourceReadCallback callback)
|
||||
{
|
||||
var byteSpan = new Span<byte>((void*) dataOut, bytesToRead);
|
||||
|
||||
bytesRead = _stream.Read(byteSpan);
|
||||
|
||||
return bytesRead != 0;
|
||||
}
|
||||
|
||||
protected override void Cancel()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Robust.Client.CEF/Robust.Client.CEF.csproj
Normal file
25
Robust.Client.CEF/Robust.Client.CEF.csproj
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\MSBuild\Robust.Engine.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>WinExe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
|
||||
<Target Name="RobustAfterBuild" AfterTargets="Build" />
|
||||
<Import Project="..\MSBuild\Robust.Engine.targets" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\cefglue\CefGlue\CefGlue.csproj" />
|
||||
<ProjectReference Include="..\Robust.Client\Robust.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
48
Robust.Client.CEF/RobustCefApp.cs
Normal file
48
Robust.Client.CEF/RobustCefApp.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
internal class RobustCefApp : CefApp
|
||||
{
|
||||
private readonly BrowserProcessHandler _browserProcessHandler = new();
|
||||
private readonly RenderProcessHandler _renderProcessHandler = new();
|
||||
|
||||
protected override CefBrowserProcessHandler GetBrowserProcessHandler()
|
||||
{
|
||||
return _browserProcessHandler;
|
||||
}
|
||||
|
||||
protected override CefRenderProcessHandler GetRenderProcessHandler()
|
||||
{
|
||||
return _renderProcessHandler;
|
||||
}
|
||||
|
||||
protected override void OnBeforeCommandLineProcessing(string processType, CefCommandLine commandLine)
|
||||
{
|
||||
// Disable zygote on Linux.
|
||||
commandLine.AppendSwitch("--no-zygote");
|
||||
|
||||
//commandLine.AppendSwitch("--disable-gpu");
|
||||
//commandLine.AppendSwitch("--disable-gpu-compositing");
|
||||
//commandLine.AppendSwitch("--in-process-gpu");
|
||||
|
||||
commandLine.AppendSwitch("disable-threaded-scrolling", "1");
|
||||
commandLine.AppendSwitch("disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents");
|
||||
|
||||
if(IoCManager.Instance != null)
|
||||
Logger.Debug($"{commandLine}");
|
||||
}
|
||||
|
||||
private class BrowserProcessHandler : CefBrowserProcessHandler
|
||||
{
|
||||
}
|
||||
|
||||
// TODO CEF: Research - Is this even needed?
|
||||
private class RenderProcessHandler : CefRenderProcessHandler
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Robust.Client.CEF/RobustCefClient.cs
Normal file
23
Robust.Client.CEF/RobustCefClient.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
// Simple CEF client.
|
||||
internal class RobustCefClient : CefClient
|
||||
{
|
||||
private readonly CefRenderHandler _renderHandler;
|
||||
private readonly CefRequestHandler _requestHandler;
|
||||
private readonly CefLoadHandler _loadHandler;
|
||||
|
||||
internal RobustCefClient(CefRenderHandler handler, CefRequestHandler requestHandler, CefLoadHandler loadHandler)
|
||||
{
|
||||
_renderHandler = handler;
|
||||
_requestHandler = requestHandler;
|
||||
_loadHandler = loadHandler;
|
||||
}
|
||||
|
||||
protected override CefRenderHandler GetRenderHandler() => _renderHandler;
|
||||
protected override CefRequestHandler GetRequestHandler() => _requestHandler;
|
||||
protected override CefLoadHandler GetLoadHandler() => _loadHandler;
|
||||
}
|
||||
}
|
||||
17
Robust.Client.CEF/RobustLoadHandler.cs
Normal file
17
Robust.Client.CEF/RobustLoadHandler.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public sealed class RobustLoadHandler : CefLoadHandler
|
||||
{
|
||||
protected override void OnLoadStart(CefBrowser browser, CefFrame frame, CefTransitionType transitionType)
|
||||
{
|
||||
base.OnLoadStart(browser, frame, transitionType);
|
||||
}
|
||||
|
||||
protected override void OnLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode)
|
||||
{
|
||||
base.OnLoadEnd(browser, frame, httpStatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
128
Robust.Client.CEF/RobustRequestHandler.cs
Normal file
128
Robust.Client.CEF/RobustRequestHandler.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Log;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
internal sealed class RobustRequestHandler : CefRequestHandler
|
||||
{
|
||||
private readonly ISawmill _sawmill;
|
||||
private readonly List<Action<RequestHandlerContext>> _resourceRequestHandlers = new();
|
||||
private readonly List<Action<BeforeBrowseContext>> _beforeBrowseHandlers = new();
|
||||
|
||||
public RobustRequestHandler(ISawmill sawmill)
|
||||
{
|
||||
_sawmill = sawmill;
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
lock (_resourceRequestHandlers)
|
||||
{
|
||||
_resourceRequestHandlers.Add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
lock (_resourceRequestHandlers)
|
||||
{
|
||||
_resourceRequestHandlers.Remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
lock (_beforeBrowseHandlers)
|
||||
{
|
||||
_beforeBrowseHandlers.Add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
lock (_beforeBrowseHandlers)
|
||||
{
|
||||
_beforeBrowseHandlers.Remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
protected override CefResourceRequestHandler? GetResourceRequestHandler(
|
||||
CefBrowser browser,
|
||||
CefFrame frame,
|
||||
CefRequest request,
|
||||
bool isNavigation,
|
||||
bool isDownload,
|
||||
string requestInitiator,
|
||||
ref bool disableDefaultHandling)
|
||||
{
|
||||
lock (_resourceRequestHandlers)
|
||||
{
|
||||
_sawmill.Debug($"HANDLING REQUEST: {request.Url}");
|
||||
|
||||
var context = new RequestHandlerContext(isNavigation, isDownload, requestInitiator, request);
|
||||
|
||||
foreach (var handler in _resourceRequestHandlers)
|
||||
{
|
||||
handler(context);
|
||||
|
||||
if (context.IsHandled)
|
||||
disableDefaultHandling = true;
|
||||
|
||||
if (context.IsCancelled)
|
||||
return null;
|
||||
|
||||
if (context.Result != null)
|
||||
return new WrapReaderResourceHandler(context.Result.MakeHandler());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override bool OnBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request, bool userGesture, bool isRedirect)
|
||||
{
|
||||
lock (_beforeBrowseHandlers)
|
||||
{
|
||||
var context = new BeforeBrowseContext(isRedirect, userGesture, request);
|
||||
|
||||
foreach (var handler in _beforeBrowseHandlers)
|
||||
{
|
||||
handler(context);
|
||||
|
||||
if (context.IsCancelled)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private sealed class WrapReaderResourceHandler : CefResourceRequestHandler
|
||||
{
|
||||
private readonly CefResourceHandler _handler;
|
||||
|
||||
public WrapReaderResourceHandler(CefResourceHandler handler)
|
||||
{
|
||||
_handler = handler;
|
||||
}
|
||||
|
||||
protected override CefCookieAccessFilter? GetCookieAccessFilter(
|
||||
CefBrowser browser,
|
||||
CefFrame frame,
|
||||
CefRequest request)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override CefResourceHandler GetResourceHandler(
|
||||
CefBrowser browser,
|
||||
CefFrame frame,
|
||||
CefRequest request)
|
||||
{
|
||||
return _handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Robust.Client.Injectors/MathParsing.cs
Normal file
37
Robust.Client.Injectors/MathParsing.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Linq;
|
||||
using Pidgin;
|
||||
using static Pidgin.Parser;
|
||||
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
public static class MathParsing
|
||||
{
|
||||
public static Parser<char, float> Single { get; } = Real.Select(c => (float) c);
|
||||
|
||||
public static Parser<char, float> Single1 { get; }
|
||||
= Single.Between(SkipWhitespaces);
|
||||
|
||||
public static Parser<char, (float, float)> Single2 { get; }
|
||||
= Single.Before(SkipWhitespaces).Repeat(2).Select(e =>
|
||||
{
|
||||
var arr = e.ToArray();
|
||||
return (arr[0], arr[1]);
|
||||
});
|
||||
|
||||
public static Parser<char, (float, float, float, float)> Single4 { get; }
|
||||
= Single.Before(SkipWhitespaces).Repeat(4).Select(e =>
|
||||
{
|
||||
var arr = e.ToArray();
|
||||
return (arr[0], arr[1], arr[2], arr[3]);
|
||||
});
|
||||
|
||||
public static Parser<char, float[]> Thickness { get; }
|
||||
= SkipWhitespaces.Then(
|
||||
OneOf(
|
||||
Try(Single4.Select(c => new[] {c.Item1, c.Item2, c.Item3, c.Item4})),
|
||||
Try(Single2.Select(c => new[] {c.Item1, c.Item2})),
|
||||
Try(Single1.Select(c => new[] {c}))
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -29,26 +29,27 @@ namespace Robust.Build.Tasks
|
||||
}
|
||||
}
|
||||
|
||||
//formatted according to https://github.com/dotnet/msbuild/blob/main/src/Shared/CanonicalError.cs#L57
|
||||
class ConsoleBuildEngine : IBuildEngine
|
||||
{
|
||||
public void LogErrorEvent(BuildErrorEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL ERROR {e.Code}: {e.Message}");
|
||||
}
|
||||
|
||||
public void LogWarningEvent(BuildWarningEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"WARNING: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL WARNING {e.Code}: {e.Message}");
|
||||
}
|
||||
|
||||
public void LogMessageEvent(BuildMessageEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL MESSAGE {e.Code}: {e.Message}");
|
||||
}
|
||||
|
||||
public void LogCustomEvent(CustomBuildEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"CUSTOM: {e.Message}");
|
||||
Console.WriteLine(e.Message);
|
||||
}
|
||||
|
||||
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,
|
||||
|
||||
32
Robust.Client.Injectors/RXamlColorAstNode.cs
Normal file
32
Robust.Client.Injectors/RXamlColorAstNode.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Reflection.Emit;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Emit;
|
||||
using XamlX.IL;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
internal class RXamlColorAstNode
|
||||
: XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode
|
||||
{
|
||||
private readonly IXamlMethod _method;
|
||||
private readonly string _color;
|
||||
|
||||
public RXamlColorAstNode(IXamlLineInfo lineInfo, RXamlWellKnownTypes types, string color) : base(lineInfo)
|
||||
{
|
||||
_color = color;
|
||||
Type = new XamlAstClrTypeReference(lineInfo, types.Color, false);
|
||||
_method = types.ColorFromXaml;
|
||||
}
|
||||
|
||||
public IXamlAstTypeReference Type { get; }
|
||||
|
||||
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
|
||||
{
|
||||
codeGen.Ldstr(_color);
|
||||
codeGen.EmitCall(_method);
|
||||
|
||||
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
|
||||
}
|
||||
}
|
||||
}
|
||||
93
Robust.Client.Injectors/RXamlVecLikeConstAstNode.cs
Normal file
93
Robust.Client.Injectors/RXamlVecLikeConstAstNode.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Emit;
|
||||
using XamlX.IL;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
public abstract class RXamlVecLikeConstAstNode<T>
|
||||
: XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode
|
||||
where T : unmanaged
|
||||
{
|
||||
private readonly IXamlConstructor _constructor;
|
||||
protected readonly T[] Values;
|
||||
|
||||
public RXamlVecLikeConstAstNode(
|
||||
IXamlLineInfo lineInfo,
|
||||
IXamlType type, IXamlConstructor constructor,
|
||||
IXamlType componentType, T[] values)
|
||||
: base(lineInfo)
|
||||
{
|
||||
_constructor = constructor;
|
||||
Values = values;
|
||||
|
||||
var @params = constructor.Parameters;
|
||||
|
||||
if (@params.Count != values.Length)
|
||||
throw new ArgumentException("Invalid amount of parameters");
|
||||
|
||||
if (@params.Any(c => c != componentType))
|
||||
throw new ArgumentException("Invalid constructor: not all parameters match component type");
|
||||
|
||||
Type = new XamlAstClrTypeReference(lineInfo, type, false);
|
||||
}
|
||||
|
||||
public IXamlAstTypeReference Type { get; }
|
||||
|
||||
public virtual XamlILNodeEmitResult Emit(
|
||||
XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context,
|
||||
IXamlILEmitter codeGen)
|
||||
{
|
||||
codeGen.Newobj(_constructor);
|
||||
|
||||
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RXamlSingleVecLikeConstAstNode : RXamlVecLikeConstAstNode<float>
|
||||
{
|
||||
public RXamlSingleVecLikeConstAstNode(
|
||||
IXamlLineInfo lineInfo,
|
||||
IXamlType type, IXamlConstructor constructor,
|
||||
IXamlType componentType, float[] values)
|
||||
: base(lineInfo, type, constructor, componentType, values)
|
||||
{
|
||||
}
|
||||
|
||||
public override XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context,
|
||||
IXamlILEmitter codeGen)
|
||||
{
|
||||
foreach (var value in Values)
|
||||
{
|
||||
codeGen.Emit(OpCodes.Ldc_R4, value);
|
||||
}
|
||||
|
||||
return base.Emit(context, codeGen);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RXamlInt32VecLikeConstAstNode : RXamlVecLikeConstAstNode<int>
|
||||
{
|
||||
public RXamlInt32VecLikeConstAstNode(
|
||||
IXamlLineInfo lineInfo,
|
||||
IXamlType type, IXamlConstructor constructor,
|
||||
IXamlType componentType, int[] values)
|
||||
: base(lineInfo, type, constructor, componentType, values)
|
||||
{
|
||||
}
|
||||
|
||||
public override XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context,
|
||||
IXamlILEmitter codeGen)
|
||||
{
|
||||
foreach (var value in Values)
|
||||
{
|
||||
codeGen.Emit(OpCodes.Ldc_I4, value);
|
||||
}
|
||||
|
||||
return base.Emit(context, codeGen);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Robust.Client.Injectors/RXamlWellKnownTypes.cs
Normal file
58
Robust.Client.Injectors/RXamlWellKnownTypes.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Linq;
|
||||
using XamlX.Transform;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
class RXamlWellKnownTypes
|
||||
{
|
||||
public XamlTypeWellKnownTypes XamlIlTypes { get; }
|
||||
public IXamlType Single { get; }
|
||||
public IXamlType Int32 { get; }
|
||||
public IXamlType Vector2 { get; }
|
||||
public IXamlConstructor Vector2ConstructorFull { get; }
|
||||
public IXamlType Vector2i { get; }
|
||||
public IXamlConstructor Vector2iConstructorFull { get; }
|
||||
public IXamlType Thickness { get; }
|
||||
public IXamlConstructor ThicknessConstructorFull { get; }
|
||||
public IXamlType Color { get; }
|
||||
public IXamlMethod ColorFromXaml { get; }
|
||||
|
||||
public RXamlWellKnownTypes(TransformerConfiguration cfg)
|
||||
{
|
||||
var ts = cfg.TypeSystem;
|
||||
XamlIlTypes = cfg.WellKnownTypes;
|
||||
Single = ts.GetType("System.Single");
|
||||
Int32 = ts.GetType("System.Int32");
|
||||
|
||||
(Vector2, Vector2ConstructorFull) = GetNumericTypeInfo("Robust.Shared.Maths.Vector2", Single, 2);
|
||||
(Vector2i, Vector2iConstructorFull) = GetNumericTypeInfo("Robust.Shared.Maths.Vector2i", Int32, 2);
|
||||
(Thickness, ThicknessConstructorFull) = GetNumericTypeInfo("Robust.Shared.Maths.Thickness", Single, 4);
|
||||
|
||||
(IXamlType, IXamlConstructor) GetNumericTypeInfo(string name, IXamlType componentType, int componentCount)
|
||||
{
|
||||
var type = cfg.TypeSystem.GetType(name);
|
||||
var ctor = type.GetConstructor(Enumerable.Repeat(componentType, componentCount).ToList());
|
||||
|
||||
return (type, ctor);
|
||||
}
|
||||
|
||||
Color = cfg.TypeSystem.GetType("Robust.Shared.Maths.Color");
|
||||
ColorFromXaml = Color.GetMethod(new FindMethodMethodSignature("FromXaml", Color, XamlIlTypes.String)
|
||||
{
|
||||
IsStatic = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static class RXamlWellKnownTypesExtensions
|
||||
{
|
||||
public static RXamlWellKnownTypes GetRobustTypes(this AstTransformationContext ctx)
|
||||
{
|
||||
if (ctx.TryGetItem<RXamlWellKnownTypes>(out var rv))
|
||||
return rv;
|
||||
ctx.SetItem(rv = new RXamlWellKnownTypes(ctx.Configuration));
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build.Framework" Version="16.8.0" />
|
||||
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
|
||||
<PackageReference Include="Pidgin" Version="2.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using XamlX;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Emit;
|
||||
using XamlX.IL;
|
||||
@@ -11,11 +13,14 @@ namespace Robust.Build.Tasks
|
||||
/// Emitters & Transformers based on:
|
||||
/// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
|
||||
/// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
|
||||
/// - https://github.com/AvaloniaUI/Avalonia/blob/afb8ae6f3c517dae912729511483995b16cb31af/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
|
||||
/// </summary>
|
||||
public class RobustXamlILCompiler : XamlILCompiler
|
||||
{
|
||||
public RobustXamlILCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings, bool fillWithDefaults) : base(configuration, emitMappings, fillWithDefaults)
|
||||
{
|
||||
Transformers.Insert(0, new IgnoredDirectivesTransformer());
|
||||
|
||||
Transformers.Add(new AddNameScopeRegistration());
|
||||
Transformers.Add(new RobustMarkRootObjectScopeNode());
|
||||
|
||||
@@ -197,5 +202,24 @@ namespace Robust.Build.Tasks
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IgnoredDirectivesTransformer : IXamlAstTransformer
|
||||
{
|
||||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
|
||||
{
|
||||
if (node is XamlAstObjectNode astNode)
|
||||
{
|
||||
astNode.Children.RemoveAll(n =>
|
||||
n is XamlAstXmlDirective dir &&
|
||||
dir.Namespace == XamlNamespaces.Xaml2006 &&
|
||||
(dir.Name == "Class" ||
|
||||
dir.Name == "Precompile" ||
|
||||
dir.Name == "FieldModifier" ||
|
||||
dir.Name == "ClassModifier"));
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.Build.Framework;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using Pidgin;
|
||||
using XamlX;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Emit;
|
||||
@@ -70,7 +71,7 @@ namespace Robust.Build.Tasks
|
||||
{
|
||||
XmlnsAttributes =
|
||||
{
|
||||
typeSystem.GetType("Robust.Client.UserInterface.XAML.XmlnsDefinitionAttribute"),
|
||||
typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute"),
|
||||
|
||||
},
|
||||
ContentAttributes =
|
||||
@@ -98,7 +99,7 @@ namespace Robust.Build.Tasks
|
||||
typeSystem,
|
||||
typeSystem.TargetAssembly,
|
||||
xamlLanguage,
|
||||
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage));
|
||||
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage), CustomValueConverter);
|
||||
|
||||
var contextDef = new TypeDefinition("CompiledRobustXaml", "XamlIlContext",
|
||||
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
|
||||
@@ -279,8 +280,8 @@ namespace Robust.Build.Tasks
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", res.Uri, 0, 0, 0, 0,
|
||||
e.ToString(), "", "CompileRobustXaml"));
|
||||
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
|
||||
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -297,6 +298,112 @@ namespace Robust.Build.Tasks
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool CustomValueConverter(
|
||||
AstTransformationContext context,
|
||||
IXamlAstValueNode node,
|
||||
IXamlType type,
|
||||
out IXamlAstValueNode result)
|
||||
{
|
||||
if (!(node is XamlAstTextNode textNode))
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var text = textNode.Text;
|
||||
var types = context.GetRobustTypes();
|
||||
|
||||
if (type.Equals(types.Vector2))
|
||||
{
|
||||
var foo = MathParsing.Single2.Parse(text);
|
||||
|
||||
if (!foo.Success)
|
||||
throw new XamlLoadException($"Unable to parse \"{text}\" as a Vector2", node);
|
||||
|
||||
var (x, y) = foo.Value;
|
||||
|
||||
result = new RXamlSingleVecLikeConstAstNode(
|
||||
node,
|
||||
types.Vector2, types.Vector2ConstructorFull,
|
||||
types.Single, new[] {x, y});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.Equals(types.Thickness))
|
||||
{
|
||||
var foo = MathParsing.Thickness.Parse(text);
|
||||
|
||||
if (!foo.Success)
|
||||
throw new XamlLoadException($"Unable to parse \"{text}\" as a Thickness", node);
|
||||
|
||||
var val = foo.Value;
|
||||
float[] full;
|
||||
if (val.Length == 1)
|
||||
{
|
||||
var u = val[0];
|
||||
full = new[] {u, u, u, u};
|
||||
}
|
||||
else if (val.Length == 2)
|
||||
{
|
||||
var h = val[0];
|
||||
var v = val[1];
|
||||
full = new[] {h, v, h, v};
|
||||
}
|
||||
else // 4
|
||||
{
|
||||
full = val;
|
||||
}
|
||||
|
||||
result = new RXamlSingleVecLikeConstAstNode(
|
||||
node,
|
||||
types.Thickness, types.ThicknessConstructorFull,
|
||||
types.Single, full);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.Equals(types.Thickness))
|
||||
{
|
||||
var foo = MathParsing.Thickness.Parse(text);
|
||||
|
||||
if (!foo.Success)
|
||||
throw new XamlLoadException($"Unable to parse \"{text}\" as a Thickness", node);
|
||||
|
||||
var val = foo.Value;
|
||||
float[] full;
|
||||
if (val.Length == 1)
|
||||
{
|
||||
var u = val[0];
|
||||
full = new[] {u, u, u, u};
|
||||
}
|
||||
else if (val.Length == 2)
|
||||
{
|
||||
var h = val[0];
|
||||
var v = val[1];
|
||||
full = new[] {h, v, h, v};
|
||||
}
|
||||
else // 4
|
||||
{
|
||||
full = val;
|
||||
}
|
||||
|
||||
result = new RXamlSingleVecLikeConstAstNode(
|
||||
node,
|
||||
types.Thickness, types.ThicknessConstructorFull,
|
||||
types.Single, full);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.Equals(types.Color))
|
||||
{
|
||||
// TODO: Interpret these colors at XAML compile time instead of at runtime.
|
||||
result = new RXamlColorAstNode(node, types, text);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public const string ContextNameScopeFieldName = "RobustNameScope";
|
||||
|
||||
private static void EmitNameScopeField(XamlLanguageTypeMappings xamlLanguage, CecilTypeSystem typeSystem, IXamlTypeBuilder<IXamlILEmitter> typeBuilder, IXamlILEmitter constructor)
|
||||
|
||||
19
Robust.Client.NameGenerator/InvalidXamlRootTypeException.cs
Normal file
19
Robust.Client.NameGenerator/InvalidXamlRootTypeException.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Robust.Client.NameGenerator
|
||||
{
|
||||
public class InvalidXamlRootTypeException : Exception
|
||||
{
|
||||
public readonly INamedTypeSymbol ExpectedType;
|
||||
public readonly INamedTypeSymbol ExpectedBaseType;
|
||||
public readonly INamedTypeSymbol Actual;
|
||||
|
||||
public InvalidXamlRootTypeException(INamedTypeSymbol actual, INamedTypeSymbol expectedType, INamedTypeSymbol expectedBaseType)
|
||||
{
|
||||
Actual = actual;
|
||||
ExpectedType = expectedType;
|
||||
ExpectedBaseType = expectedBaseType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
@@ -24,6 +25,7 @@ namespace Robust.Client.NameGenerator
|
||||
{
|
||||
private const string AttributeName = "Robust.Client.AutoGenerated.GenerateTypedNameReferencesAttribute";
|
||||
private const string AttributeFile = "GenerateTypedNameReferencesAttribute";
|
||||
|
||||
private const string AttributeCode = @"// <auto-generated />
|
||||
using System;
|
||||
namespace Robust.Client.AutoGenerated
|
||||
@@ -54,8 +56,8 @@ namespace Robust.Client.AutoGenerated
|
||||
{
|
||||
var clrtype = objectNode.Type.GetClrType();
|
||||
var isControl = IsControl(clrtype);
|
||||
//clrtype.Interfaces.Any(i =>
|
||||
//i.IsInterface && i.FullName == "Robust.Client.UserInterface.IControl");
|
||||
//clrtype.Interfaces.Any(i =>
|
||||
//i.IsInterface && i.FullName == "Robust.Client.UserInterface.IControl");
|
||||
|
||||
if (!isControl)
|
||||
return node;
|
||||
@@ -80,9 +82,13 @@ namespace Robust.Client.AutoGenerated
|
||||
return node;
|
||||
}
|
||||
|
||||
public void Push(IXamlAstNode node) { }
|
||||
public void Push(IXamlAstNode node)
|
||||
{
|
||||
}
|
||||
|
||||
public void Pop() { }
|
||||
public void Pop()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static string GenerateSourceCode(
|
||||
@@ -97,16 +103,26 @@ namespace Robust.Client.AutoGenerated
|
||||
var compiler =
|
||||
new XamlILCompiler(
|
||||
new TransformerConfiguration(typeSystem, typeSystem.Assemblies[0],
|
||||
new XamlLanguageTypeMappings(typeSystem)),
|
||||
new XamlLanguageTypeMappings(typeSystem)
|
||||
{
|
||||
XmlnsAttributes = {typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute")}
|
||||
}),
|
||||
new XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult>(), false);
|
||||
compiler.Transformers.Add(new TypeReferenceResolver());
|
||||
compiler.Transform(parsed);
|
||||
var initialRoot = (XamlAstObjectNode) parsed.Root;
|
||||
var names = NameVisitor.GetNames(initialRoot);
|
||||
|
||||
var rootType = (INamedTypeSymbol)initialRoot.Type.GetClrType().Id;
|
||||
var rootTypeString = rootType.ToString();
|
||||
if (classSymbol.ToString() != rootTypeString && classSymbol.BaseType?.ToString() != rootTypeString)
|
||||
throw new InvalidXamlRootTypeException(rootType, classSymbol, classSymbol.BaseType);
|
||||
|
||||
var fieldAccess = classSymbol.IsSealed ? "private" : "protected";
|
||||
//var names = NameVisitor.GetNames((XamlAstObjectNode)XDocumentXamlParser.Parse(xamlFile).Root);
|
||||
var namedControls = names.Select(info => " " +
|
||||
$"protected global::{info.type} {info.name} => " +
|
||||
$"this.FindControl<global::{info.type}>(\"{info.name}\");");
|
||||
$"{fieldAccess} global::{info.type} {info.name} => " +
|
||||
$"this.FindControl<global::{info.type}>(\"{info.name}\");");
|
||||
return $@"// <auto-generated />
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -124,7 +140,7 @@ namespace {nameSpace}
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
var comp = (CSharpCompilation) context.Compilation;
|
||||
if(comp.GetTypeByMetadataName(AttributeName) == null)
|
||||
if (comp.GetTypeByMetadataName(AttributeName) == null)
|
||||
context.AddSource(AttributeFile, SourceText.From(AttributeCode, Encoding.UTF8));
|
||||
if (!(context.SyntaxReceiver is NameReferenceSyntaxReceiver receiver))
|
||||
{
|
||||
@@ -132,15 +148,16 @@ namespace {nameSpace}
|
||||
}
|
||||
|
||||
var symbols = UnpackAnnotatedTypes(context, comp, receiver);
|
||||
if(symbols == null)
|
||||
if (symbols == null)
|
||||
return;
|
||||
|
||||
foreach (var typeSymbol in symbols)
|
||||
{
|
||||
var xamlFileName = $"{typeSymbol.Name}.xaml";
|
||||
var relevantXamlFile = context.AdditionalFiles.FirstOrDefault(t => t.Path.EndsWith(xamlFileName));
|
||||
var xamlFileNameSep = $"{Path.DirectorySeparatorChar}{xamlFileName}";
|
||||
var relevantXamlFiles = context.AdditionalFiles.Where(t => t.Path.EndsWith(xamlFileNameSep)).ToArray();
|
||||
|
||||
if (relevantXamlFile == null)
|
||||
if (relevantXamlFiles.Length == 0)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
@@ -156,19 +173,35 @@ namespace {nameSpace}
|
||||
continue;
|
||||
}
|
||||
|
||||
var txt = relevantXamlFile.GetText()?.ToString();
|
||||
if (txt == null)
|
||||
if (relevantXamlFiles.Length > 1)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0002",
|
||||
$"Found multiple candidate XAML files for {typeSymbol}",
|
||||
$"Multiple files exist with name {xamlFileName}",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
typeSymbol.Locations[0]));
|
||||
continue;
|
||||
}
|
||||
|
||||
var txt = relevantXamlFiles[0].GetText()?.ToString();
|
||||
if (txt == null)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0004",
|
||||
$"Unexpected empty Xaml-File was found at {xamlFileName}",
|
||||
"Expected Content due to a Class with the same name being annotated with [GenerateTypedNameReferences].",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
Location.Create(xamlFileName, new TextSpan(0,0), new LinePositionSpan(new LinePosition(0,0),new LinePosition(0,0)))));
|
||||
Location.Create(xamlFileName, new TextSpan(0, 0),
|
||||
new LinePositionSpan(new LinePosition(0, 0), new LinePosition(0, 0)))));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -177,12 +210,27 @@ namespace {nameSpace}
|
||||
var sourceCode = GenerateSourceCode(typeSymbol, txt, comp);
|
||||
context.AddSource($"{typeSymbol.Name}.g.cs", SourceText.From(sourceCode, Encoding.UTF8));
|
||||
}
|
||||
catch (InvalidXamlRootTypeException invRootType)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0005",
|
||||
$"XAML-File {xamlFileName} has the wrong root type!",
|
||||
$"{xamlFileName}: Expected root type '{invRootType.ExpectedType}' or '{invRootType.ExpectedBaseType}', but got '{invRootType.Actual}'.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
Location.Create(xamlFileName, new TextSpan(0, 0),
|
||||
new LinePositionSpan(new LinePosition(0, 0), new LinePosition(0, 0)))));
|
||||
continue;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"AXN0003",
|
||||
"RXN0003",
|
||||
"Unhandled exception occured while generating typed Name references.",
|
||||
$"Unhandled exception occured while generating typed Name references: {e}",
|
||||
"Usage",
|
||||
@@ -194,7 +242,8 @@ namespace {nameSpace}
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<INamedTypeSymbol> UnpackAnnotatedTypes(in GeneratorExecutionContext context, CSharpCompilation comp, NameReferenceSyntaxReceiver receiver)
|
||||
private IReadOnlyList<INamedTypeSymbol> UnpackAnnotatedTypes(in GeneratorExecutionContext context,
|
||||
CSharpCompilation comp, NameReferenceSyntaxReceiver receiver)
|
||||
{
|
||||
var options = (CSharpParseOptions) comp.SyntaxTrees[0].Options;
|
||||
var compilation =
|
||||
@@ -229,14 +278,13 @@ namespace {nameSpace}
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0004",
|
||||
"RXN0006",
|
||||
missingPartialKeywordMessage,
|
||||
missingPartialKeywordMessage,
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
Location.None));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Client.Animations
|
||||
/// <seealso cref="AnimationPlayerComponent"/>
|
||||
public sealed class Animation
|
||||
{
|
||||
public readonly List<AnimationTrack> AnimationTracks = new();
|
||||
public List<AnimationTrack> AnimationTracks { get; private set; } = new();
|
||||
|
||||
public TimeSpan Length { get; set; }
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Robust.Client.Animations
|
||||
{
|
||||
@@ -14,7 +15,7 @@ namespace Robust.Client.Animations
|
||||
/// <summary>
|
||||
/// A list of key frames for when to fire flicks.
|
||||
/// </summary>
|
||||
public readonly List<KeyFrame> KeyFrames = new();
|
||||
public List<KeyFrame> KeyFrames { get; private set; } = new();
|
||||
|
||||
public override (int KeyFrameIndex, float FramePlayingTime) InitPlayback()
|
||||
{
|
||||
@@ -36,8 +37,7 @@ namespace Robust.Client.Animations
|
||||
|
||||
var keyFrame = KeyFrames[keyFrameIndex];
|
||||
|
||||
EntitySystem.Get<AudioSystem>()
|
||||
.Play(keyFrame.Resource, entity, keyFrame.AudioParamsFunc.Invoke());
|
||||
SoundSystem.Play(Filter.Local(), keyFrame.Resource, entity, keyFrame.AudioParamsFunc.Invoke());
|
||||
}
|
||||
|
||||
return (keyFrameIndex, playingTime);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Robust.Client.Animations
|
||||
/// </summary>
|
||||
public abstract class AnimationTrackProperty : AnimationTrack
|
||||
{
|
||||
public readonly List<KeyFrame> KeyFrames = new();
|
||||
public List<KeyFrame> KeyFrames { get; protected set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// How to interpolate values when between two keyframes.
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Robust.Client.Animations
|
||||
/// <summary>
|
||||
/// A list of key frames for when to fire flicks.
|
||||
/// </summary>
|
||||
public readonly List<KeyFrame> KeyFrames = new();
|
||||
public List<KeyFrame> KeyFrames { get; private set; } = new();
|
||||
|
||||
// TODO: Should this layer key be per keyframe maybe?
|
||||
/// <summary>
|
||||
|
||||
@@ -6,6 +6,8 @@ using System.Threading;
|
||||
using NFluidsynth;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -13,7 +15,9 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Logger = Robust.Shared.Log.Logger;
|
||||
|
||||
namespace Robust.Client.Audio.Midi
|
||||
@@ -32,24 +36,6 @@ namespace Robust.Client.Audio.Midi
|
||||
/// </returns>
|
||||
IMidiRenderer? GetNewRenderer();
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Checks whether the file at the given path is a valid midi file or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We add this here so content doesn't need to reference NFluidsynth.
|
||||
/// </remarks>
|
||||
bool IsMidiFile(string filename);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the file at the given path is a valid midi file or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We add this here so content doesn't need to reference NFluidsynth.
|
||||
/// </remarks>
|
||||
bool IsSoundfontFile(string filename);
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Method called every frame.
|
||||
/// Should be used to update positional audio.
|
||||
@@ -57,20 +43,31 @@ namespace Robust.Client.Audio.Midi
|
||||
/// <param name="frameTime"></param>
|
||||
void FrameUpdate(float frameTime);
|
||||
|
||||
/// <summary>
|
||||
/// Volume, in db.
|
||||
/// </summary>
|
||||
float Volume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, MIDI support is available.
|
||||
/// </summary>
|
||||
bool IsAvailable { get; }
|
||||
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
|
||||
void Shutdown();
|
||||
}
|
||||
|
||||
internal class MidiManager : IDisposable, IMidiManager
|
||||
internal class MidiManager : IMidiManager
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
|
||||
|
||||
private SharedBroadphaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsAvailable
|
||||
{
|
||||
get
|
||||
@@ -81,12 +78,29 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<MidiRenderer> _renderers = new();
|
||||
[ViewVariables]
|
||||
private readonly List<IMidiRenderer> _renderers = new();
|
||||
|
||||
private bool _alive = true;
|
||||
private Settings? _settings;
|
||||
private Thread? _midiThread;
|
||||
private ISawmill _midiSawmill = default!;
|
||||
private float _volume = 0f;
|
||||
private bool _volumeDirty = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseTo(_volume, value))
|
||||
return;
|
||||
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, value);
|
||||
_volumeDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string[] LinuxSoundfonts =
|
||||
{
|
||||
@@ -113,12 +127,19 @@ namespace Robust.Client.Audio.Midi
|
||||
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
|
||||
private void InitializeFluidsynth()
|
||||
{
|
||||
if (FluidsynthInitialized || _failedInitialize) return;
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
|
||||
{
|
||||
_volume = value;
|
||||
_volumeDirty = true;
|
||||
}, true);
|
||||
|
||||
_midiSawmill = Logger.GetSawmill("midi");
|
||||
_sawmill = Logger.GetSawmill("midi.fluidsynth");
|
||||
_loggerDelegate = LoggerDelegate;
|
||||
@@ -154,6 +175,7 @@ namespace Robust.Client.Audio.Midi
|
||||
_midiThread = new Thread(ThreadUpdate);
|
||||
_midiThread.Start();
|
||||
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
@@ -170,18 +192,6 @@ namespace Robust.Client.Audio.Midi
|
||||
_sawmill.Log(rLevel, message);
|
||||
}
|
||||
|
||||
/*
|
||||
public bool IsMidiFile(string filename)
|
||||
{
|
||||
return SoundFont.IsMidiFile(filename);
|
||||
}
|
||||
|
||||
public bool IsSoundfontFile(string filename)
|
||||
{
|
||||
return SoundFont.IsSoundFont(filename);
|
||||
}
|
||||
*/
|
||||
|
||||
public IMidiRenderer? GetNewRenderer()
|
||||
{
|
||||
if (!FluidsynthInitialized)
|
||||
@@ -215,7 +225,7 @@ namespace Robust.Client.Audio.Midi
|
||||
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
|
||||
renderer.LoadSoundfont(FallbackSoundfont);
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
foreach (var filepath in LinuxSoundfonts)
|
||||
{
|
||||
@@ -233,19 +243,21 @@ namespace Robust.Client.Audio.Midi
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
|
||||
renderer.LoadSoundfont(OsxSoundfont, true);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
|
||||
renderer.LoadSoundfont(WindowsSoundfont, true);
|
||||
}
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
_renderers.Add(renderer);
|
||||
}
|
||||
|
||||
return renderer;
|
||||
}
|
||||
@@ -263,69 +275,79 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
|
||||
// Update positions of streams every frame.
|
||||
lock (_renderers)
|
||||
foreach (var renderer in _renderers)
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
if (renderer.Disposed)
|
||||
continue;
|
||||
|
||||
if(_volumeDirty)
|
||||
renderer.Source.SetVolume(Volume);
|
||||
|
||||
if (!renderer.Mono)
|
||||
{
|
||||
if (renderer.Disposed)
|
||||
continue;
|
||||
|
||||
if (!renderer.Mono)
|
||||
{
|
||||
renderer.Source.SetGlobal();
|
||||
continue;
|
||||
}
|
||||
|
||||
MapCoordinates? mapPos = null;
|
||||
if (renderer.TrackingCoordinates != null)
|
||||
{
|
||||
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
|
||||
}
|
||||
else if (renderer.TrackingEntity != null)
|
||||
{
|
||||
mapPos = renderer.TrackingEntity.Transform.MapPosition;
|
||||
}
|
||||
|
||||
if (mapPos != null)
|
||||
{
|
||||
var pos = mapPos.Value;
|
||||
if (pos.MapId != _eyeManager.CurrentMap)
|
||||
{
|
||||
renderer.Source.SetVolume(-10000000);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length > 0)
|
||||
{
|
||||
occlusion = IoCManager.Resolve<IPhysicsManager>().IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
pos.Position,
|
||||
sourceRelative.Normalized,
|
||||
OcclusionCollisionMask),
|
||||
sourceRelative.Length,
|
||||
renderer.TrackingEntity);
|
||||
}
|
||||
renderer.Source.SetOcclusion(occlusion);
|
||||
}
|
||||
|
||||
if (renderer.Source.SetPosition(pos.Position))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (float.IsNaN(pos.Position.X) || float.IsNaN(pos.Position.Y))
|
||||
{
|
||||
// just duck out instead of move to NaN
|
||||
renderer.Source.SetOcclusion(float.MaxValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
_midiSawmill?.Warning("Interrupting positional audio, can't set position.");
|
||||
renderer.Source.StopPlaying();
|
||||
}
|
||||
renderer.Source.SetGlobal();
|
||||
continue;
|
||||
}
|
||||
|
||||
MapCoordinates? mapPos = null;
|
||||
if (renderer.TrackingCoordinates != null)
|
||||
{
|
||||
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
|
||||
}
|
||||
else if (renderer.TrackingEntity != null)
|
||||
{
|
||||
mapPos = renderer.TrackingEntity.Transform.MapPosition;
|
||||
}
|
||||
|
||||
if (mapPos != null)
|
||||
{
|
||||
var pos = mapPos.Value;
|
||||
if (pos.MapId != _eyeManager.CurrentMap)
|
||||
{
|
||||
renderer.Source.SetVolume(-10000000);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length > 0)
|
||||
{
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
pos.Position,
|
||||
sourceRelative.Normalized,
|
||||
OcclusionCollisionMask),
|
||||
sourceRelative.Length,
|
||||
renderer.TrackingEntity);
|
||||
}
|
||||
|
||||
renderer.Source.SetOcclusion(occlusion);
|
||||
}
|
||||
|
||||
if (renderer.Source.SetPosition(pos.Position))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (renderer.TrackingEntity != null)
|
||||
{
|
||||
renderer.Source.SetVelocity(renderer.TrackingEntity.GlobalLinearVelocity());
|
||||
}
|
||||
|
||||
if (float.IsNaN(pos.Position.X) || float.IsNaN(pos.Position.Y))
|
||||
{
|
||||
// just duck out instead of move to NaN
|
||||
renderer.Source.SetOcclusion(float.MaxValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
_midiSawmill?.Warning("Interrupting positional audio, can't set position.");
|
||||
renderer.Source.StopPlaying();
|
||||
}
|
||||
}
|
||||
|
||||
_volumeDirty = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -336,6 +358,7 @@ namespace Robust.Client.Audio.Midi
|
||||
while (_alive)
|
||||
{
|
||||
lock (_renderers)
|
||||
{
|
||||
for (var i = 0; i < _renderers.Count; i++)
|
||||
{
|
||||
var renderer = _renderers[i];
|
||||
@@ -343,23 +366,28 @@ namespace Robust.Client.Audio.Midi
|
||||
renderer.Render();
|
||||
else
|
||||
{
|
||||
((IMidiRenderer)renderer).InternalDispose();
|
||||
renderer.InternalDispose();
|
||||
_renderers.Remove(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public void Shutdown()
|
||||
{
|
||||
_alive = false;
|
||||
_midiThread?.Join();
|
||||
_settings?.Dispose();
|
||||
foreach (var renderer in _renderers)
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
renderer?.Dispose();
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
renderer?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (FluidsynthInitialized && !_failedInitialize)
|
||||
@@ -414,6 +442,7 @@ namespace Robust.Client.Audio.Midi
|
||||
var span = new Span<byte>(buf.ToPointer(), length);
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
|
||||
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
|
||||
try
|
||||
{
|
||||
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
|
||||
@@ -437,6 +466,7 @@ namespace Robust.Client.Audio.Midi
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -458,10 +488,12 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
public override int Close(IntPtr sfHandle)
|
||||
{
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
if (!_openStreams.Remove((int) sfHandle, out var stream))
|
||||
return -1;
|
||||
|
||||
stream.Dispose();
|
||||
_openStreams.Remove((int) sfHandle);
|
||||
return 0;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using NFluidsynth;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Asynchronous;
|
||||
@@ -7,7 +6,8 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using MidiEvent = NFluidsynth.MidiEvent;
|
||||
|
||||
namespace Robust.Client.Audio.Midi
|
||||
@@ -21,6 +21,17 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
public interface IMidiRenderer : IDisposable
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The buffered audio source of this renderer.
|
||||
/// </summary>
|
||||
internal IClydeBufferedAudioSource Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this renderer has been disposed or not.
|
||||
/// </summary>
|
||||
bool Disposed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This controls whether the midi file being played will loop or not.
|
||||
/// </summary>
|
||||
@@ -110,6 +121,11 @@ namespace Robust.Client.Audio.Midi
|
||||
/// </summary>
|
||||
void StopAllNotes();
|
||||
|
||||
/// <summary>
|
||||
/// Render and play MIDI to the audio source.
|
||||
/// </summary>
|
||||
internal void Render();
|
||||
|
||||
/// <summary>
|
||||
/// Loads a new soundfont into the renderer.
|
||||
/// </summary>
|
||||
@@ -159,7 +175,7 @@ namespace Robust.Client.Audio.Midi
|
||||
internal void InternalDispose();
|
||||
}
|
||||
|
||||
public class MidiRenderer : IMidiRenderer
|
||||
internal class MidiRenderer : IMidiRenderer
|
||||
{
|
||||
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
@@ -186,53 +202,67 @@ namespace Robust.Client.Audio.Midi
|
||||
private const int SampleRate = 44100;
|
||||
private const int Buffers = SampleRate / 2205;
|
||||
private readonly object _playerStateLock = new();
|
||||
private bool _debugEvents = false;
|
||||
private SequencerClientId _synthRegister;
|
||||
private SequencerClientId _debugRegister;
|
||||
public IClydeBufferedAudioSource Source { get; set; }
|
||||
IClydeBufferedAudioSource IMidiRenderer.Source => Source;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Disposed { get; private set; } = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public byte MidiProgram
|
||||
{
|
||||
get => _midiProgram;
|
||||
set
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
for (var i = 0; i < 16; i++)
|
||||
for (var i = 0; i < _synth.MidiChannelCount; i++)
|
||||
_synth.ProgramChange(i, value);
|
||||
|
||||
_midiProgram = value;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public byte MidiBank
|
||||
{
|
||||
get => _midiBank;
|
||||
set
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
for (var i = 0; i < 16; i++)
|
||||
for (var i = 0; i < _synth.MidiChannelCount; i++)
|
||||
_synth.BankSelect(i, value);
|
||||
|
||||
_midiBank = value;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public uint MidiSoundfont
|
||||
{
|
||||
get => _midiSoundfont;
|
||||
set
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
for (var i = 0; i < 16; i++)
|
||||
for (var i = 0; i < _synth.MidiChannelCount; i++)
|
||||
_synth.SoundFontSelect(i, value);
|
||||
|
||||
_midiSoundfont = value;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool DisablePercussionChannel { get; set; } = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool DisableProgramChangeEvent { get; set; } = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PlayerTotalTick => _player?.GetTotalTicks ?? 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PlayerTick
|
||||
{
|
||||
get => _player?.CurrentTick ?? 0;
|
||||
@@ -243,12 +273,19 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public uint SequencerTick => _sequencer?.Tick ?? 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public double SequencerTimeScale => _sequencer?.TimeScale ?? 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Mono { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public MidiRendererStatus Status { get; private set; } = MidiRendererStatus.None;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool LoopMidi
|
||||
{
|
||||
get => _loopMidi;
|
||||
@@ -260,10 +297,11 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public IEntity? TrackingEntity { get; set; } = null;
|
||||
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
|
||||
|
||||
internal bool Free { get; set; } = false;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
|
||||
|
||||
internal MidiRenderer(Settings settings, SoundFontLoader soundFontLoader, bool mono = true)
|
||||
{
|
||||
@@ -276,6 +314,7 @@ namespace Robust.Client.Audio.Midi
|
||||
_soundFontLoader = soundFontLoader;
|
||||
_synth = new Synth(_settings);
|
||||
_sequencer = new Sequencer(false);
|
||||
_debugRegister = _sequencer.RegisterClient("honk", DumpSequencerEvent);
|
||||
_synthRegister = _sequencer.RegisterFluidsynth(_synth);
|
||||
|
||||
_synth.AddSoundFontLoader(soundFontLoader);
|
||||
@@ -285,6 +324,27 @@ namespace Robust.Client.Audio.Midi
|
||||
Source.StartPlaying();
|
||||
}
|
||||
|
||||
private void DumpSequencerEvent(uint time, SequencerEvent @event)
|
||||
{
|
||||
// ReSharper disable once UseStringInterpolation
|
||||
_midiSawmill.Debug(string.Format(
|
||||
"{0:D8}: {1} chan:{2:D2} key:{3:D5} bank:{4:D2} ctrl:{5:D5} dur:{6:D5} pitch:{7:D5} prog:{8:D3} val:{9:D5} vel:{10:D5}",
|
||||
time,
|
||||
@event.Type.ToString().PadLeft(22),
|
||||
@event.Channel,
|
||||
@event.Key,
|
||||
@event.Bank,
|
||||
@event.Control,
|
||||
@event.Duration,
|
||||
@event.Pitch,
|
||||
@event.Program,
|
||||
@event.Value,
|
||||
@event.Velocity));
|
||||
|
||||
@event.Dest = _synthRegister;
|
||||
_sequencer.SendNow(@event);
|
||||
}
|
||||
|
||||
public bool OpenInput()
|
||||
{
|
||||
if (Disposed)
|
||||
@@ -294,7 +354,11 @@ namespace Robust.Client.Audio.Midi
|
||||
Status = MidiRendererStatus.Input;
|
||||
StopAllNotes();
|
||||
|
||||
_driver = new MidiDriver(_settings, MidiDriverEventHandler);
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
_driver = new MidiDriver(_settings, MidiDriverEventHandler);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -332,8 +396,13 @@ namespace Robust.Client.Audio.Midi
|
||||
{
|
||||
if (Status != MidiRendererStatus.Input) return false;
|
||||
Status = MidiRendererStatus.None;
|
||||
_driver?.Dispose();
|
||||
_driver = null;
|
||||
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
_driver?.Dispose();
|
||||
_driver = null;
|
||||
}
|
||||
|
||||
StopAllNotes();
|
||||
return true;
|
||||
}
|
||||
@@ -357,7 +426,8 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
public void StopAllNotes()
|
||||
{
|
||||
_synth.AllNotesOff(-1);
|
||||
lock(_playerStateLock)
|
||||
_synth.AllNotesOff(-1);
|
||||
}
|
||||
|
||||
public void LoadSoundfont(string filename, bool resetPresets = false)
|
||||
@@ -372,13 +442,15 @@ namespace Robust.Client.Audio.Midi
|
||||
public event Action<Shared.Audio.Midi.MidiEvent>? OnMidiEvent;
|
||||
public event Action? OnMidiPlayerFinished;
|
||||
|
||||
internal void Render(int length = SampleRate / 250)
|
||||
void IMidiRenderer.Render()
|
||||
{
|
||||
Render();
|
||||
}
|
||||
|
||||
private void Render(int length = SampleRate / 250)
|
||||
{
|
||||
if (Disposed) return;
|
||||
|
||||
// SSE needs this.
|
||||
DebugTools.Assert(length % 4 == 0, "Sample length must be multiple of 4");
|
||||
|
||||
var buffersProcessed = Source.GetNumberOfBuffersProcessed();
|
||||
if(buffersProcessed == Buffers) _midiSawmill.Warning("MIDI buffer overflow!");
|
||||
if (buffersProcessed == 0) return;
|
||||
@@ -393,36 +465,16 @@ namespace Robust.Client.Audio.Midi
|
||||
Source.GetBuffersProcessed(buffers);
|
||||
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
// _sequencer.Process(10);
|
||||
_synth?.WriteSampleFloat(length * buffers.Length, audio, 0, Mono ? 1 : 2,
|
||||
audio, Mono ? length * buffers.Length : 1, Mono ? 1 : 2);
|
||||
|
||||
}
|
||||
if (Mono) // Turn audio to mono
|
||||
{
|
||||
var l = length * buffers.Length;
|
||||
|
||||
if (Sse.IsSupported)
|
||||
{
|
||||
fixed (float* ptr = audio)
|
||||
{
|
||||
for (var j = 0; j < l; j += 4)
|
||||
{
|
||||
var k = j + l;
|
||||
|
||||
var jV = Sse.LoadVector128(ptr + j);
|
||||
var kV = Sse.LoadVector128(ptr + k);
|
||||
|
||||
Sse.Store(j + ptr, Sse.Add(jV, kV));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var j = 0; j < l; j++)
|
||||
{
|
||||
var k = j + l;
|
||||
audio[j] = ((audio[k] + audio[j]));
|
||||
}
|
||||
}
|
||||
NumericsHelpers.Add(audio[..l], audio[l..]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < buffers.Length; i++)
|
||||
@@ -452,6 +504,7 @@ namespace Robust.Client.Audio.Midi
|
||||
var timestamp = SequencerTick;
|
||||
var midiEv = (Shared.Audio.Midi.MidiEvent) midiEvent;
|
||||
midiEv.Tick = timestamp;
|
||||
midiEvent.Dispose();
|
||||
SendMidiEvent(midiEv);
|
||||
return 0;
|
||||
}
|
||||
@@ -462,6 +515,7 @@ namespace Robust.Client.Audio.Midi
|
||||
var timestamp = SequencerTick;
|
||||
var midiEv = (Shared.Audio.Midi.MidiEvent) midiEvent;
|
||||
midiEv.Tick = timestamp;
|
||||
midiEvent.Dispose();
|
||||
SendMidiEvent(midiEv);
|
||||
return 0;
|
||||
}
|
||||
@@ -478,16 +532,16 @@ namespace Robust.Client.Audio.Midi
|
||||
lock(_playerStateLock)
|
||||
switch (midiEvent.Type)
|
||||
{
|
||||
// Note On 0x80
|
||||
case 144:
|
||||
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
|
||||
break;
|
||||
|
||||
// Note Off - 0x90
|
||||
// Note Off - 0x80
|
||||
case 128:
|
||||
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
|
||||
break;
|
||||
|
||||
// Note On 0x90
|
||||
case 144:
|
||||
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
|
||||
break;
|
||||
|
||||
// After Touch - 0xA
|
||||
case 160:
|
||||
_synth.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);
|
||||
@@ -519,10 +573,20 @@ namespace Robust.Client.Audio.Midi
|
||||
// Sometimes MIDI files spam these for no good reason and I can't find any info on what they are.
|
||||
case 1:
|
||||
case 5:
|
||||
// MetaEvent -- SetTempo - 0x51
|
||||
case 81:
|
||||
// Already handled by the player.
|
||||
return;
|
||||
// System Messages - 0xF0
|
||||
case 240:
|
||||
return;
|
||||
switch ((byte)midiEvent.Control)
|
||||
{
|
||||
case 11:
|
||||
_synth.AllNotesOff(midiEvent.Channel);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
_midiSawmill.Warning("Unhandled midi event of type {0}", midiEvent.Type, midiEvent);
|
||||
@@ -543,7 +607,7 @@ namespace Robust.Client.Audio.Midi
|
||||
if (Disposed) return;
|
||||
|
||||
var seqEv = (SequencerEvent) midiEvent;
|
||||
seqEv.Dest = _synthRegister;
|
||||
seqEv.Dest = _debugEvents ? _debugRegister : _synthRegister;
|
||||
_sequencer.SendAt(seqEv, time, absolute);
|
||||
}
|
||||
|
||||
@@ -566,10 +630,15 @@ namespace Robust.Client.Audio.Midi
|
||||
void IMidiRenderer.InternalDispose()
|
||||
{
|
||||
Source?.Dispose();
|
||||
_driver?.Dispose();
|
||||
|
||||
// Do NOT dispose of the sequencer after the synth or it'll cause a segfault for some fucking reason.
|
||||
_sequencer?.UnregisterClient(_debugRegister);
|
||||
_sequencer?.UnregisterClient(_synthRegister);
|
||||
_sequencer?.Dispose();
|
||||
|
||||
_synth?.Dispose();
|
||||
_player?.Dispose();
|
||||
_driver?.Dispose();
|
||||
_sequencer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net;
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -8,10 +8,12 @@ using Robust.Client.Utility;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -24,6 +26,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IPlayerManager _playMan = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _entityLookup = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IDiscordRichPresence _discord = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
@@ -96,6 +99,25 @@ namespace Robust.Client
|
||||
_net.ClientDisconnect(reason);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StartSinglePlayer()
|
||||
{
|
||||
DebugTools.Assert(RunLevel < ClientRunLevel.Connecting);
|
||||
DebugTools.Assert(!_net.IsConnected);
|
||||
_playMan.Startup();
|
||||
_playMan.LocalPlayer!.Name = PlayerNameOverride ?? _configManager.GetCVar(CVars.PlayerName);
|
||||
OnRunLevelChanged(ClientRunLevel.SinglePlayerGame);
|
||||
GameStartedSetup();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopSinglePlayer()
|
||||
{
|
||||
DebugTools.Assert(RunLevel == ClientRunLevel.SinglePlayerGame);
|
||||
DebugTools.Assert(!_net.IsConnected);
|
||||
GameStoppedReset();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<RunLevelChangedEventArgs>? RunLevelChanged;
|
||||
|
||||
@@ -132,7 +154,7 @@ namespace Robust.Client
|
||||
var userId = _net.ServerChannel.UserId;
|
||||
_discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString());
|
||||
// start up player management
|
||||
_playMan.Startup(_net.ServerChannel!);
|
||||
_playMan.Startup();
|
||||
|
||||
_playMan.LocalPlayer!.UserId = userId;
|
||||
_playMan.LocalPlayer.Name = userName;
|
||||
@@ -145,16 +167,13 @@ namespace Robust.Client
|
||||
/// receiving states when they join the lobby.
|
||||
/// </summary>
|
||||
/// <param name="session">Session of the player.</param>
|
||||
private void OnPlayerJoinedServer(IPlayerSession session)
|
||||
private void OnPlayerJoinedServer(ICommonSession session)
|
||||
{
|
||||
DebugTools.Assert(RunLevel < ClientRunLevel.Connected);
|
||||
OnRunLevelChanged(ClientRunLevel.Connected);
|
||||
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
GameStartedSetup();
|
||||
|
||||
_timing.ResetSimTime();
|
||||
_timing.Paused = false;
|
||||
PlayerJoinedServer?.Invoke(this, new PlayerEventArgs(session));
|
||||
}
|
||||
|
||||
@@ -162,7 +181,7 @@ namespace Robust.Client
|
||||
/// Player is joining the game
|
||||
/// </summary>
|
||||
/// <param name="session">Session of the player.</param>
|
||||
private void OnPlayerJoinedGame(IPlayerSession session)
|
||||
private void OnPlayerJoinedGame(ICommonSession session)
|
||||
{
|
||||
DebugTools.Assert(RunLevel >= ClientRunLevel.Connected);
|
||||
OnRunLevelChanged(ClientRunLevel.InGame);
|
||||
@@ -189,10 +208,25 @@ namespace Robust.Client
|
||||
PlayerLeaveServer?.Invoke(this, new PlayerEventArgs(_playMan.LocalPlayer?.Session));
|
||||
|
||||
LastDisconnectReason = args.Reason;
|
||||
GameStoppedReset();
|
||||
}
|
||||
|
||||
private void GameStartedSetup()
|
||||
{
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
_entityLookup.Startup();
|
||||
|
||||
_timing.ResetSimTime();
|
||||
_timing.Paused = false;
|
||||
}
|
||||
|
||||
private void GameStoppedReset()
|
||||
{
|
||||
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
|
||||
_gameStates.Reset();
|
||||
_playMan.Shutdown();
|
||||
_entityLookup.Shutdown();
|
||||
_entityManager.Shutdown();
|
||||
_mapManager.Shutdown();
|
||||
_discord.ClearPresence();
|
||||
@@ -249,6 +283,11 @@ namespace Robust.Client
|
||||
/// The client is now in the game, moving around.
|
||||
/// </summary>
|
||||
InGame,
|
||||
|
||||
/// <summary>
|
||||
/// The client is now in singleplayer mode, in-game.
|
||||
/// </summary>
|
||||
SinglePlayerGame,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -259,12 +298,12 @@ namespace Robust.Client
|
||||
/// <summary>
|
||||
/// The session that triggered the event.
|
||||
/// </summary>
|
||||
private IPlayerSession? Session { get; }
|
||||
private ICommonSession? Session { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the class.
|
||||
/// </summary>
|
||||
public PlayerEventArgs(IPlayerSession? session)
|
||||
public PlayerEventArgs(ICommonSession? session)
|
||||
{
|
||||
Session = session;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Debugging;
|
||||
@@ -10,20 +10,26 @@ using Robust.Client.Input;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.Prototypes;
|
||||
using Robust.Client.Reflection;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Client.ViewVariables;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client
|
||||
{
|
||||
@@ -33,24 +39,35 @@ namespace Robust.Client
|
||||
{
|
||||
SharedIoC.RegisterIoC();
|
||||
|
||||
IoCManager.Register<IPrototypeManager, PrototypeManager>();
|
||||
IoCManager.Register<IGameTiming, ClientGameTiming>();
|
||||
IoCManager.Register<IClientGameTiming, ClientGameTiming>();
|
||||
IoCManager.Register<IPrototypeManager, ClientPrototypeManager>();
|
||||
IoCManager.Register<IMapManager, ClientMapManager>();
|
||||
IoCManager.Register<IMapManagerInternal, ClientMapManager>();
|
||||
IoCManager.Register<IClientMapManager, ClientMapManager>();
|
||||
IoCManager.Register<IEntityManager, ClientEntityManager>();
|
||||
IoCManager.Register<IEntityLookup, EntityLookup>();
|
||||
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
|
||||
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
|
||||
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
IoCManager.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
IoCManager.Register<GameController, GameController>();
|
||||
IoCManager.Register<IGameController, GameController>();
|
||||
IoCManager.Register<IGameControllerInternal, GameController>();
|
||||
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
|
||||
IoCManager.Register<IResourceManager, ResourceCache>();
|
||||
IoCManager.Register<IResourceManagerInternal, ResourceCache>();
|
||||
IoCManager.Register<IResourceCache, ResourceCache>();
|
||||
IoCManager.Register<IResourceCacheInternal, ResourceCache>();
|
||||
IoCManager.Register<IClientNetManager, NetManager>();
|
||||
IoCManager.Register<IClientEntityManager, ClientEntityManager>();
|
||||
IoCManager.Register<IEntityNetworkManager, ClientEntityNetworkManager>();
|
||||
IoCManager.Register<IClientEntityManagerInternal, ClientEntityManager>();
|
||||
IoCManager.Register<IEntityNetworkManager, ClientEntityManager>();
|
||||
IoCManager.Register<IClientGameStateManager, ClientGameStateManager>();
|
||||
IoCManager.Register<IBaseClient, BaseClient>();
|
||||
IoCManager.Register<IPlayerManager, PlayerManager>();
|
||||
IoCManager.Register<ISharedPlayerManager, PlayerManager>();
|
||||
IoCManager.Register<IStateManager, StateManager>();
|
||||
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
|
||||
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
|
||||
@@ -58,11 +75,9 @@ namespace Robust.Client
|
||||
IoCManager.Register<IDebugDrawingManager, DebugDrawingManager>();
|
||||
IoCManager.Register<ILightManager, LightManager>();
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IFontManager, FontManager>();
|
||||
IoCManager.Register<IFontManagerInternal, FontManager>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
IoCManager.Register<IAuthManager, AuthManager>();
|
||||
IoCManager.Register<IPhysicsManager, PhysicsManager>();
|
||||
switch (mode)
|
||||
{
|
||||
case GameController.DisplayMode.Headless:
|
||||
@@ -87,8 +102,9 @@ namespace Robust.Client
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
IoCManager.Register<IFontManager, FontManager>();
|
||||
IoCManager.Register<IFontManagerInternal, FontManager>();
|
||||
IoCManager.Register<IEyeManager, EyeManager>();
|
||||
|
||||
IoCManager.Register<IPlacementManager, PlacementManager>();
|
||||
IoCManager.Register<IOverlayManager, OverlayManager>();
|
||||
IoCManager.Register<IOverlayManagerInternal, OverlayManager>();
|
||||
@@ -96,7 +112,6 @@ namespace Robust.Client
|
||||
IoCManager.Register<IViewVariablesManagerInternal, ViewVariablesManager>();
|
||||
IoCManager.Register<IClientConGroupController, ClientConGroupController>();
|
||||
IoCManager.Register<IScriptClient, ScriptClient>();
|
||||
//IoCManager.Register<IXamlCompiler, XamlCompiler>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Robust.Client
|
||||
public bool Launcher { get; }
|
||||
public string? Username { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> CVars { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
|
||||
|
||||
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
|
||||
// Also I don't like spending 100ms parsing command line args. Do you?
|
||||
@@ -31,6 +32,7 @@ namespace Robust.Client
|
||||
var launcher = false;
|
||||
string? username = null;
|
||||
var cvars = new List<(string, string)>();
|
||||
var logLevels = new List<(string, string)>();
|
||||
var mountOptions = new MountOptions();
|
||||
|
||||
using var enumerator = args.GetEnumerator();
|
||||
@@ -124,6 +126,26 @@ namespace Robust.Client
|
||||
|
||||
mountOptions.DirMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--loglevel")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing loglevel sawmill.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var loglevel = enumerator.Current;
|
||||
DebugTools.AssertNotNull(loglevel);
|
||||
var pos = loglevel.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in loglevel.");
|
||||
return false;
|
||||
}
|
||||
|
||||
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
|
||||
}
|
||||
else if (arg == "--help")
|
||||
{
|
||||
PrintHelp();
|
||||
@@ -142,6 +164,7 @@ namespace Robust.Client
|
||||
launcher,
|
||||
username,
|
||||
cvars,
|
||||
logLevels,
|
||||
connectAddress,
|
||||
ss14Address,
|
||||
mountOptions);
|
||||
@@ -162,6 +185,7 @@ Options:
|
||||
--launcher Run in launcher mode (no main menu, auto connect).
|
||||
--username Override username.
|
||||
--cvar Specifies an additional cvar overriding the config file. Syntax is <key>=<value>
|
||||
--loglevel Specifies an additional sawmill log level overriding the default values. Syntax is <key>=<value>
|
||||
--mount-dir Resource directory to mount.
|
||||
--mount-zip Resource zip to mount.
|
||||
--help Display this help text and exit.
|
||||
@@ -175,6 +199,7 @@ Options:
|
||||
bool launcher,
|
||||
string? username,
|
||||
IReadOnlyCollection<(string key, string value)> cVars,
|
||||
IReadOnlyCollection<(string key, string value)> logLevels,
|
||||
string connectAddress, string? ss14Address,
|
||||
MountOptions mountOptions)
|
||||
{
|
||||
@@ -184,6 +209,7 @@ Options:
|
||||
Launcher = launcher;
|
||||
Username = username;
|
||||
CVars = cVars;
|
||||
LogLevels = logLevels;
|
||||
ConnectAddress = connectAddress;
|
||||
Ss14Address = ss14Address;
|
||||
MountOptions = mountOptions;
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Robust.Client.Console
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc cref="IClientConsoleHost" />
|
||||
internal class ClientConsoleHost : ConsoleHost, IClientConsoleHost
|
||||
{
|
||||
@@ -45,11 +45,15 @@ namespace Robust.Client.Console
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
NetManager.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME, HandleConCmdReg);
|
||||
NetManager.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME, HandleConCmdAck);
|
||||
NetManager.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME, ProcessCommand);
|
||||
NetManager.RegisterNetMessage<MsgConCmdReg>(HandleConCmdReg);
|
||||
NetManager.RegisterNetMessage<MsgConCmdAck>(HandleConCmdAck);
|
||||
NetManager.RegisterNetMessage<MsgConCmd>(ProcessCommand);
|
||||
|
||||
Reset();
|
||||
_requestedCommands = false;
|
||||
NetManager.Connected += OnNetworkConnected;
|
||||
|
||||
LoadConsoleCommands();
|
||||
SendServerCommandRequest();
|
||||
LogManager.RootSawmill.AddHandler(new DebugConsoleLogHandler(this));
|
||||
}
|
||||
|
||||
@@ -61,17 +65,6 @@ namespace Robust.Client.Console
|
||||
ExecuteCommand(null, text);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
AvailableCommands.Clear();
|
||||
_requestedCommands = false;
|
||||
NetManager.Connected += OnNetworkConnected;
|
||||
|
||||
LoadConsoleCommands();
|
||||
SendServerCommandRequest();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<AddStringArgs>? AddString;
|
||||
|
||||
@@ -90,6 +83,8 @@ namespace Robust.Client.Console
|
||||
OutputText(text, true, true);
|
||||
}
|
||||
|
||||
public override event ConAnyCommandCallback? AnyCommandExecuted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ExecuteCommand(ICommonSession? session, string command)
|
||||
{
|
||||
@@ -97,7 +92,7 @@ namespace Robust.Client.Console
|
||||
return;
|
||||
|
||||
// echo the command locally
|
||||
WriteError(null, "> " + command);
|
||||
WriteLine(null, "> " + command);
|
||||
|
||||
//Commands are processed locally and then sent to the server to be processed there again.
|
||||
var args = new List<string>();
|
||||
@@ -110,7 +105,11 @@ namespace Robust.Client.Console
|
||||
{
|
||||
var command1 = AvailableCommands[commandName];
|
||||
args.RemoveAt(0);
|
||||
command1.Execute(new ConsoleShell(this, null), command, args.ToArray());
|
||||
var shell = new ConsoleShell(this, null);
|
||||
var cmdArgs = args.ToArray();
|
||||
|
||||
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
|
||||
command1.Execute(shell, command, cmdArgs);
|
||||
}
|
||||
else
|
||||
WriteError(null, "Unknown command: " + commandName);
|
||||
@@ -142,6 +141,9 @@ namespace Robust.Client.Console
|
||||
private void OutputText(string text, bool local, bool error)
|
||||
{
|
||||
AddString?.Invoke(this, new AddStringArgs(text, local, error));
|
||||
|
||||
var level = error ? LogLevel.Warning : LogLevel.Info;
|
||||
Logger.LogS(level, "CON", text);
|
||||
}
|
||||
|
||||
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)
|
||||
|
||||
@@ -12,7 +12,6 @@ using Robust.Client.Input;
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement.ResourceTypes;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
@@ -28,6 +27,7 @@ using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
@@ -78,7 +78,7 @@ namespace Robust.Client.Console.Commands
|
||||
message.Append($"net ID: {registration.NetID}");
|
||||
}
|
||||
|
||||
message.Append($", NSE: {registration.NetworkSynchronizeExistence}, references:");
|
||||
message.Append($", References:");
|
||||
|
||||
shell.WriteLine(message.ToString());
|
||||
|
||||
@@ -281,12 +281,12 @@ namespace Robust.Client.Console.Commands
|
||||
internal class SnapGridGetCell : IConsoleCommand
|
||||
{
|
||||
public string Command => "sggcell";
|
||||
public string Help => "sggcell <gridID> <vector2i> [offset]\nThat vector2i param is in the form x<int>,y<int>.";
|
||||
public string Help => "sggcell <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.";
|
||||
public string Description => "Lists entities on a snap grid cell.";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2 && args.Length != 3)
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
@@ -294,7 +294,6 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
string gridId = args[0];
|
||||
string indices = args[1];
|
||||
string offset = args.Length == 3 ? args[2] : "Center";
|
||||
|
||||
if (!int.TryParse(args[0], out var id))
|
||||
{
|
||||
@@ -308,29 +307,17 @@ namespace Robust.Client.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
SnapGridOffset selectedOffset;
|
||||
if (Enum.IsDefined(typeof(SnapGridOffset), offset))
|
||||
{
|
||||
selectedOffset = (SnapGridOffset)Enum.Parse(typeof(SnapGridOffset), offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteError("given offset type is not defined");
|
||||
return;
|
||||
}
|
||||
|
||||
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
if (mapMan.GridExists(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))))
|
||||
{
|
||||
foreach (var entity in
|
||||
mapMan.GetGrid(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))).GetSnapGridCell(
|
||||
mapMan.GetGrid(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))).GetAnchoredEntities(
|
||||
new Vector2i(
|
||||
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
|
||||
int.Parse(indices.Split(',')[1], CultureInfo.InvariantCulture)),
|
||||
selectedOffset))
|
||||
int.Parse(indices.Split(',')[1], CultureInfo.InvariantCulture))))
|
||||
{
|
||||
shell.WriteLine(entity.Owner.Uid.ToString());
|
||||
shell.WriteLine(entity.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -473,21 +460,28 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var root = IoCManager.Resolve<IUserInterfaceManager>().RootControl;
|
||||
var uiMgr = IoCManager.Resolve<IUserInterfaceManager>();
|
||||
var res = IoCManager.Resolve<IResourceManager>();
|
||||
|
||||
using (var stream = res.UserData.Create(new ResourcePath("/guidump.txt")))
|
||||
using (var writer = new StreamWriter(stream, EncodingHelpers.UTF8))
|
||||
{
|
||||
_writeNode(root, 0, writer);
|
||||
foreach (var root in uiMgr.AllRoots)
|
||||
{
|
||||
writer.WriteLine($"ROOT: {root}");
|
||||
_writeNode(root, 0, writer);
|
||||
writer.WriteLine("---------------");
|
||||
}
|
||||
}
|
||||
|
||||
shell.WriteLine("Saved guidump");
|
||||
}
|
||||
|
||||
private static void _writeNode(Control control, int indents, TextWriter writer)
|
||||
{
|
||||
var indentation = new string(' ', indents * 2);
|
||||
writer.WriteLine("{0}{1}", indentation, control);
|
||||
foreach (var (key, value) in _propertyValuesFor(control))
|
||||
foreach (var (key, value) in PropertyValuesFor(control))
|
||||
{
|
||||
writer.WriteLine("{2} * {0}: {1}", key, value, indentation);
|
||||
}
|
||||
@@ -498,14 +492,14 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
private static List<(string, string)> _propertyValuesFor(Control control)
|
||||
internal static List<(string, string)> PropertyValuesFor(Control control)
|
||||
{
|
||||
var members = new List<(string, string)>();
|
||||
var type = control.GetType();
|
||||
|
||||
foreach (var fieldInfo in type.GetAllFields())
|
||||
{
|
||||
if (fieldInfo.GetCustomAttribute<ViewVariablesAttribute>() == null)
|
||||
if (!ViewVariablesUtility.TryGetViewVariablesAccess(fieldInfo, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -515,7 +509,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
foreach (var propertyInfo in type.GetAllProperties())
|
||||
{
|
||||
if (propertyInfo.GetCustomAttribute<ViewVariablesAttribute>() == null)
|
||||
if (!ViewVariablesUtility.TryGetViewVariablesAccess(propertyInfo, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -542,13 +536,16 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var window = new SS14Window { CustomMinimumSize = (500, 400)};
|
||||
var window = new SS14Window { MinSize = (500, 400)};
|
||||
var tabContainer = new TabContainer();
|
||||
window.Contents.AddChild(tabContainer);
|
||||
var scroll = new ScrollContainer();
|
||||
tabContainer.AddChild(scroll);
|
||||
//scroll.SetAnchorAndMarginPreset(Control.LayoutPreset.Wide);
|
||||
var vBox = new VBoxContainer();
|
||||
var vBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
scroll.AddChild(vBox);
|
||||
|
||||
var progressBar = new ProgressBar { MaxValue = 10, Value = 5 };
|
||||
@@ -562,7 +559,7 @@ namespace Robust.Client.Console.Commands
|
||||
optionButton.OnItemSelected += eventArgs => optionButton.SelectId(eventArgs.Id);
|
||||
vBox.AddChild(optionButton);
|
||||
|
||||
var tree = new Tree { SizeFlagsVertical = Control.SizeFlags.FillExpand };
|
||||
var tree = new Tree { VerticalExpand = true };
|
||||
var root = tree.CreateItem();
|
||||
root.Text = "Honk!";
|
||||
var child = tree.CreateItem();
|
||||
@@ -599,14 +596,17 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
grid.AddChild(new Button
|
||||
{
|
||||
CustomMinimumSize = (50, 50),
|
||||
MinSize = (50, 50),
|
||||
Text = $"{x}, {y}"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var group = new ButtonGroup();
|
||||
var vBoxRadioButtons = new VBoxContainer();
|
||||
var vBoxRadioButtons = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
vBoxRadioButtons.AddChild(new Button
|
||||
@@ -622,8 +622,9 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
TabContainer.SetTabTitle(vBoxRadioButtons, "Radio buttons!!");
|
||||
|
||||
tabContainer.AddChild(new VBoxContainer
|
||||
tabContainer.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Name = "Slider",
|
||||
Children =
|
||||
{
|
||||
@@ -631,6 +632,30 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
});
|
||||
|
||||
tabContainer.AddChild(new SplitContainer
|
||||
{
|
||||
Orientation = SplitContainer.SplitOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.Red},
|
||||
Children =
|
||||
{
|
||||
new Label{ Text = "FOOBARBAZ"},
|
||||
}
|
||||
},
|
||||
new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.Blue},
|
||||
Children =
|
||||
{
|
||||
new Label{ Text = "FOOBARBAZ"},
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
window.OpenCentered();
|
||||
}
|
||||
}
|
||||
@@ -654,10 +679,10 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Gets the system clipboard";
|
||||
public string Help => "getclipboard";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<IClipboardManager>();
|
||||
shell.WriteLine(mgr.GetText());
|
||||
shell.WriteLine(await mgr.GetText());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -852,7 +877,7 @@ namespace Robust.Client.Console.Commands
|
||||
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
|
||||
var chunk = internalGrid.GetChunk(chunkIndex);
|
||||
|
||||
shell.WriteLine($"worldBounds: {chunk.CalcWorldBounds()} localBounds: {chunk.CalcLocalBounds()}");
|
||||
shell.WriteLine($"worldBounds: {chunk.CalcWorldAABB()} localBounds: {chunk.CalcLocalBounds()}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1073,15 +1098,8 @@ namespace Robust.Client.Console.Commands
|
||||
var key = (Keyboard.Key) parsed!;
|
||||
|
||||
var name = clyde.GetKeyName(key);
|
||||
var scanCode = clyde.GetKeyScanCode(key);
|
||||
var nameScanCode = clyde.GetKeyNameScanCode(scanCode);
|
||||
|
||||
shell.WriteLine($"name: '{name}' scan code: '{scanCode}' name via scan code: '{nameScanCode}'");
|
||||
}
|
||||
else if (int.TryParse(args[0], out var scanCode))
|
||||
{
|
||||
var nameScanCode = clyde.GetKeyNameScanCode(scanCode);
|
||||
shell.WriteLine($"name via scan code: '{nameScanCode}'");
|
||||
shell.WriteLine($"name: '{name}' ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
Robust.Client/Console/Commands/DebugAnchoredCommand.cs
Normal file
19
Robust.Client/Console/Commands/DebugAnchoredCommand.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
#if DEBUG
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public sealed class DebugAnchoredCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showanchored";
|
||||
public string Description => $"Shows anchored entities on a particular tile";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<DebugAnchoringSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
17
Robust.Client/Console/Commands/GridChunkBBCommand.cs
Normal file
17
Robust.Client/Console/Commands/GridChunkBBCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public class GridChunkBBCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showchunkbb";
|
||||
public string Description => "Displays chunk bounds for the purposes of rendering";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<GridChunkBoundsDebugSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Robust.Client/Console/Commands/LightBBCommand.cs
Normal file
19
Robust.Client/Console/Commands/LightBBCommand.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
#if DEBUG
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
internal sealed class LightDebugCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "lightbb";
|
||||
public string Description => "Toggles whether to show light bounding boxes";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<DebugLightTreeSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
72
Robust.Client/Console/Commands/MonitorCommands.cs
Normal file
72
Robust.Client/Console/Commands/MonitorCommands.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class LsMonitorCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "lsmonitor";
|
||||
public string Description => "";
|
||||
public string Help => "";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
|
||||
foreach (var monitor in clyde.EnumerateMonitors())
|
||||
{
|
||||
shell.WriteLine(
|
||||
$"[{monitor.Id}] {monitor.Name}: {monitor.Size.X}x{monitor.Size.Y}@{monitor.RefreshRate}Hz");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class MonitorInfoCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "monitorinfo";
|
||||
public string Description => "";
|
||||
public string Help => "Usage: monitorinfo <id>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
{
|
||||
shell.WriteError("Expected one argument.");
|
||||
return;
|
||||
}
|
||||
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
var monitor = clyde.EnumerateMonitors().Single(c => c.Id == int.Parse(args[0]));
|
||||
|
||||
shell.WriteLine($"{monitor.Id}: {monitor.Name}");
|
||||
shell.WriteLine($"Video modes:");
|
||||
|
||||
foreach (var mode in monitor.VideoModes)
|
||||
{
|
||||
shell.WriteLine($" {mode.Width}x{mode.Height} {mode.RefreshRate} Hz {mode.RedBits}/{mode.GreenBits}/{mode.BlueBits}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class SetMonitorCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "setmonitor";
|
||||
public string Description => "";
|
||||
public string Help => "Usage: setmonitor <id>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
|
||||
var id = int.Parse(args[0]);
|
||||
var monitor = clyde.EnumerateMonitors().Single(m => m.Id == id);
|
||||
clyde.SetWindowMonitor(monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Robust.Client/Console/Commands/PhysicsOverlayCommands.cs
Normal file
41
Robust.Client/Console/Commands/PhysicsOverlayCommands.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public sealed class PhysicsOverlayCommands : IConsoleCommand
|
||||
{
|
||||
public string Command => "physics";
|
||||
public string Description => $"{Command} <contactnormals / contactpoints / shapes>";
|
||||
public string Help => $"{Command} <overlay>";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine($"Invalid number of args supplied");
|
||||
return;
|
||||
}
|
||||
|
||||
var system = EntitySystem.Get<DebugPhysicsSystem>();
|
||||
|
||||
switch (args[0])
|
||||
{
|
||||
case "contactnormals":
|
||||
system.Flags ^= PhysicsDebugFlags.ContactNormals;
|
||||
break;
|
||||
case "contactpoints":
|
||||
system.Flags ^= PhysicsDebugFlags.ContactPoints;
|
||||
break;
|
||||
case "shapes":
|
||||
system.Flags ^= PhysicsDebugFlags.Shapes;
|
||||
break;
|
||||
default:
|
||||
shell.WriteLine($"{args[0]} is not a recognised overlay");
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
class QuitCommand : IConsoleCommand
|
||||
class HardQuitCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "quit";
|
||||
public string Command => "hardquit";
|
||||
public string Description => "Kills the game client instantly.";
|
||||
public string Help => "Kills the game client instantly, leaving no traces. No telling the server goodbye";
|
||||
|
||||
@@ -14,4 +15,16 @@ namespace Robust.Client.Console.Commands
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
class QuitCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "quit";
|
||||
public string Description => "Shuts down the game client gracefully.";
|
||||
public string Help => "Properly shuts down the game client, notifying the connected server and such.";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
IoCManager.Resolve<IGameController>().Shutdown("quit command used");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
Robust.Client/Console/Commands/ReloadLocalizationsCommand.cs
Normal file
20
Robust.Client/Console/Commands/ReloadLocalizationsCommand.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
internal sealed class ReloadLocalizationsCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "rldloc";
|
||||
public string Description => "Reloads localization (client & server)";
|
||||
public string Help => "Usage: rldloc";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
IoCManager.Resolve<ILocalizationManager>().ReloadLocalizations();
|
||||
|
||||
shell.RemoteExecuteCommand("sudo rldloc");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
@@ -41,7 +40,7 @@ namespace Robust.Client.Console.Commands
|
||||
var mgr = IoCManager.Resolve<IScriptClient>();
|
||||
if (!mgr.CanScript)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("You do not have server side scripting permission."));
|
||||
shell.WriteError("You do not have server side scripting permission.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
17
Robust.Client/Console/Commands/VelocitiesCommand.cs
Normal file
17
Robust.Client/Console/Commands/VelocitiesCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public class VelocitiesCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showvelocities";
|
||||
public string Description => "Displays your angular and linear velocities";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<VelocityDebugSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,6 @@ namespace Robust.Client.Console
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the console to a post-initialized state.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
|
||||
event EventHandler<AddStringArgs> AddString;
|
||||
event EventHandler<AddFormattedMessageArgs> AddFormatted;
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ namespace Robust.Client.Console
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_netManager.RegisterNetMessage<MsgScriptStop>(MsgScriptStop.NAME);
|
||||
_netManager.RegisterNetMessage<MsgScriptEval>(MsgScriptEval.NAME);
|
||||
_netManager.RegisterNetMessage<MsgScriptStart>(MsgScriptStart.NAME);
|
||||
_netManager.RegisterNetMessage<MsgScriptResponse>(MsgScriptResponse.NAME, ReceiveScriptResponse);
|
||||
_netManager.RegisterNetMessage<MsgScriptStartAck>(MsgScriptStartAck.NAME, ReceiveScriptStartAckResponse);
|
||||
_netManager.RegisterNetMessage<MsgScriptStop>();
|
||||
_netManager.RegisterNetMessage<MsgScriptEval>();
|
||||
_netManager.RegisterNetMessage<MsgScriptStart>();
|
||||
_netManager.RegisterNetMessage<MsgScriptResponse>(ReceiveScriptResponse);
|
||||
_netManager.RegisterNetMessage<MsgScriptStartAck>(ReceiveScriptStartAckResponse);
|
||||
}
|
||||
|
||||
private void ReceiveScriptStartAckResponse(MsgScriptStartAck message)
|
||||
@@ -30,7 +30,8 @@ namespace Robust.Client.Console
|
||||
|
||||
var console = new ScriptConsoleServer(this, session);
|
||||
_activeConsoles.Add(session, console);
|
||||
console.Open();
|
||||
// FIXME: When this is Open(), resizing the window will cause its position to get NaN'd.
|
||||
console.OpenCentered();
|
||||
}
|
||||
|
||||
private void ReceiveScriptResponse(MsgScriptResponse message)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#if CLIENT_SCRIPTING
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.CodeAnalysis;
|
||||
@@ -11,11 +13,11 @@ using Microsoft.CodeAnalysis.Text;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Scripting;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
|
||||
#nullable enable
|
||||
|
||||
@@ -36,16 +38,18 @@ namespace Robust.Client.Console
|
||||
private readonly ScriptGlobals _globals;
|
||||
private ScriptState? _state;
|
||||
|
||||
private (string[] imports, string code)? _autoImportRepeatBuffer;
|
||||
|
||||
public ScriptConsoleClient()
|
||||
{
|
||||
Title = Loc.GetString("Robust C# Interactive (CLIENT)");
|
||||
Title = "Robust C# Interactive (CLIENT)";
|
||||
ScriptInstanceShared.InitDummy();
|
||||
|
||||
_globals = new ScriptGlobalsImpl(this);
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
OutputPanel.AddText(Loc.GetString(@"Robust C# interactive console (CLIENT)."));
|
||||
OutputPanel.AddText("Robust C# interactive console (CLIENT).");
|
||||
OutputPanel.AddText(">");
|
||||
}
|
||||
|
||||
@@ -54,38 +58,56 @@ namespace Robust.Client.Console
|
||||
var code = InputBar.Text;
|
||||
InputBar.Clear();
|
||||
|
||||
// Remove > or . at the end of the output panel.
|
||||
OutputPanel.RemoveEntry(^1);
|
||||
|
||||
_inputBuffer.AppendLine(code);
|
||||
_linesEntered += 1;
|
||||
|
||||
var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(_inputBuffer.ToString()), ScriptInstanceShared.ParseOptions);
|
||||
|
||||
if (!SyntaxFactory.IsCompleteSubmission(tree))
|
||||
if (_autoImportRepeatBuffer.HasValue && code == "y")
|
||||
{
|
||||
if (_linesEntered == 1)
|
||||
var (imports, repeatCode) = _autoImportRepeatBuffer.Value;
|
||||
var sb = new StringBuilder();
|
||||
foreach (var import in imports)
|
||||
{
|
||||
OutputPanel.AddText($"> {code}");
|
||||
sb.AppendFormat("using {0};\n", import);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputPanel.AddText($". {code}");
|
||||
}
|
||||
OutputPanel.AddText(".");
|
||||
return;
|
||||
|
||||
sb.Append(repeatCode);
|
||||
|
||||
code = sb.ToString();
|
||||
}
|
||||
|
||||
code = _inputBuffer.ToString().Trim();
|
||||
|
||||
// Remove echo of partial submission from the output panel.
|
||||
for (var i = 1; i < _linesEntered; i++)
|
||||
else
|
||||
{
|
||||
// Remove > or . at the end of the output panel.
|
||||
OutputPanel.RemoveEntry(^1);
|
||||
}
|
||||
|
||||
_inputBuffer.Clear();
|
||||
_linesEntered = 0;
|
||||
_inputBuffer.AppendLine(code);
|
||||
_linesEntered += 1;
|
||||
|
||||
var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(_inputBuffer.ToString()),
|
||||
ScriptInstanceShared.ParseOptions);
|
||||
|
||||
if (!SyntaxFactory.IsCompleteSubmission(tree))
|
||||
{
|
||||
if (_linesEntered == 1)
|
||||
{
|
||||
OutputPanel.AddText($"> {code}");
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputPanel.AddText($". {code}");
|
||||
}
|
||||
|
||||
OutputPanel.AddText(".");
|
||||
return;
|
||||
}
|
||||
|
||||
code = _inputBuffer.ToString().Trim();
|
||||
|
||||
// Remove echo of partial submission from the output panel.
|
||||
for (var i = 1; i < _linesEntered; i++)
|
||||
{
|
||||
OutputPanel.RemoveEntry(^1);
|
||||
}
|
||||
|
||||
_inputBuffer.Clear();
|
||||
_linesEntered = 0;
|
||||
}
|
||||
|
||||
Script newScript;
|
||||
|
||||
@@ -95,7 +117,7 @@ namespace Robust.Client.Console
|
||||
}
|
||||
else
|
||||
{
|
||||
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager);
|
||||
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager).AddReferences(typeof(Image).Assembly);
|
||||
newScript = CSharpScript.Create(code, options, typeof(ScriptGlobals));
|
||||
}
|
||||
|
||||
@@ -135,6 +157,8 @@ namespace Robust.Client.Console
|
||||
|
||||
OutputPanel.AddMessage(msg);
|
||||
OutputPanel.AddText(">");
|
||||
|
||||
PromptAutoImports(e.Diagnostics, code);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -148,13 +172,23 @@ namespace Robust.Client.Console
|
||||
else if (ScriptInstanceShared.HasReturnValue(newScript))
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(CSharpObjectFormatter.Instance.FormatObject(_state.ReturnValue));
|
||||
msg.AddText(ScriptInstanceShared.SafeFormat(_state.ReturnValue));
|
||||
OutputPanel.AddMessage(msg);
|
||||
}
|
||||
|
||||
OutputPanel.AddText(">");
|
||||
}
|
||||
|
||||
private void PromptAutoImports(IEnumerable<Diagnostic> diags, string code)
|
||||
{
|
||||
if (!ScriptInstanceShared.CalcAutoImports(_reflectionManager, diags, out var found))
|
||||
return;
|
||||
|
||||
OutputPanel.AddText($"Auto-import {string.Join(", ", found)} (enter 'y')?");
|
||||
|
||||
_autoImportRepeatBuffer = (found.ToArray(), code);
|
||||
}
|
||||
|
||||
private sealed class ScriptGlobalsImpl : ScriptGlobals
|
||||
{
|
||||
private readonly ScriptConsoleClient _owner;
|
||||
@@ -180,7 +214,7 @@ namespace Robust.Client.Console
|
||||
|
||||
public override void show(object obj)
|
||||
{
|
||||
write(CSharpObjectFormatter.Instance.FormatObject(obj));
|
||||
write(ScriptInstanceShared.SafeFormat(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,10 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Scripting;
|
||||
using Robust.Shared.Timing;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.Console
|
||||
{
|
||||
@@ -20,9 +19,7 @@ namespace Robust.Client.Console
|
||||
{
|
||||
private readonly IReflectionManager _reflectionManager;
|
||||
|
||||
protected override Vector2? CustomSize => (300, 300);
|
||||
|
||||
private readonly VBoxContainer _watchesVBox;
|
||||
private readonly BoxContainer _watchesVBox;
|
||||
private readonly LineEdit _addWatchEdit;
|
||||
private readonly Button _addWatchButton;
|
||||
|
||||
@@ -33,29 +30,32 @@ namespace Robust.Client.Console
|
||||
|
||||
ScriptInstanceShared.InitDummy();
|
||||
|
||||
Title = Loc.GetString("Watch Window");
|
||||
Title = "Watch Window";
|
||||
|
||||
var mainVBox = new VBoxContainer
|
||||
var mainVBox = new BoxContainer
|
||||
{
|
||||
CustomMinimumSize = (500, 300),
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
MinSize = (500, 300),
|
||||
Children =
|
||||
{
|
||||
(_watchesVBox = new VBoxContainer
|
||||
(_watchesVBox = new BoxContainer
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.FillExpand
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
VerticalExpand = true
|
||||
}),
|
||||
new HBoxContainer
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(_addWatchEdit = new HistoryLineEdit
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
PlaceHolder = Loc.GetString("Add watch (C# interactive)")
|
||||
HorizontalExpand = true,
|
||||
PlaceHolder = "Add watch (C# interactive)"
|
||||
}),
|
||||
(_addWatchButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("Add")
|
||||
Text = "Add"
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,8 @@ namespace Robust.Client.Console
|
||||
_addWatchEdit.OnTextEntered += _ => AddWatch();
|
||||
|
||||
Contents.AddChild(mainVBox);
|
||||
|
||||
SetSize = (300, 300);
|
||||
}
|
||||
|
||||
private void AddWatch()
|
||||
@@ -107,18 +109,19 @@ namespace Robust.Client.Console
|
||||
Button delButton;
|
||||
_runner = runner;
|
||||
|
||||
AddChild(new HBoxContainer
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(_outputLabel = new Label
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
HorizontalExpand = true,
|
||||
ClipText = true
|
||||
}),
|
||||
(delButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("Remove")
|
||||
Text = "Remove"
|
||||
}),
|
||||
}
|
||||
});
|
||||
@@ -168,17 +171,18 @@ namespace Robust.Client.Console
|
||||
public CompilationErrorControl(string message)
|
||||
{
|
||||
Button delButton;
|
||||
AddChild(new HBoxContainer
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = message,
|
||||
ClipText = true,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
HorizontalExpand = true
|
||||
},
|
||||
(delButton = new Button {Text = Loc.GetString("Remove")})
|
||||
(delButton = new Button {Text = "Remove"})
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user