void UpdateAliasesLink(int index) { string hostname = panel.HostNames[index].Value; string address = panel.Addresses[index].Value; if (!string.IsNullOrWhiteSpace(hostname) && !string.IsNullOrWhiteSpace(address)) { var newHostAlias = new HostAlias(hostname, address); if (HostAliases[index] == newHostAlias) { return; } HostAliases[index] = newHostAlias; Logger.Info($"{this}: Adding pair {hostname} -> {address} to resolver list."); ConnectionUtils.GlobalResolver[hostname] = address; ModuleListPanel.UpdateSimpleConfigurationSettings(); } else if (HostAliases[index] != default) { ConnectionUtils.GlobalResolver.Remove(HostAliases[index].Hostname); Logger.Info($"{this}: Removing {HostAliases[index].Hostname} from resolver list."); HostAliases[index] = default; ModuleListPanel.UpdateSimpleConfigurationSettings(); } }
static async ValueTask <string[]> GetModulesAsync() { using (SemaphoreSlim signal = new SemaphoreSlim(0)) { string[] result = Array.Empty <string>(); GameThread.Post(() => { try { IConfiguration[] configurations = ModuleDatas.Select(data => data.Configuration).ToArray(); result = configurations.Select(JsonConvert.SerializeObject).ToArray(); } catch (JsonException e) { Logger.Error("ControllerService: Unexpected JSON exception in GetModules", e); } catch (Exception e) { Logger.Error("ControllerService: Unexpected exception in GetModules", e); } finally { signal.Release(); } }); if (!await signal.WaitAsync(DefaultTimeoutInMs)) { Logger.Error("Timeout in GetModules"); } return(result); } }
public InternalResourceManager() { string robotsFile = UnityEngine.Resources.Load <TextAsset>("Package/iviz/resources")?.text; if (string.IsNullOrEmpty(robotsFile)) { Logger.Warn($"{this}: Empty resource file!"); robotDescriptions = new Dictionary <string, string>().AsReadOnly(); return; } Dictionary <string, string> robots = JsonConvert.DeserializeObject <Dictionary <string, string> >(robotsFile); Dictionary <string, string> tmpRobotDescriptions = new Dictionary <string, string>(); foreach (var pair in robots) { string robotDescription = UnityEngine.Resources.Load <TextAsset>("Package/iviz/robots/" + pair.Value)?.text; if (string.IsNullOrEmpty(robotDescription)) { Logger.Info($"{this}: Empty or null description file {pair.Value}!"); continue; } tmpRobotDescriptions[pair.Key] = robotDescription; } robotDescriptions = tmpRobotDescriptions.AsReadOnly(); }
void Initialize() { ThreadPool.SetMinThreads(25, 20); try { Logger.Debug("Hololens Manager: Initializing!"); Settings.SettingsManager = this; Settings.ScreenshotManager = new HololensScreenshotManager(); if (!UnityEngine.Application.isEditor) { floorHelper.gameObject.SetActive(false); } floorFrame.OkClicked += StartWorld; ModuleListPanel.Instance.MenuDialog = markerMenu; //StartLog(); StartRosConnection(); StartHandMenu(); //StartPalms(); StartOriginPlaceMode(); } catch (Exception e) { Debug.Log(e); Logger.Error("Error initializing Hololens Manager", e); } }
static async Task RemoveRobotAsync([NotNull] UpdateRobot srv) { string id = srv.Request.Id; if (id.Length == 0) { srv.Response.Success = false; srv.Response.Message = "EE Id field is empty"; return; } ModuleData moduleData; if ((moduleData = ModuleDatas.FirstOrDefault(data => data.Configuration.Id == id)) == null) { srv.Response.Success = true; srv.Response.Message = $"WW There is no node with name '{id}'"; return; } if (moduleData.ModuleType != ModuleType.Robot) { srv.Response.Success = false; srv.Response.Message = $"EE Another module of the same id already exists, but it has type {moduleData.ModuleType}"; return; } using (SemaphoreSlim signal = new SemaphoreSlim(0)) { GameThread.Post(() => { try { Logger.Info($"ControllerService: Removing robot"); ModuleListPanel.Instance.RemoveModule(moduleData); } catch (Exception e) { srv.Response.Success = false; srv.Response.Message = $"EE An exception was raised: {e.Message}"; } finally { signal.Release(); } }); if (!await signal.WaitAsync(DefaultTimeoutInMs)) { srv.Response.Success = false; srv.Response.Message = "EE Request timed out!"; return; } if (string.IsNullOrEmpty(srv.Response.Message)) { srv.Response.Success = true; } } }
static async ValueTask <(bool success, string message)> TrySetFixedFrame(string id) { (bool success, string message)result = default; using (SemaphoreSlim signal = new SemaphoreSlim(0)) { GameThread.Post(() => { try { TfListener.FixedFrameId = id; } finally { signal.Release(); } }); if (!await signal.WaitAsync(DefaultTimeoutInMs)) { Logger.Error("ControllerService: Unexpected timeout in TrySetFixedFrame"); return(result); } } return(true, ""); }
public override void UpdateConfiguration(string configAsJson, IEnumerable <string> fields) { var config = JsonConvert.DeserializeObject <MarkerConfiguration>(configAsJson); foreach (string field in fields) { switch (field) { case nameof(MarkerConfiguration.Visible): listener.Visible = config.Visible; break; case nameof(MarkerConfiguration.RenderAsOcclusionOnly): listener.RenderAsOcclusionOnly = config.RenderAsOcclusionOnly; break; case nameof(MarkerConfiguration.Tint): listener.Tint = config.Tint.ToUnityColor(); break; case nameof(MarkerConfiguration.TriangleListFlipWinding): listener.TriangleListFlipWinding = config.TriangleListFlipWinding; break; default: Logger.Error($"{this}: Unknown field '{field}'"); break; } } ResetPanel(); }
static async ValueTask <(bool success, string message)> TryUpdateModuleAsync([NotNull] string id, string[] fields, string config) { (bool success, string message)result = default; if (string.IsNullOrWhiteSpace(id)) { result.message = "EE Empty configuration id!"; return(result); } if (string.IsNullOrWhiteSpace(config)) { result.message = "EE Empty configuration text!"; return(result); } using (var signal = new SemaphoreSlim(0)) { GameThread.Post(() => { try { var module = ModuleDatas.FirstOrDefault(data => data.Configuration.Id == id); if (module == null) { result.success = false; result.message = "EE There is no module with that id"; return; } module.UpdateConfiguration(config, fields); result.success = true; } catch (JsonException e) { result.success = false; result.message = $"EE Error parsing JSON config: {e.Message}"; Logger.Error("Error:", e); } catch (Exception e) { result.success = false; result.message = $"EE An exception was raised: {e.Message}"; Logger.Error("Error:", e); } finally { signal.Release(); } }); return(await signal.WaitAsync(DefaultTimeoutInMs) ? result : (false, "EE Request timed out!")); } }
bool TryGet <T>([NotNull] string uriString, [NotNull] Dictionary <string, Info <T> > repository, [NotNull] HashSet <string> negRepository, [CanBeNull] out Info <T> info) where T : UnityEngine.Object { if (uriString is null) { throw new ArgumentNullException(nameof(uriString)); } if (repository.TryGetValue(uriString, out info)) { if (info != null) { return(true); } repository.Remove(uriString); info = null; return(false); } if (negRepository.Contains(uriString)) { return(false); } if (!Uri.TryCreate(uriString, UriKind.Absolute, out Uri uri)) { Logger.Warn($"{this}: Uri '{uriString}' is not a valid uri!"); negRepository.Add(uriString); return(false); } string path = $"Package/{uri.Host}{Uri.UnescapeDataString(uri.AbsolutePath)}".Replace("//", "/"); T resource = UnityEngine.Resources.Load <T>(path); if (resource == null) { string alternativePath = Path.GetDirectoryName(path) + "/" + Path.GetFileNameWithoutExtension(path); resource = UnityEngine.Resources.Load <T>(alternativePath); } if (resource == null) { negRepository.Add(uriString); return(false); } info = new Info <T>(resource); repository[uriString] = info; return(true); }
public Sender([NotNull] string topic) { if (string.IsNullOrWhiteSpace(topic)) { throw new ArgumentException("Invalid topic!", nameof(topic)); } Topic = topic; Type = BuiltIns.GetMessageType(typeof(T)); Logger.Info($"Advertising <b>{topic}</b> <i>[{Type}]</i>."); GameThread.EverySecond += UpdateStats; Connection.Advertise(this); }
void WritePanelToConfiguration() { var markers = new List <ARExecutableMarker>(); for (int index = 0; index < panel.Types.Count; index++) { var type = (ARMarkerType)panel.Types[index].Index; if (type == ARMarkerType.Unset) { continue; } if (!float.TryParse(panel.Sizes[index].Value, out float sizeInMm) || sizeInMm <= 0) { Logger.Info($"{this}: Ignoring size for entry {index}, cannot parse '{panel.Sizes[index].Value}' " + "into a positive number."); continue; } string code = panel.Codes[index].Value.Trim(); if (string.IsNullOrEmpty(code)) { Logger.Info($"{this}: Ignoring empty code for entry {index}."); continue; } ARExecutableMarker marker = new ARExecutableMarker { Type = type, Action = (ARMarkerAction)panel.Actions[index].Index, Code = code, SizeInMm = sizeInMm, }; markers.Add(marker); } Configuration = new ARMarkersConfiguration { MaxMarkerDistanceInM = 0.5f, Markers = markers.ToArray(), }; if (ARController.Instance != null) { ARController.Instance.MarkerExecutor.Configuration = Configuration; } ModuleListPanel.UpdateSimpleConfigurationSettings(); }
void PrintListenerStatus() { StringBuilder str = new StringBuilder(); var modules = ModuleDatas.OfType <ListenerModuleData>(); foreach (var data in modules) { ListenerController controller = (ListenerController)data.Controller; var listener = controller.Listener; str.Append(listener.Topic).Append(listener.Subscribed ? " (On): " : " (Off): ") .Append(listener.NumPublishers).Append(" publishers").AppendLine(); } Logger.Info(str.ToString()); }
internal static async Task StartCaptureAsync([NotNull] StartCapture srv) { if (Settings.ScreenCaptureManager == null) { srv.Response.Success = false; srv.Response.Message = "No screenshot manager has been set for this platform"; return; } string errorMessage = null; using (SemaphoreSlim signal = new SemaphoreSlim(0)) { GameThread.Post(async() => { try { await Settings.ScreenCaptureManager.StartAsync( srv.Request.ResolutionX, srv.Request.ResolutionY, srv.Request.WithHolograms); } catch (Exception e) { errorMessage = e.Message; } finally { signal.Release(); } }); if (!await signal.WaitAsync(DefaultTimeoutInMs)) { Logger.Error("ControllerService: Unexpected timeout in StartCaptureAsync"); srv.Response.Success = false; srv.Response.Message = "Request timed out"; return; } } if (errorMessage != null) { srv.Response.Success = false; srv.Response.Message = errorMessage; return; } srv.Response.Success = true; }
static async ValueTask <(bool[] success, Pose[] poses)> TryGetFramePoseAsync(string[] ids) { bool[] success = null; Pose[] poses = null; using (SemaphoreSlim signal = new SemaphoreSlim(0)) { GameThread.Post(() => { try { List <bool> successList = new List <bool>(); List <Pose> posesList = new List <Pose>(); foreach (string id in ids) { if (!TfListener.TryGetFrame(id, out var frame)) { successList.Add(false); posesList.Add(Pose.Identity); } else { successList.Add(true); posesList.Add(frame.OriginWorldPose.Unity2RosPose()); } } success = successList.ToArray(); poses = posesList.ToArray(); } finally { signal.Release(); } }); if (!await signal.WaitAsync(DefaultTimeoutInMs)) { Logger.Error("ControllerService: Unexpected timeout in TryGetFramePoseAsync"); return(new bool[ids.Length], new Pose[ids.Length]); } } return(success, poses); }
async Task Run() { try { while (!connectionTs.IsCancellationRequested) { DateTime now = GameThread.Now; if (KeepReconnecting && ConnectionState != ConnectionState.Connected && (now - lastConnectionTry).TotalMilliseconds > ConnectionRetryTimeInMs) { SetConnectionState(ConnectionState.Connecting); bool connectionResult; try { lastConnectionTry = now; connectionResult = await Connect(); } catch (Exception e) { Logger.Error("Unexpected error in RosConnection.Connect", e); continue; } SetConnectionState(connectionResult ? ConnectionState.Connected : ConnectionState.Disconnected); } await signal.WaitAsync(TaskWaitTimeInMs); await ExecuteTasks().AwaitNoThrow(this); } SetConnectionState(ConnectionState.Disconnected); } catch (Exception e) { // shouldn't happen Logger.Internal("Left connection thread!"); Logger.Internal("Error:", e); Logger.Error("XXX Left connection thread: ", e); } connectionTs.Cancel(); }
public override void UpdateConfiguration(string configAsJson, IEnumerable <string> fields) { var config = JsonConvert.DeserializeObject <GridMapConfiguration>(configAsJson); foreach (string field in fields) { switch (field) { case nameof(GridMapConfiguration.Visible): listener.Visible = config.Visible; break; case nameof(GridMapConfiguration.Colormap): listener.Colormap = config.Colormap; break; case nameof(GridMapConfiguration.ForceMinMax): listener.ForceMinMax = config.ForceMinMax; break; case nameof(GridMapConfiguration.MinIntensity): listener.MinIntensity = config.MinIntensity; break; case nameof(GridMapConfiguration.MaxIntensity): listener.MaxIntensity = config.MaxIntensity; break; case nameof(GridMapConfiguration.FlipMinMax): listener.FlipMinMax = config.FlipMinMax; break; default: Logger.Error($"{this}: Unknown field '{field}'"); break; } } ResetPanel(); }
public ExternalResourceManager(bool createNode = true) { if (createNode) { Node = new GameObject("External Resources"); Node.SetActive(false); } try { Directory.CreateDirectory(Settings.ResourcesPath); Directory.CreateDirectory(Settings.SavedRobotsPath); } catch (Exception e) { Logger.Error($"{this}: Error creating directories", e); } if (!File.Exists(Settings.ResourcesFilePath)) { Logger.Debug("ExternalResourceManager: Failed to find file " + Settings.ResourcesFilePath); return; } Logger.Debug("ExternalResourceManager: Using resource file " + Settings.ResourcesFilePath); try { string text = File.ReadAllText(Settings.ResourcesFilePath); resourceFiles = JsonConvert.DeserializeObject <ResourceFiles>(text); } catch (Exception e) { Logger.Error($"{this}: Error reading config file", e); } }
public Task ClearModelCacheAsync(CancellationToken token = default) { runningTs.Cancel(); runningTs = new CancellationTokenSource(); var allFiles = resourceFiles.Models.Values .Concat(resourceFiles.Scenes.Values) .Concat(resourceFiles.Textures.Values) .Concat(resourceFiles.RobotDescriptions.Values); Logger.Debug($"{this}: Removing all files in {Settings.SavedRobotsPath}"); Logger.Debug($"{this}: Removing all files in {Settings.ResourcesPath}"); foreach (string path in allFiles) { try { File.Delete(path); } catch (Exception e) { Logger.Error($"ExternalResourceManager: Failed to delete file '{path}' :", e); } } resourceFiles.Models.Clear(); resourceFiles.Scenes.Clear(); resourceFiles.Textures.Clear(); resourceFiles.RobotDescriptions.Clear(); loadedModels.Clear(); loadedScenes.Clear(); loadedTextures.Clear(); return(WriteResourceFileAsync(token)); }
void OnItemClicked(int index, int subIndex) { switch (subIndex) { case 0: ModuleListPanel.LoadStateConfiguration(files[index].FileName); Close(); break; case 1: string filename = files[index].FullPath; try { File.Delete(filename); } catch (Exception e) { Logger.Internal("Error deleting config file", e); } ReadAllFiles(); break; } }
static async ValueTask <(string id, bool success, string message)> TryAddModuleFromTopicAsync( [NotNull] string topic, [NotNull] string requestedId) { (string id, bool success, string message)result = default; if (string.IsNullOrWhiteSpace(topic)) { result.message = "EE Invalid topic name"; return(result); } var data = ModuleDatas.FirstOrDefault(module => module.Topic == topic); if (data != null) { result.message = requestedId == data.Configuration.Id ? "** Module already exists" : "WW A module with that topic but different id already exists"; result.id = data.Configuration.Id; result.success = true; return(result); } if (requestedId.Length != 0 && ModuleDatas.Any(module => module.Configuration.Id == requestedId)) { result.message = "EE There is already another module with that id"; return(result); } var topics = Connection.GetSystemPublishedTopicTypes(RequestType.CachedOnly); string type = topics.FirstOrDefault(topicInfo => topicInfo.Topic == topic)?.Type; if (type == null) { topics = await Connection.GetSystemPublishedTopicTypesAsync(DefaultTimeoutInMs); type = topics.FirstOrDefault(topicInfo => topicInfo.Topic == topic)?.Type; } if (type == null) { return("", false, $"EE Failed to find topic '{topic}'"); } if (!Resource.ResourceByRosMessageType.TryGetValue(type, out ModuleType resource)) { return("", false, $"EE Type '{type}' is unsupported"); } using (var signal = new SemaphoreSlim(0)) { GameThread.Post(() => { try { result.id = ModuleListPanel.Instance.CreateModule(resource, topic, type, requestedId: requestedId.Length != 0 ? requestedId : null).Configuration.Id; result.success = true; } catch (Exception e) { result.message = $"EE An exception was raised: {e.Message}"; Logger.Error("Exception raised in TryAddModuleFromTopicAsync", e); } finally { signal.Release(); } }); return(await signal.WaitAsync(DefaultTimeoutInMs) ? result : ("", false, "EE Request timed out!")); } }
internal static async Task LaunchDialogAsync([NotNull] LaunchDialog srv) { if (string.IsNullOrEmpty(srv.Request.Dialog.Id)) { srv.Response.Success = false; srv.Response.Message = "Dialog Id is null or empty"; return; } if (Math.Abs(srv.Request.Dialog.Scale) < 1e-8) { srv.Response.Success = false; srv.Response.Message = "Cannot launch dialog with scale 0"; return; } Feedback feedback = new Feedback(); bool overrideExpired = false; using (var signal = new SemaphoreSlim(0)) { GameThread.Post(() => { try { Logger.Info($"ControllerService: Creating dialog"); /* * var dialogTimeSpan = srv.Request.Dialog.Lifetime.ToTimeSpan(); * if (srv.Request.Dialog.Lifetime == default || dialogTimeSpan.TotalSeconds > 10) * { * srv.Request.Dialog.Lifetime = TimeSpan.FromSeconds(10); * } */ var dialog = GuiDialogListener.DefaultHandler.AddDialog(srv.Request.Dialog); if (dialog == null) { TryRelease(signal); return; } dialog.ButtonClicked += TriggerButton; dialog.MenuEntryClicked += TriggerMenu; dialog.Expired += Expired; void TriggerButton(ARDialog mDialog, int buttonId) { mDialog.ButtonClicked -= TriggerButton; mDialog.MenuEntryClicked -= TriggerMenu; mDialog.Expired -= Expired; feedback.VizId = ConnectionManager.MyId ?? ""; feedback.Id = mDialog.Id ?? ""; feedback.FeedbackType = FeedbackType.ButtonClick; feedback.EntryId = buttonId; overrideExpired = true; TryRelease(signal); } void TriggerMenu(ARDialog mDialog, int buttonId) { mDialog.ButtonClicked -= TriggerButton; mDialog.MenuEntryClicked -= TriggerMenu; mDialog.Expired -= Expired; feedback.VizId = ConnectionManager.MyId ?? ""; feedback.Id = mDialog.Id ?? ""; feedback.FeedbackType = FeedbackType.MenuEntryClick; feedback.EntryId = buttonId; overrideExpired = true; TryRelease(signal); } void Expired(ARDialog mDialog) { if (overrideExpired) { return; } mDialog.ButtonClicked -= TriggerButton; mDialog.MenuEntryClicked -= TriggerMenu; mDialog.Expired -= Expired; feedback.VizId = ConnectionManager.MyId ?? ""; feedback.Id = mDialog.Id ?? ""; feedback.FeedbackType = FeedbackType.Expired; feedback.EntryId = 0; TryRelease(signal); } } catch (Exception e) { srv.Response.Success = false; srv.Response.Message = $"EE An exception was raised: {e.Message}"; signal.Release(); } }); /* * if (!await signal.WaitAsync(10000)) * { * srv.Response.Success = false; * srv.Response.Message = "EE Request timed out!"; * return; * } */ await signal.WaitAsync(); if (string.IsNullOrEmpty(srv.Response.Message)) { srv.Response.Success = true; srv.Response.Feedback = feedback; } } }
void StartLog() { void AddToQueue(string s) { messageQueue.Enqueue(s); queueHasChanged = true; if (messageQueue.Count > 100) { messageQueue.Dequeue(); } } Logger.LogInternal += AddToQueue; ConnectionManager.LogMessageArrived += (in Log message) => { string messageTime = message.Header.Stamp == default ? "" : message.Header.Stamp.ToDateTime().ToLocalTime().ToString("HH:mm:ss"); string messageLevel; switch ((LogLevel)message.Level) { case LogLevel.Debug: messageLevel = "[DEBUG]"; break; case LogLevel.Error: messageLevel = "[ERROR]"; break; case LogLevel.Fatal: messageLevel = "[FATAL]"; break; case LogLevel.Info: messageLevel = "[INFO]"; break; case LogLevel.Warn: messageLevel = "[WARN]"; break; default: messageLevel = "[?]"; break; } string messageString = $"<b>[{messageTime}] {messageLevel} [{message.Name}]:</b> {message.Msg}"; AddToQueue(messageString); }; GameThread.EverySecond += () => { if (!consoleLog.activeSelf) { return; } if (!queueHasChanged) { return; } builder.Length = 0; foreach (string msg in messageQueue) { if (msg == null) { Logger.Debug("HololensManager: Got null message!"); continue; } const int maxSize = 300; if (msg.Length > maxSize) { builder.Append(msg.Substring(0, maxSize)).Append(" ... +").Append(msg.Length - maxSize) .AppendLine(" chars"); } else { builder.AppendLine(msg); } } consoleText.text = builder.ToString(); queueHasChanged = false; }; }
internal static async Task CaptureScreenshotAsync([NotNull] CaptureScreenshot srv) { if (Settings.ScreenCaptureManager == null) { srv.Response.Success = false; srv.Response.Message = "No screenshot manager has been set for this platform"; return; } string errorMessage = null; Screenshot ss = null; Pose? pose = null; using (SemaphoreSlim signal = new SemaphoreSlim(0)) { GameThread.Post(async() => { try { var assetHolder = UnityEngine.Resources .Load <GameObject>("App Asset Holder") .GetComponent <AppAssetHolder>(); AudioSource.PlayClipAtPoint(assetHolder.Screenshot, Settings.MainCamera.transform.position); ss = await Settings.ScreenCaptureManager.CaptureColorAsync(); pose = ss != null ? TfListener.RelativePoseToFixedFrame(ss.CameraPose).Unity2RosPose().ToCameraFrame() : (Pose?)null; } catch (Exception e) { errorMessage = e.Message; } finally { signal.Release(); } }); if (!await signal.WaitAsync(DefaultTimeoutInMs)) { Logger.Error("ControllerService: Unexpected timeout in CaptureScreenshotAsync"); srv.Response.Success = false; srv.Response.Message = "Request timed out"; return; } } if (errorMessage != null) { srv.Response.Success = false; srv.Response.Message = errorMessage; return; } if (ss == null) { srv.Response.Success = false; srv.Response.Message = "Captured failed for unknown reason"; return; } srv.Response.Success = true; srv.Response.Width = ss.Width; srv.Response.Height = ss.Height; srv.Response.Bpp = ss.Bpp; srv.Response.Header = (screenshotSeq++, ss.Timestamp, TfListener.FixedFrameId); srv.Response.Intrinsics = ss.Intrinsic.ToArray(); srv.Response.Pose = pose ?? Pose.Identity; srv.Response.Data = await CompressAsync(ss); }
static async ValueTask <(string id, bool success, string message)> TryAddModuleAsync( [NotNull] string moduleTypeStr, [NotNull] string requestedId) { (string id, bool success, string message)result = default; if (string.IsNullOrWhiteSpace(moduleTypeStr)) { result.message = "EE Invalid module type"; return(result); } ModuleType moduleType = ModuleTypeFromString(moduleTypeStr); if (moduleType == ModuleType.Invalid) { result.message = "EE Invalid module type"; return(result); } if (moduleType != ModuleType.Grid && moduleType != ModuleType.DepthCloud && moduleType != ModuleType.AugmentedReality && moduleType != ModuleType.Joystick && moduleType != ModuleType.Robot) { result.message = "EE Cannot create module of that type, use AddModuleFromTopic instead"; return(result); } ModuleData moduleData; if (requestedId.Length != 0 && (moduleData = ModuleDatas.FirstOrDefault(data => data.Configuration.Id == requestedId)) != null) { if (moduleData.ModuleType != moduleType) { result.message = $"EE Another module of the same id already exists, but it has type {moduleData.ModuleType}"; } else { result.success = true; result.id = requestedId; result.message = "** Module already exists"; } return(result); } using (SemaphoreSlim signal = new SemaphoreSlim(0)) { GameThread.Post(() => { try { Logger.Info($"Creating module of type {moduleType}"); var newModuleData = ModuleListPanel.Instance.CreateModule(moduleType, requestedId: requestedId.Length != 0 ? requestedId : null); result.id = newModuleData.Configuration.Id; result.success = true; Logger.Info("Done!"); } catch (Exception e) { result.message = $"EE An exception was raised: {e.Message}"; } finally { signal.Release(); } }); return(await signal.WaitAsync(DefaultTimeoutInMs) ? result : ("", false, "EE Request timed out!")); } }