You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

292 lines
6.0 KiB

  1. //System Imports
  2. const childProcess = require('child_process');
  3. //Imports
  4. const objects = require('../objects/objects');
  5. const connections = require('../security/connections');
  6. const { mapList } = require('./mapManager');
  7. const { registerCallback } = require('./atlas/registerCallback');
  8. //Internals
  9. const threads = [];
  10. const listenersOnZoneIdle = [];
  11. //Helpers
  12. const getThreadFromName = name => {
  13. return threads.find(t => t.name === name);
  14. };
  15. const getThreadFromId = threadId => {
  16. return threads.find(t => t.id === threadId);
  17. };
  18. const getPlayerCountInThread = async thread => {
  19. const { playerCount } = await new Promise(res => {
  20. const cb = registerCallback(res);
  21. thread.worker.send({
  22. method: 'getPlayerCount',
  23. args: {
  24. callbackId: cb
  25. }
  26. });
  27. });
  28. return playerCount;
  29. };
  30. const messageHandlers = {
  31. onReady: function (thread) {
  32. thread.worker.send({
  33. method: 'init',
  34. args: {
  35. zoneName: thread.name,
  36. zoneId: thread.id,
  37. path: thread.path
  38. }
  39. });
  40. },
  41. onInitialized: function (thread) {
  42. thread.isReady = true;
  43. thread.cbOnInitialized(thread);
  44. delete thread.cbOnInitialized;
  45. delete thread.promise;
  46. },
  47. event: function (thread, message) {
  48. objects.sendEvent(message, thread);
  49. },
  50. events: function (thread, message) {
  51. objects.sendEvents(message, thread);
  52. },
  53. object: function (thread, message) {
  54. objects.updateObject(message);
  55. },
  56. track: function (thread, message) {
  57. let player = objects.objects.find(o => o.id === message.serverId);
  58. if (!player)
  59. return;
  60. player.auth.gaTracker.track(message.obj);
  61. },
  62. callDifferentThread: function (thread, message) {
  63. let obj = connections.players.find(p => (p.name === message.playerName));
  64. if (!obj)
  65. return;
  66. let newThread = getThreadFromName(obj.zoneName);
  67. if (!newThread)
  68. return;
  69. newThread.worker.send({
  70. module: message.data.module,
  71. method: message.data.method,
  72. args: message.data.args
  73. });
  74. },
  75. rezone: async function (thread, message) {
  76. const { args: { obj, newZone, keepPos = true } } = message;
  77. if (thread.instanced && (await getPlayerCountInThread(thread)) === 0) {
  78. thread.worker.kill();
  79. threads.spliceWhere(t => t === thread);
  80. }
  81. //When messages are sent from map threads, they have an id (id of the object in the map thread)
  82. // as well as a serverId (id of the object in the main thread)
  83. const serverId = obj.serverId;
  84. obj.id = serverId;
  85. obj.destroyed = false;
  86. const serverObj = objects.objects.find(o => o.id === obj.id);
  87. const mapExists = mapList.some(m => m.name === newZone);
  88. if (mapExists) {
  89. serverObj.zoneName = newZone;
  90. obj.zoneName = newZone;
  91. } else {
  92. obj.zoneName = clientConfig.config.defaultZone;
  93. serverObj.zoneName = clientConfig.config.defaultZone;
  94. }
  95. delete serverObj.zoneId;
  96. delete obj.zoneId;
  97. const isRezone = true;
  98. await atlas.addObject(obj, keepPos, isRezone);
  99. },
  100. onZoneIdle: function (thread) {
  101. listenersOnZoneIdle.forEach(l => l(thread));
  102. }
  103. };
  104. const onMessage = (thread, message) => {
  105. if (message.module) {
  106. try {
  107. global[message.module][message.method](message);
  108. } catch (e) {
  109. /* eslint-disable-next-line no-console */
  110. console.log('No global method found', message.module, message.method);
  111. process.exit();
  112. }
  113. } else if (message.event === 'onCrashed') {
  114. thread.worker.kill();
  115. process.exit();
  116. } else
  117. messageHandlers[message.method](thread, message);
  118. };
  119. const spawnThread = async ({ name, path, instanced }) => {
  120. let cbOnInitialized;
  121. const promise = new Promise(resolveOnReady => {
  122. cbOnInitialized = resolveOnReady;
  123. });
  124. const worker = childProcess.fork('./world/worker', [name]);
  125. const id = instanced ? _.getGuid() : name;
  126. const thread = {
  127. id,
  128. name,
  129. instanced,
  130. path,
  131. worker,
  132. isReady: false,
  133. promise,
  134. cbOnInitialized
  135. };
  136. worker.on('message', onMessage.bind(null, thread));
  137. threads.push(thread);
  138. return promise;
  139. };
  140. const doesThreadExist = ({ zoneName, zoneId }) => {
  141. let map = mapList.find(m => m.name === zoneName);
  142. if (!map)
  143. map = mapList.find(m => m.name === clientConfig.config.defaultZone);
  144. const exists = threads.some(t => t.id === zoneId && t.name === zoneName);
  145. if (exists)
  146. return true;
  147. if (map.instanced)
  148. return false;
  149. const thread = getThreadFromName(map.name);
  150. return !!thread;
  151. };
  152. const getThread = async ({ zoneName, zoneId }) => {
  153. const result = {
  154. resetObjPosition: false,
  155. thread: null
  156. };
  157. let map = mapList.find(m => m.name === zoneName);
  158. if (!map)
  159. map = mapList.find(m => m.name === clientConfig.config.defaultZone);
  160. let thread = threads.find(t => t.id === zoneId && t.name === zoneName);
  161. if (!thread) {
  162. if (map.instanced) {
  163. result.resetObjPosition = true;
  164. thread = await spawnThread(map);
  165. } else
  166. thread = getThreadFromName(map.name);
  167. }
  168. if (!thread.isReady)
  169. await thread.promise;
  170. result.thread = thread;
  171. return result;
  172. };
  173. const killThread = thread => {
  174. thread.worker.kill();
  175. threads.spliceWhere(t => t === thread);
  176. };
  177. const killThreadIfEmpty = async thread => {
  178. const playerCount = await getPlayerCountInThread(thread);
  179. if (playerCount === 0)
  180. killThread(thread);
  181. };
  182. const spawnMapThreads = async () => {
  183. const promises = mapList
  184. .filter(m => !m.disabled && !m.instanced)
  185. .map(m => spawnThread(m));
  186. await Promise.all(promises);
  187. };
  188. const sendMessageToThread = ({ threadId, msg }) => {
  189. const thread = threads.find(t => t.id === threadId);
  190. if (thread)
  191. thread.worker.send(msg);
  192. };
  193. const messageAllThreads = message => {
  194. threads.forEach(t => t.worker.send(message));
  195. };
  196. const returnWhenThreadsIdle = async () => {
  197. return new Promise(res => {
  198. let doneCount = 0;
  199. const onZoneIdle = thread => {
  200. doneCount++;
  201. if (doneCount.length < threads.length)
  202. return;
  203. listenersOnZoneIdle.spliceWhere(l => l === onZoneIdle);
  204. res();
  205. };
  206. listenersOnZoneIdle.push(onZoneIdle);
  207. threads.forEach(t => {
  208. t.worker.send({
  209. method: 'notifyOnceIdle'
  210. });
  211. });
  212. });
  213. };
  214. //Exports
  215. module.exports = {
  216. getThread,
  217. killThread,
  218. getThreadFromId,
  219. doesThreadExist,
  220. spawnMapThreads,
  221. messageAllThreads,
  222. killThreadIfEmpty,
  223. sendMessageToThread,
  224. returnWhenThreadsIdle,
  225. getPlayerCountInThread
  226. };