초기커밋
This commit is contained in:
154
ServerCore/AWS/AwsHelper.cs
Normal file
154
ServerCore/AWS/AwsHelper.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public static class AwsHelper
|
||||
{
|
||||
// EC2에서 실행중인 상태 종류
|
||||
public enum Ec2RunningState
|
||||
{
|
||||
None = 0,
|
||||
|
||||
False = 1, //EC2에서 실행하고 있지 않다 !!!
|
||||
True = 2 //EC2에서 실행중이다 !!!
|
||||
};
|
||||
|
||||
private static readonly string MetaDataTokenUrl = "http://169.254.169.254/latest/api/token";
|
||||
private static readonly string MetaDataInstanceIdUrl = "http://169.254.169.254/latest/meta-data/instance-id";
|
||||
private static readonly string MetaDataIpV4Url = "http://169.254.169.254/latest/meta-data/public-ipv4";
|
||||
|
||||
private static Ec2RunningState RunningState = Ec2RunningState.None;
|
||||
|
||||
public static async Task<string> getAwsInstanceId()
|
||||
{
|
||||
var aws_meta_token = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
aws_meta_token = await getAwsMetaDataToken();
|
||||
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, MetaDataInstanceIdUrl);
|
||||
request.Headers.Add("X-aws-ec2-metadata-token", aws_meta_token);
|
||||
request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, Failed to function in getAwsInstanceId() !!! : exception:{e}, awsMetaToken:{aws_meta_token}";
|
||||
Log.getLogger().fatal(err_msg);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string getAwsPublicIPv4OrEthernetIPv4()
|
||||
{
|
||||
if (true == AwsHelper.isRunningOnEC2().GetAwaiter().GetResult())
|
||||
{
|
||||
return AwsHelper.getPublicIPv4().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
return NetworkHelper.getEthernetLocalIPv4();
|
||||
}
|
||||
|
||||
private static async Task<bool> isRunningOnEC2()
|
||||
{
|
||||
if (Ec2RunningState.None != RunningState)
|
||||
{
|
||||
if(Ec2RunningState.True == RunningState) { return true; }
|
||||
else if(Ec2RunningState.False == RunningState) { return false; }
|
||||
}
|
||||
|
||||
var aws_meta_token = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
aws_meta_token = await getAwsMetaDataToken();
|
||||
|
||||
if(false == aws_meta_token.isNullOrWhiteSpace())
|
||||
{
|
||||
RunningState = Ec2RunningState.True;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
var err_msg = $"HttpRequestException !!!, Failed to function in isRunningOnEC2() : exception:{httpEx}, awsMetaToken:{aws_meta_token}";
|
||||
Log.getLogger().debug(err_msg);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, Failed to function in isRunningOnEC2() : exception:{e}, awsMetaToken:{aws_meta_token}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
|
||||
// EC2 환경이 아닌 경우
|
||||
RunningState = Ec2RunningState.False;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static async Task<string> getPublicIPv4()
|
||||
{
|
||||
var aws_meta_token = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
aws_meta_token = await getAwsMetaDataToken();
|
||||
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, MetaDataIpV4Url);
|
||||
request.Headers.Add("X-aws-ec2-metadata-token", aws_meta_token);
|
||||
request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, Failed to function in getPublicIPv4() : exception:{e}, awsMetaToken:{aws_meta_token}";
|
||||
Log.getLogger().error(err_msg);
|
||||
return await Task.FromResult(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> getAwsMetaDataToken()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Put, MetaDataTokenUrl);
|
||||
request.Headers.Add("X-aws-ec2-metadata-token-ttl-seconds", "21600");
|
||||
request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, Failed to function in getAwsMetaDataToken() !!! : Exception:{e}";
|
||||
Log.getLogger().debug(err_msg);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
ServerCore/Comparer/KeyComparer.cs
Normal file
24
ServerCore/Comparer/KeyComparer.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public static class KeyComparer
|
||||
{
|
||||
public static List<TKey> getKeysOnlyInSecond<TKey, TValue>(Dictionary<TKey, TValue> first, Dictionary<TKey, TValue> second)
|
||||
where TKey : notnull
|
||||
where TValue : notnull
|
||||
{
|
||||
return second.Keys.Except(first.Keys).ToList();
|
||||
}
|
||||
|
||||
public static List<TKey> getKeysOnlyInSecond<TKey>(List<TKey> first, List<TKey> second)
|
||||
where TKey : notnull
|
||||
{
|
||||
return second.Except(first).ToList();
|
||||
}
|
||||
}
|
||||
175
ServerCore/Config/ConfigManager.cs
Normal file
175
ServerCore/Config/ConfigManager.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
|
||||
public sealed class ConfigManager : Singleton<ConfigManager>
|
||||
{
|
||||
private readonly List<(string path, string type)> m_file_sources = new();
|
||||
private readonly List<JObject> m_jobject_sources = new();
|
||||
|
||||
private readonly Dictionary<string, FileSystemWatcher> m_watchers = new();
|
||||
private bool m_is_detect_duplicates = true;
|
||||
|
||||
public ConfigManager() { }
|
||||
|
||||
public void dnableDuplicateDetection(bool enable)
|
||||
{
|
||||
m_is_detect_duplicates = enable;
|
||||
}
|
||||
|
||||
public ConfigManager addJson(string path)
|
||||
{
|
||||
loadJson(path);
|
||||
watchFile(path, "json");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigManager addYaml(string path)
|
||||
{
|
||||
loadYaml(path);
|
||||
watchFile(path, "yaml");
|
||||
return this;
|
||||
}
|
||||
|
||||
private void loadJson(string path)
|
||||
{
|
||||
if (false == File.Exists(path))
|
||||
{
|
||||
throw new FileNotFoundException($"Not found Json File !!! : path:{path}");
|
||||
}
|
||||
|
||||
var json_text = File.ReadAllText(path);
|
||||
var jObject = JObject.Parse(json_text);
|
||||
m_file_sources.Add((path, "json"));
|
||||
m_jobject_sources.Add(jObject);
|
||||
}
|
||||
|
||||
private void loadYaml(string path)
|
||||
{
|
||||
if (false == File.Exists(path))
|
||||
{
|
||||
throw new FileNotFoundException($"Not found Yaml File !!! : path:{path}");
|
||||
}
|
||||
|
||||
var yamlText = File.ReadAllText(path);
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(CamelCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
|
||||
var yamlObject = deserializer.Deserialize<object>(yamlText);
|
||||
var serializer = new SerializerBuilder()
|
||||
.JsonCompatible()
|
||||
.Build();
|
||||
|
||||
var json = serializer.Serialize(yamlObject);
|
||||
var jObject = JObject.Parse(json);
|
||||
m_file_sources.Add((path, "yaml"));
|
||||
m_jobject_sources.Add(jObject);
|
||||
}
|
||||
|
||||
private void watchFile(string path, string type)
|
||||
{
|
||||
if (m_watchers.ContainsKey(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var directory_name = Path.GetDirectoryName(path);
|
||||
NullReferenceCheckHelper.throwIfNull(directory_name, () => $"directory_name is null !!! : path:{path}, format:{type}");
|
||||
var watcher = new FileSystemWatcher(directory_name)
|
||||
{
|
||||
Filter = Path.GetFileName(path),
|
||||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size
|
||||
};
|
||||
|
||||
watcher.Changed += (sender, e) => reloadAll();
|
||||
watcher.Created += (sender, e) => reloadAll();
|
||||
watcher.Renamed += (sender, e) => reloadAll();
|
||||
watcher.EnableRaisingEvents = true;
|
||||
|
||||
m_watchers[path] = watcher;
|
||||
}
|
||||
|
||||
private void reloadAll()
|
||||
{
|
||||
Log.getLogger().error("Detected changes in config file !!!, Reloading...");
|
||||
|
||||
m_jobject_sources.Clear();
|
||||
foreach (var (path, type) in m_file_sources)
|
||||
{
|
||||
if (type == "json")
|
||||
{
|
||||
loadJson(path);
|
||||
}
|
||||
else if (type == "yaml")
|
||||
{
|
||||
loadYaml(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T bind<T>(string sectionName)
|
||||
{
|
||||
if (m_jobject_sources.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("No config sources have been loaded !!!");
|
||||
}
|
||||
|
||||
var merged = new JObject();
|
||||
|
||||
foreach (var source in m_jobject_sources)
|
||||
{
|
||||
if (true == m_is_detect_duplicates)
|
||||
{
|
||||
detectDuplicateKeys(merged, source);
|
||||
}
|
||||
|
||||
merged.Merge(source, new JsonMergeSettings
|
||||
{
|
||||
MergeArrayHandling = MergeArrayHandling.Replace,
|
||||
MergeNullValueHandling = MergeNullValueHandling.Merge
|
||||
});
|
||||
}
|
||||
|
||||
JToken? target_token = null;
|
||||
if (false == merged.TryGetValue(sectionName, out var found_target_token))
|
||||
{
|
||||
target_token = merged;
|
||||
}
|
||||
else
|
||||
{
|
||||
target_token = found_target_token;
|
||||
}
|
||||
|
||||
if (target_token == null)
|
||||
{
|
||||
throw new Exception($"Section could not be found !!! : sectionName:{sectionName}");
|
||||
}
|
||||
|
||||
return target_token.ToObject<T>()!;
|
||||
}
|
||||
|
||||
private void detectDuplicateKeys(JObject existing, JObject incoming)
|
||||
{
|
||||
foreach (var prop in incoming.Properties())
|
||||
{
|
||||
if (existing.ContainsKey(prop.Name))
|
||||
{
|
||||
Log.getLogger().error($"Duplicate key detected !!!, overwritten by later value : propertyName:{prop.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
376
ServerCore/Container/MultiIndexDictionary.cs
Normal file
376
ServerCore/Container/MultiIndexDictionary.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public class MultiIndexDictionary<TPrimaryKey, TSubKey, TValue>
|
||||
where TPrimaryKey : notnull
|
||||
where TSubKey : notnull
|
||||
{
|
||||
private readonly ConcurrentDictionary<TPrimaryKey, TValue> m_values_by_primary_key = new();
|
||||
private readonly ConcurrentDictionary<TSubKey, TPrimaryKey> m_sub_key_to_primary_keys = new();
|
||||
private readonly ConcurrentDictionary<TPrimaryKey, TSubKey> m_primary_key_to_sub_keys = new();
|
||||
|
||||
private object m_lock = new();
|
||||
|
||||
public TValue this[TSubKey subKey]
|
||||
{
|
||||
get
|
||||
{
|
||||
TValue? item;
|
||||
if (tryGetValue(subKey, out item))
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item, $"item is null !!!");
|
||||
return item;
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"Not found SubKey !!! : {subKey.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
public TValue this[TPrimaryKey primaryKey]
|
||||
{
|
||||
get
|
||||
{
|
||||
TValue? item;
|
||||
if (tryGetValue(primaryKey, out item))
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item, $"item is null !!!");
|
||||
return item;
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"Not found PrimaryKey !!! : {primaryKey.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
public bool trySubKeyBindToPrimaryKey(TSubKey subKey, TPrimaryKey primaryKey)
|
||||
{
|
||||
var is_success = true;
|
||||
|
||||
try
|
||||
{
|
||||
lock(m_lock)
|
||||
{
|
||||
if (false == m_values_by_primary_key.ContainsKey(primaryKey))
|
||||
{
|
||||
throw new KeyNotFoundException($"Not found PrimaryKey !!! : {primaryKey.ToString()}");
|
||||
}
|
||||
|
||||
if (m_primary_key_to_sub_keys.ContainsKey(primaryKey))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_sub_key_to_primary_keys.ContainsKey(m_primary_key_to_sub_keys[primaryKey]))
|
||||
{
|
||||
m_sub_key_to_primary_keys.TryRemove(m_primary_key_to_sub_keys[primaryKey], out _);
|
||||
}
|
||||
|
||||
m_primary_key_to_sub_keys.TryRemove(primaryKey, out _);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
is_success = false;
|
||||
|
||||
var err_msg = $"Exception !!! MultiIndexDictionary.trySubKeyBindToPrimaryKey(), bound Key remove !!! :{primaryKey.ToString()}, exception:{e}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
m_sub_key_to_primary_keys[subKey] = primaryKey;
|
||||
m_primary_key_to_sub_keys[primaryKey] = subKey;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
is_success = false;
|
||||
|
||||
var err_msg = $"Exception !!! MultiIndexDictionary.trySubKeyBindToPrimaryKey(), Not found PrimaryKey :{primaryKey.ToString()}, exception:{e}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
|
||||
return is_success;
|
||||
}
|
||||
|
||||
public bool tryGetSubValue(TSubKey subKey, [MaybeNullWhen(false)] out TValue val)
|
||||
{
|
||||
val = default(TValue);
|
||||
|
||||
TPrimaryKey? primaryKey;
|
||||
|
||||
if (m_sub_key_to_primary_keys.TryGetValue(subKey, out primaryKey))
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(primaryKey, $"primaryKey is null !!!");
|
||||
return m_values_by_primary_key.TryGetValue(primaryKey, out val);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool tryGetValue(TSubKey subKey, out TValue? val)
|
||||
{
|
||||
val = default(TValue);
|
||||
|
||||
TPrimaryKey? primaryKey;
|
||||
|
||||
if (m_sub_key_to_primary_keys.TryGetValue(subKey, out primaryKey))
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(primaryKey, $"primaryKey is null !!!");
|
||||
return m_values_by_primary_key.TryGetValue(primaryKey, out val);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool tryGetPrimaryValue(TPrimaryKey primaryKey, [MaybeNullWhen(false)] out TValue val)
|
||||
{
|
||||
return m_values_by_primary_key.TryGetValue(primaryKey, out val);
|
||||
}
|
||||
|
||||
public bool tryGetValue(TPrimaryKey primaryKey, [MaybeNullWhen(false)] out TValue val)
|
||||
{
|
||||
return m_values_by_primary_key.TryGetValue(primaryKey, out val);
|
||||
}
|
||||
|
||||
public bool containsSubKey(TSubKey subKey)
|
||||
{
|
||||
TValue? val;
|
||||
|
||||
return tryGetSubValue(subKey, out val);
|
||||
}
|
||||
|
||||
public bool containsPrimaryKey(TPrimaryKey primaryKey)
|
||||
{
|
||||
TValue? val;
|
||||
|
||||
return tryGetPrimaryValue(primaryKey, out val);
|
||||
}
|
||||
|
||||
public bool tryRemoveByPrimaryKey(TPrimaryKey primaryKey, out TValue? removedValue)
|
||||
{
|
||||
removedValue = default;
|
||||
|
||||
bool is_success = true;
|
||||
|
||||
try
|
||||
{
|
||||
lock (m_lock)
|
||||
{
|
||||
if (m_primary_key_to_sub_keys.ContainsKey(primaryKey))
|
||||
{
|
||||
if (m_sub_key_to_primary_keys.ContainsKey(m_primary_key_to_sub_keys[primaryKey]))
|
||||
{
|
||||
m_sub_key_to_primary_keys.TryRemove(m_primary_key_to_sub_keys[primaryKey], out _);
|
||||
}
|
||||
|
||||
m_primary_key_to_sub_keys.TryRemove(primaryKey, out _);
|
||||
}
|
||||
|
||||
if (false == m_values_by_primary_key.TryRemove(primaryKey, out removedValue))
|
||||
{
|
||||
is_success = false;
|
||||
|
||||
var err_msg = $"Failed to TryRemove() !!!, SubKey:{primaryKey.ToString()}";
|
||||
Log.getLogger().debug(err_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
is_success = false;
|
||||
|
||||
var err_msg = $"Exception !!! MultiIndexDictionary.remove() !!!, PrimaryKey:{primaryKey.ToString()}, exception:{e}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
|
||||
return is_success;
|
||||
}
|
||||
|
||||
public bool tryRemoveBySubKey(TSubKey subKey, out TValue? removedValue)
|
||||
{
|
||||
removedValue = default;
|
||||
|
||||
bool is_success = true;
|
||||
|
||||
try
|
||||
{
|
||||
lock (m_lock)
|
||||
{
|
||||
if (false == m_values_by_primary_key.TryRemove(m_sub_key_to_primary_keys[subKey], out removedValue))
|
||||
{
|
||||
is_success = false;
|
||||
|
||||
var err_msg = $"Failed to TryRemove() !!!, SubKey:{subKey.ToString()}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_primary_key_to_sub_keys.TryRemove(m_sub_key_to_primary_keys[subKey], out _);
|
||||
m_sub_key_to_primary_keys.TryRemove(subKey, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
is_success = false;
|
||||
|
||||
var err_msg = $"Exception !!! MultiIndexDictionary.remove() !!!, SubKey:{subKey.ToString()}, exception:{e}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
|
||||
return is_success;
|
||||
}
|
||||
|
||||
public bool tryReplaceSubKey(TSubKey newSubKey, TPrimaryKey targetPrimaryKey)
|
||||
{
|
||||
bool is_success = true;
|
||||
|
||||
try
|
||||
{
|
||||
lock(m_lock)
|
||||
{
|
||||
if (true == m_sub_key_to_primary_keys.ContainsKey(newSubKey))
|
||||
{
|
||||
is_success = false;
|
||||
|
||||
var err_msg = $"Failed to ContainsKey() !!!, Already contained SubKey !!! : NewSubKey:{newSubKey.ToString()} - TargetPrimaryKey:{targetPrimaryKey.ToString()}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (false == m_primary_key_to_sub_keys.TryRemove(targetPrimaryKey, out var target_sub_key))
|
||||
{
|
||||
is_success = false;
|
||||
|
||||
var err_msg = $"Failed to TryRemove() !!!, TargetPrimaryKey:{targetPrimaryKey.ToString()}, NewSubKey:{newSubKey.ToString()}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (false == m_sub_key_to_primary_keys.TryRemove(target_sub_key, out _))
|
||||
{
|
||||
is_success = false;
|
||||
|
||||
var err_msg = $"Failed to TryRemove() !!!, TargetSubKey:{target_sub_key.ToString()}, PrimaryKey:{targetPrimaryKey.ToString()}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sub_key_to_primary_keys[newSubKey] = targetPrimaryKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
is_success = false;
|
||||
|
||||
var err_msg = $"Exception !!! MultiIndexDictionary.tryReplaceSubKey() !!! : Exception:{e} - NewSubKey:{newSubKey.ToString()}, TargetPrimaryKey:{targetPrimaryKey.ToString()}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
|
||||
return is_success;
|
||||
}
|
||||
|
||||
public bool tryAdd(TPrimaryKey primaryKey, TValue val)
|
||||
{
|
||||
bool is_success = true;
|
||||
|
||||
try
|
||||
{
|
||||
if(false == m_values_by_primary_key.TryAdd(primaryKey, val))
|
||||
{
|
||||
is_success = false;
|
||||
|
||||
var err_msg = $"Failed to TryAdd() !!!, PrimaryKey:{primaryKey.ToString()}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
is_success = false;
|
||||
|
||||
var err_msg = $"Exception !!! MultiIndexDictionary.add() !!!, PrimaryKey:{primaryKey.ToString()}, exception:{e}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
|
||||
return is_success;
|
||||
}
|
||||
|
||||
public bool tryAdd(TPrimaryKey primaryKey, TSubKey subKey, TValue val)
|
||||
{
|
||||
var is_success = tryAdd(primaryKey, val);
|
||||
if(false == is_success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return trySubKeyBindToPrimaryKey(subKey, primaryKey);
|
||||
}
|
||||
|
||||
public TValue[] cloneValues()
|
||||
{
|
||||
var values = new TValue[m_values_by_primary_key.Values.Count];
|
||||
|
||||
m_values_by_primary_key.Values.CopyTo(values, 0);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public List<TValue> Values
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_values_by_primary_key.Values.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public TPrimaryKey[] clonePrimaryKeys()
|
||||
{
|
||||
var primary_keys = new TPrimaryKey[m_values_by_primary_key.Keys.Count];
|
||||
|
||||
m_values_by_primary_key.Keys.CopyTo(primary_keys, 0);
|
||||
|
||||
return primary_keys;
|
||||
}
|
||||
|
||||
public TSubKey[] cloneSubKeys()
|
||||
{
|
||||
var sub_keys = new TSubKey[m_sub_key_to_primary_keys.Keys.Count];
|
||||
|
||||
m_sub_key_to_primary_keys.Keys.CopyTo(sub_keys, 0);
|
||||
|
||||
return sub_keys;
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
m_values_by_primary_key.Clear();
|
||||
|
||||
m_sub_key_to_primary_keys.Clear();
|
||||
|
||||
m_primary_key_to_sub_keys.Clear();
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_values_by_primary_key.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TPrimaryKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return m_values_by_primary_key.GetEnumerator();
|
||||
}
|
||||
}
|
||||
150
ServerCore/Container/PriorityQueue.cs
Normal file
150
ServerCore/Container/PriorityQueue.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 우선 순위 정책에 의해 순서를 정렬해주는 컨테이너 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
// From http://visualstudiomagazine.com/articles/2012/11/01/priority-queues-with-c.aspx
|
||||
//=============================================================================================
|
||||
|
||||
public class PriorityQueue<T> : IEnumerable<T> where T : IComparable<T>
|
||||
{
|
||||
protected List<T> m_datas;
|
||||
|
||||
public PriorityQueue()
|
||||
{
|
||||
m_datas = new();
|
||||
}
|
||||
|
||||
// 큐에 새 데이터 추가
|
||||
public void enqueue(T item)
|
||||
{
|
||||
m_datas.Add(item);
|
||||
var child_index = m_datas.Count - 1;
|
||||
|
||||
// binary heaps algorithm을 이용한 정렬
|
||||
while (child_index > 0)
|
||||
{
|
||||
var parent_index = (child_index - 1) / 2;
|
||||
if (m_datas[child_index].CompareTo(m_datas[parent_index]) >= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
T tmp = m_datas[child_index];
|
||||
m_datas[child_index] = m_datas[parent_index];
|
||||
m_datas[parent_index] = tmp;
|
||||
child_index = parent_index;
|
||||
}
|
||||
}
|
||||
|
||||
// 최상단 pop
|
||||
public T dequeue()
|
||||
{
|
||||
var last_index = m_datas.Count - 1;
|
||||
|
||||
T frontItem = m_datas[0]; // fetch the front
|
||||
m_datas[0] = m_datas[last_index];
|
||||
m_datas.RemoveAt(last_index);
|
||||
|
||||
--last_index; // last index (after removal)
|
||||
var parent_index = 0; // parent index. start at front of pq
|
||||
// binary heaps algorithm을 이용한 정렬
|
||||
while (true)
|
||||
{
|
||||
var client_index = parent_index * 2 + 1; // left child index of parent
|
||||
if (client_index > last_index)
|
||||
{
|
||||
break; // no children so done
|
||||
}
|
||||
|
||||
var rc = client_index + 1; // right child
|
||||
if (rc <= last_index && m_datas[rc].CompareTo(m_datas[client_index]) < 0) // if there is a rc (ci + 1), and it is smaller than left child, use the rc instead
|
||||
{
|
||||
client_index = rc;
|
||||
}
|
||||
|
||||
if (m_datas[parent_index].CompareTo(m_datas[client_index]) <= 0)
|
||||
{
|
||||
break; // parent is smaller than (or equal to) smallest child so done
|
||||
}
|
||||
|
||||
T tmp = m_datas[parent_index];
|
||||
m_datas[parent_index] = m_datas[client_index];
|
||||
m_datas[client_index] = tmp; // swap parent and child
|
||||
parent_index = client_index;
|
||||
}
|
||||
|
||||
return frontItem;
|
||||
}
|
||||
|
||||
// 최상단 얻기
|
||||
public T peek()
|
||||
{
|
||||
T frontItem = m_datas[0];
|
||||
return frontItem;
|
||||
}
|
||||
|
||||
public Int32 count()
|
||||
{
|
||||
return m_datas.Count;
|
||||
}
|
||||
|
||||
public virtual string toBasicString()
|
||||
{
|
||||
string str = "";
|
||||
for (Int32 index = 0; index < m_datas.Count; ++index)
|
||||
{
|
||||
str += m_datas[index].ToString() + " ";
|
||||
}
|
||||
str += "count = " + m_datas.Count;
|
||||
return str;
|
||||
}
|
||||
|
||||
//
|
||||
public bool isConsistent()
|
||||
{
|
||||
// is the heap property true for all data?
|
||||
if (m_datas.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var li = m_datas.Count - 1; // last index
|
||||
for (var parent_index = 0; parent_index < m_datas.Count; ++parent_index)
|
||||
{ // each parent index
|
||||
var left_child_index = 2 * parent_index + 1; // left child index
|
||||
var right_child_index = 2 * parent_index + 2; // right child index
|
||||
|
||||
if (left_child_index <= li && m_datas[parent_index].CompareTo(m_datas[left_child_index]) > 0)
|
||||
{
|
||||
return false; // if lc exists and it's greater than parent then bad.
|
||||
}
|
||||
if (right_child_index <= li && m_datas[parent_index].CompareTo(m_datas[right_child_index]) > 0)
|
||||
{
|
||||
return false; // check the right child too.
|
||||
}
|
||||
}
|
||||
return true; // passed all checks
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return m_datas.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
41
ServerCore/Define/ConstValue.cs
Normal file
41
ServerCore/Define/ConstValue.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace ServerCore
|
||||
{
|
||||
public static class ConstValue
|
||||
{
|
||||
// 비율
|
||||
public static readonly float default_ratio_f = 0.0001f;
|
||||
public static readonly Int64 default_ratio = 10000;
|
||||
public static readonly Int32 default_100_ratio = 100;
|
||||
public static readonly float default_100_ratio_f = 0.01f;
|
||||
|
||||
|
||||
// 시간 단위 (시간단위 기준)
|
||||
public static readonly Int32 default_1_day_to_hour = 24;
|
||||
|
||||
|
||||
// 시간 단위 (초단위 기준)
|
||||
public static readonly Int32 default_1_sec = 1;
|
||||
public static readonly Int32 default_1_min_to_sec = default_1_sec * 60;
|
||||
public static readonly Int32 default_1_hour_to_sec = 60 * default_1_min_to_sec;
|
||||
public static readonly Int32 default_1_day_to_sec = 24 * default_1_hour_to_sec;
|
||||
public static readonly Int32 default_1000_millisec = 1000;
|
||||
public static readonly Int32 default_1_sec_to_milisec = default_1_sec * default_1000_millisec;
|
||||
public static readonly float default_1000_of_sec = 0.001f; // 1000분의 1초
|
||||
public static readonly Int32 default_1000000_microsec = 1000000;
|
||||
public static readonly float default_1000000_of_sec = 0.000001f; // 1백만분의 1초
|
||||
public static readonly Int64 default_1000000000_nanosec = 1000000000;
|
||||
public static readonly double default_1000000000_of_sec = 0.000000001f; // 10억분의 1초
|
||||
|
||||
// 거리 단위
|
||||
public static readonly float default_1000_of_meter = 0.001f; // 1000분의 1미터
|
||||
|
||||
|
||||
}//ConstValues
|
||||
}
|
||||
78
ServerCore/Define/Type.cs
Normal file
78
ServerCore/Define/Type.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
||||
namespace ServerCore
|
||||
{
|
||||
//=============================================================================================
|
||||
// TimeZone 종류
|
||||
//=============================================================================================
|
||||
public enum TimeZoneType
|
||||
{
|
||||
None = 0,
|
||||
|
||||
Korea = 1, // 한국 지역
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 위치
|
||||
//=============================================================================================
|
||||
|
||||
public class Position
|
||||
{
|
||||
[JsonProperty("X")]
|
||||
public double X = 0;
|
||||
|
||||
[JsonProperty("Y")]
|
||||
public double Y = 0;
|
||||
|
||||
[JsonProperty("Z")]
|
||||
public double Z = 0;
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 회전
|
||||
//=============================================================================================
|
||||
public class Rotation
|
||||
{
|
||||
[JsonProperty("Pitch")]
|
||||
public double Pitch = 0;
|
||||
|
||||
[JsonProperty("Yaw")]
|
||||
public double Yaw = 0;
|
||||
|
||||
[JsonProperty("Roll")]
|
||||
public double Roll = 0;
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 네트워크 주소
|
||||
//=============================================================================================
|
||||
public class NetworkAddress
|
||||
{
|
||||
public string IP { get; set; } = string.Empty;
|
||||
public UInt16 Port { get; set; }
|
||||
|
||||
public void reset()
|
||||
{
|
||||
IP = string.Empty;
|
||||
Port = 0;
|
||||
}
|
||||
|
||||
public bool hasAddress()
|
||||
{
|
||||
if (0 < IP.Length && 0 < Port)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
ServerCore/Directory.Build.props
Normal file
8
ServerCore/Directory.Build.props
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<BaseIntermediateOutputPath>..\..\obj\AnyCPU\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
|
||||
<BaseOutputPath>..\..\bin\</BaseOutputPath>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
61
ServerCore/Dump/DotNetDumpHelper.cs
Normal file
61
ServerCore/Dump/DotNetDumpHelper.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
||||
|
||||
using Microsoft.Diagnostics.NETCore.Client;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public static class DotNetDumpHelper
|
||||
{
|
||||
private static DumpConfig DumpConfig;
|
||||
|
||||
private static void unhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
if (e.ExceptionObject is Exception ex)
|
||||
{
|
||||
// 덤프 파일 생성 경로와 이름
|
||||
string dumpFile = $"{DumpConfig}_{DateTime.Now.Ticks}.dmp";
|
||||
string txtOutputFile = $"{DumpConfig}_{DateTime.Now.Ticks}.txt";
|
||||
|
||||
// 현재 프로세스 id 가져오기
|
||||
int processId = Process.GetCurrentProcess().Id;
|
||||
var client = new DiagnosticsClient(processId);
|
||||
|
||||
// 덤프 파일 생성
|
||||
try
|
||||
{
|
||||
client.WriteDump(DumpType.Normal, dumpFile, logDumpGeneration: true);
|
||||
}
|
||||
catch (Exception dumpEx)
|
||||
{
|
||||
Log.getLogger().error($"Exception !!!, WriteDump() !!! - exception:{dumpEx}");
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
DumpConfig.TxtFile = txtOutputFile;
|
||||
// 예외 정보 및 프로세스 정보를 텍스트 파일로 저장
|
||||
DumpHandler.writeTxt(DumpConfig, ex);
|
||||
}
|
||||
catch (Exception txtEx)
|
||||
{
|
||||
Log.getLogger().error($"Exception !!!, DumpHandler.write() !!! - exception:{txtEx}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void init(string dumpPath, string name)
|
||||
{
|
||||
// 경로 및 이름 설정
|
||||
DumpConfig.DumpFile = $"{dumpPath}/{name}";
|
||||
|
||||
// 예외 처리 핸들러 등록
|
||||
AppDomain.CurrentDomain.UnhandledException += unhandledExceptionHandler;
|
||||
}
|
||||
}
|
||||
49
ServerCore/Dump/DumpHandler.cs
Normal file
49
ServerCore/Dump/DumpHandler.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public struct DumpConfig
|
||||
{
|
||||
public string DumpFile;
|
||||
public string TxtFile;
|
||||
}
|
||||
|
||||
public static class DumpHandler
|
||||
{
|
||||
public static void unhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var exception = e.ExceptionObject as Exception;
|
||||
File.WriteAllText("unhandled_exception.log", exception?.ToString());
|
||||
|
||||
writeTxt(DumpHelper.DumpConfig, exception);
|
||||
}
|
||||
|
||||
public static void writeTxt(DumpConfig dumpCopnfig, Exception? e)
|
||||
{
|
||||
using (var fs = new FileStream(dumpCopnfig.TxtFile, FileMode.Create, FileAccess.ReadWrite, FileShare.Write))
|
||||
{
|
||||
using (StreamWriter writer = new StreamWriter(fs))
|
||||
{
|
||||
writer.WriteLine("================================================================================================");
|
||||
writer.WriteLine("Date : " + DateTime.Now.ToString());
|
||||
writer.WriteLine();
|
||||
|
||||
Exception? exception = e;
|
||||
while (exception != null)
|
||||
{
|
||||
writer.WriteLine($"{exception}");
|
||||
writer.WriteLine("================================================================================================");
|
||||
|
||||
exception = exception.InnerException;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
ServerCore/Dump/DumpHelper.cs
Normal file
41
ServerCore/Dump/DumpHelper.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public static class DumpHelper
|
||||
{
|
||||
public static DumpConfig DumpConfig;
|
||||
|
||||
private static void unhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
// 윈도우 덤프 경로: C:\Users\xxxxxx\AppData\Local\CrashDumps
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
Exception ex = (Exception)e.ExceptionObject;
|
||||
MiniDump.write(DumpConfig, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void init(string dumpPath, string name)
|
||||
{
|
||||
DumpConfig.DumpFile = $"{dumpPath}\\{name}_{DateTime.Now.Ticks}.dmp";
|
||||
DumpConfig.TxtFile = $"{dumpPath}\\{name}_{DateTime.Now.Ticks}.txt";
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(DumpHelper.unhandledExceptionHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += DumpHandler.unhandledExceptionHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
123
ServerCore/Dump/MiniDump.cs
Normal file
123
ServerCore/Dump/MiniDump.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public static class MiniDump
|
||||
{
|
||||
[Flags]
|
||||
public enum MiniDumpType : uint
|
||||
{
|
||||
// From dbghelp.h:
|
||||
Normal = 0x00000000,
|
||||
WithDataSegs = 0x00000001,
|
||||
WithFullMemory = 0x00000002,
|
||||
WithHandleData = 0x00000004,
|
||||
FilterMemory = 0x00000008,
|
||||
ScanMemory = 0x00000010,
|
||||
WithUnloadedModules = 0x00000020,
|
||||
WithIndirectlyReferencedMemory = 0x00000040,
|
||||
FilterModulePaths = 0x00000080,
|
||||
WithProcessThreadData = 0x00000100,
|
||||
WithPrivateReadWriteMemory = 0x00000200,
|
||||
WithoutOptionalData = 0x00000400,
|
||||
WithFullMemoryInfo = 0x00000800,
|
||||
WithThreadInfo = 0x00001000,
|
||||
WithCodeSegs = 0x00002000,
|
||||
WithoutAuxiliaryState = 0x00004000,
|
||||
WithFullAuxiliaryState = 0x00008000,
|
||||
WithPrivateWriteCopyMemory = 0x00010000,
|
||||
IgnoreInaccessibleMemory = 0x00020000,
|
||||
ValidTypeFlags = 0x0003ffff,
|
||||
};
|
||||
|
||||
//typedef struct _MINIDUMP_EXCEPTION_INFORMATION {
|
||||
// DWORD ThreadId;
|
||||
// PEXCEPTION_POINTERS ExceptionPointers;
|
||||
// BOOL ClientPointers;
|
||||
//} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION;
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)] // Pack=4 is important! So it works also for x64!
|
||||
public struct MiniDumpExceptionInformation
|
||||
{
|
||||
public uint ThreadId;
|
||||
public IntPtr ExceptionPointers;
|
||||
[MarshalAs(UnmanagedType.Bool)]
|
||||
public bool ClientPointers;
|
||||
}
|
||||
|
||||
//BOOL
|
||||
//WINAPI
|
||||
//MiniDumpWriteDump(
|
||||
// __in HANDLE hProcess,
|
||||
// __in DWORD ProcessId,
|
||||
// __in HANDLE hFile,
|
||||
// __in MINIDUMP_TYPE DumpType,
|
||||
// __in_opt PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
|
||||
// __in_opt PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
|
||||
// __in_opt PMINIDUMP_CALLBACK_INFORMATION CallbackParam
|
||||
// );
|
||||
|
||||
// Overload requiring MiniDumpExceptionInformation
|
||||
[DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
|
||||
static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam);
|
||||
|
||||
// Overload supporting MiniDumpExceptionInformation == NULL
|
||||
[DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
|
||||
|
||||
static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam);
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
|
||||
static extern uint GetCurrentThreadId();
|
||||
|
||||
public static bool write(SafeHandle fileHandle, MiniDumpType dumpType)
|
||||
{
|
||||
Process currentProcess = Process.GetCurrentProcess();
|
||||
IntPtr currentProcessHandle = currentProcess.Handle;
|
||||
uint currentProcessId = (uint)currentProcess.Id;
|
||||
MiniDumpExceptionInformation exp;
|
||||
exp.ThreadId = GetCurrentThreadId();
|
||||
exp.ClientPointers = false;
|
||||
exp.ExceptionPointers = IntPtr.Zero;
|
||||
exp.ExceptionPointers = Marshal.GetExceptionPointers();
|
||||
|
||||
bool bRet = false;
|
||||
if (exp.ExceptionPointers == IntPtr.Zero)
|
||||
{
|
||||
bRet = MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)dumpType, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
else
|
||||
{
|
||||
bRet = MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)dumpType, ref exp, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
public static bool write(string fileName, MiniDumpType dumpType)
|
||||
{
|
||||
using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Write))
|
||||
{
|
||||
return write(fs.SafeFileHandle, dumpType);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool write(DumpConfig dumpCopnfig, Exception e)
|
||||
{
|
||||
using (var fs = new FileStream(dumpCopnfig.DumpFile, FileMode.Create, FileAccess.ReadWrite, FileShare.Write))
|
||||
{
|
||||
write(fs.SafeFileHandle, MiniDumpType.WithFullMemory);
|
||||
}
|
||||
|
||||
DumpHandler.writeTxt(dumpCopnfig, e);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
259
ServerCore/DynamoDB/DynamoDbConnectorBase.cs
Normal file
259
ServerCore/DynamoDB/DynamoDbConnectorBase.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Amazon.Runtime;
|
||||
using Amazon.DynamoDBv2;
|
||||
using Amazon.DynamoDBv2.DocumentModel;
|
||||
using Amazon.DynamoDBv2.Model;
|
||||
using Axion.Collections.Concurrent;
|
||||
|
||||
|
||||
|
||||
using DYNAMO_DB_TABLE_NAME = System.String;
|
||||
using DYNAMO_DB_TABLE_FULL_NAME = System.String;
|
||||
using MongoDB.Driver.Core.Configuration;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public abstract class DynamoDbConnectorBase
|
||||
{
|
||||
// 하나의 아이템 최대 저장 크기 : PK + SK + ITEM <= ITEM_MAX_SIZE_BYTES
|
||||
public static readonly UInt32 ITEM_MAX_SIZE_BYTES = 400 * 1024;
|
||||
|
||||
private AmazonDynamoDBClient? m_db_client;
|
||||
private ConcurrentDictionary<DYNAMO_DB_TABLE_NAME, DYNAMO_DB_TABLE_FULL_NAME> m_to_load_table_names = new();
|
||||
|
||||
private string m_access_key = string.Empty;
|
||||
private string m_secret_key = string.Empty;
|
||||
private AmazonDynamoDBConfig? m_db_config;
|
||||
|
||||
private ConcurrentDictionary<DYNAMO_DB_TABLE_FULL_NAME, Table> m_loaded_tables = new();
|
||||
|
||||
public bool connectToDb(string accessKey, string secretKey, AmazonDynamoDBConfig dbConfig)
|
||||
{
|
||||
m_access_key = accessKey;
|
||||
m_secret_key = secretKey;
|
||||
m_db_config = dbConfig;
|
||||
|
||||
try
|
||||
{
|
||||
m_db_client = new AmazonDynamoDBClient(accessKey, secretKey, dbConfig);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().error($"Exception !!!, Failed to perform in DynamoDbConnectorBase.connectToDb() !!! : exception:{e}, AccessKey:{accessKey}, SecretKey:{secretKey} - {toBasicString()}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> isExistTable(string tableName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_db_client == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var loaded_tables = await m_db_client.ListTablesAsync();
|
||||
return loaded_tables.TableNames.Contains(tableName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().fatal($"Exception !!!, Failed to perform in DynamoDbConnectorBase.isExistTable() !!! : exception:{e}, toFindTableName{tableName}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> createTable( AmazonDynamoDBClient dbClient, DYNAMO_DB_TABLE_FULL_NAME tableFullName
|
||||
, UpdateTimeToLiveRequest ttlUpdate
|
||||
, List<AttributeDefinition> tableAttributes
|
||||
, List<KeySchemaElement> tableKeySchema )
|
||||
{
|
||||
var request = new CreateTableRequest
|
||||
{
|
||||
TableName = tableFullName,
|
||||
AttributeDefinitions = tableAttributes,
|
||||
KeySchema = tableKeySchema,
|
||||
BillingMode = BillingMode.PAY_PER_REQUEST
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var new_table = await dbClient.CreateTableAsync(request);
|
||||
Log.getLogger().info($"Created DynamoDB Table : {new_table.TableDescription.TableName}");
|
||||
|
||||
var ttl_res = await dbClient.UpdateTimeToLiveAsync(ttlUpdate);
|
||||
if (System.Net.HttpStatusCode.OK != ttl_res.HttpStatusCode)
|
||||
{
|
||||
Log.getLogger().fatal($"Failed to UpdateTimeToLiveAsync() !!!, not configured TTL : {ttlUpdate.TimeToLiveSpecification.AttributeName}, toCreateTableName{tableFullName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var ttl_status_res = await dbClient.DescribeTimeToLiveAsync(new DescribeTimeToLiveRequest { TableName = tableFullName });
|
||||
if (TimeToLiveStatus.ENABLED != ttl_status_res.TimeToLiveDescription.TimeToLiveStatus)
|
||||
{
|
||||
Log.getLogger().fatal($"Failed to check configured TTL !!! : ttlName:{ttlUpdate.TimeToLiveSpecification.AttributeName}, toCreateTableName{tableFullName}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().fatal($"Exception !!!, Failed to perform in DynamoDbConnectorBase.createTable() !!! : exception:{e}, toCreateTableName{tableFullName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool checkItemSizeLimit(string itemString)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(itemString);
|
||||
if (bytes.Length > ITEM_MAX_SIZE_BYTES)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> createTable( DYNAMO_DB_TABLE_FULL_NAME tableFullName
|
||||
, List<AttributeDefinition> tableAttributes
|
||||
, List<KeySchemaElement> tableKeySchema
|
||||
, List<GlobalSecondaryIndex> globalSecondaryIndexes)
|
||||
{
|
||||
|
||||
|
||||
var request = new CreateTableRequest
|
||||
{
|
||||
TableName = tableFullName,
|
||||
AttributeDefinitions = tableAttributes,
|
||||
KeySchema = tableKeySchema,
|
||||
GlobalSecondaryIndexes = globalSecondaryIndexes,
|
||||
BillingMode = BillingMode.PAY_PER_REQUEST
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (m_db_client == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var new_table = await m_db_client.CreateTableAsync(request);
|
||||
|
||||
Log.getLogger().info($"Created DynamoDB Table : {new_table.TableDescription.TableName}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().fatal($"Exception !!!, Failed to perform in DynamoDbConnectorBase.createTable() !!! : exception:{e}, toCreateTableName{tableFullName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> deleteTables(List<DYNAMO_DB_TABLE_FULL_NAME> tableFullNames)
|
||||
{
|
||||
|
||||
foreach(var table_full_name in tableFullNames)
|
||||
{
|
||||
var request = new DeleteTableRequest
|
||||
{
|
||||
TableName = table_full_name,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (m_db_client == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var deleted_table = await m_db_client.DeleteTableAsync(request);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().fatal($"Exception !!!, Failed to perform in DynamoDbConnectorBase.deleteTables() !!! : exception:{e}, toDeleteTableName{table_full_name}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<TableDescription?> getTableDescription(DYNAMO_DB_TABLE_FULL_NAME tableFullName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_db_client == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var response = await m_db_client.DescribeTableAsync(tableFullName);
|
||||
return response.Table;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().fatal($"Exception !!!, Failed to perform in DynamoDbConnectorBase.getTableDescription() !!! : exception:{e}, TableName{tableFullName}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Table getTableByName(DYNAMO_DB_TABLE_NAME tableName)
|
||||
{
|
||||
m_to_load_table_names.TryGetValue(tableName, out var table_full_name);
|
||||
NullReferenceCheckHelper.throwIfNull(table_full_name, () => $"table_full_name is null !!! : tableName:{tableName}");
|
||||
|
||||
m_loaded_tables.TryGetValue(table_full_name, out var table);
|
||||
NullReferenceCheckHelper.throwIfNull(table, () => $"table is null !!!, Not found DynamoDb Table !!! : tableFullName:{table_full_name}");
|
||||
return table;
|
||||
}
|
||||
|
||||
public Table getTableByFullName(DYNAMO_DB_TABLE_FULL_NAME tableFullName)
|
||||
{
|
||||
m_loaded_tables.TryGetValue(tableFullName, out var table);
|
||||
NullReferenceCheckHelper.throwIfNull(table, () => $"table is null !!!, Not found DynamoDb Table !!! : tableFullName:{tableFullName}");
|
||||
return table;
|
||||
}
|
||||
|
||||
public bool addTable(DYNAMO_DB_TABLE_FULL_NAME tableFullName, Table loadedTable)
|
||||
{
|
||||
return m_loaded_tables.TryAdd(tableFullName, loadedTable);
|
||||
}
|
||||
|
||||
public bool hasTableName(DYNAMO_DB_TABLE_NAME tableName) => m_to_load_table_names.ContainsKey(tableName);
|
||||
|
||||
public DYNAMO_DB_TABLE_FULL_NAME getTableFullName(DYNAMO_DB_TABLE_NAME tableName)
|
||||
{
|
||||
m_to_load_table_names.TryGetValue(tableName, out var table_full_name);
|
||||
NullReferenceCheckHelper.throwIfNull(table_full_name, () => $"table_full_name is null !!! : tableName:{tableName}");
|
||||
return table_full_name;
|
||||
}
|
||||
|
||||
public ConcurrentDictionary<DYNAMO_DB_TABLE_NAME, DYNAMO_DB_TABLE_FULL_NAME> getTableNames() => m_to_load_table_names;
|
||||
|
||||
protected bool addTableFullName(DYNAMO_DB_TABLE_NAME tableName, DYNAMO_DB_TABLE_FULL_NAME tableFullName) => m_to_load_table_names.TryAdd(tableName, tableFullName);
|
||||
|
||||
public string toConnectionKeyString()
|
||||
{
|
||||
return $"AccessKey:{m_access_key}, SecretKey:{m_secret_key}";
|
||||
}
|
||||
|
||||
public string toBasicString()
|
||||
{
|
||||
return $"ServerUrl:{m_db_config?.ServiceURL}, RegionEndPoint:{m_db_config?.RegionEndpoint}";
|
||||
}
|
||||
|
||||
public AmazonDynamoDBClient? getDbClient() => m_db_client;
|
||||
}
|
||||
43
ServerCore/Exception/ConditionCheckHelper.cs
Normal file
43
ServerCore/Exception/ConditionCheckHelper.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
|
||||
//=============================================================================================
|
||||
// 조건 체크 관련 예외 지원 함수
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public static class ConditionValidCheckHelper
|
||||
{
|
||||
public static void throwIfFalse( bool isCondition
|
||||
, string errMsg )
|
||||
{
|
||||
if (false == isCondition)
|
||||
{
|
||||
throw new InvalidOperationException(errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void throwIfFalseWithCondition( Expression<Func<bool>> conditionExpression
|
||||
, Func<string>? fnLog = null )
|
||||
{
|
||||
var compiled_expression = conditionExpression.Compile();
|
||||
if (false == compiled_expression())
|
||||
{
|
||||
string condition_text = conditionExpression.Body.ToString();
|
||||
|
||||
var err_msg = fnLog?.Invoke() ?? $"Failed to check Condition: '{condition_text}'";
|
||||
|
||||
throw new InvalidOperationException(err_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
ServerCore/Exception/NullReferenceCheckHelper.cs
Normal file
66
ServerCore/Exception/NullReferenceCheckHelper.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
|
||||
//=============================================================================================
|
||||
// 객체 Null 체크 관련 예외 지원 함수
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public static class ArgumentNullReferenceCheckHelper
|
||||
{
|
||||
public static void throwIfNull( [NotNull] object? argument
|
||||
, Func<string>? fnLog = null
|
||||
, [CallerArgumentExpression(nameof(argument))] string? paramName = null )
|
||||
{
|
||||
if (argument != null)
|
||||
return;
|
||||
|
||||
if (fnLog != null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName, fnLog());
|
||||
}
|
||||
|
||||
if (paramName != null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName, $"Argument '{paramName}' cannot be null !!!");
|
||||
}
|
||||
|
||||
throw new ArgumentNullException("Cannot generate null-check error message: both 'fnLog' and 'paramName' are null !!!");
|
||||
}
|
||||
}
|
||||
|
||||
public static class NullReferenceCheckHelper
|
||||
{
|
||||
public static void throwIfNull( [NotNull] object? argument
|
||||
, Func<string>? fnLog = null
|
||||
, [CallerArgumentExpression(nameof(argument))] string? paramName = null )
|
||||
{
|
||||
if (argument != null)
|
||||
return;
|
||||
|
||||
if (fnLog != null)
|
||||
{
|
||||
throw new NullReferenceException(fnLog());
|
||||
}
|
||||
|
||||
if (paramName != null)
|
||||
{
|
||||
throw new NullReferenceException($"'{paramName}' object cannot be null !!!");
|
||||
}
|
||||
|
||||
throw new NullReferenceException("Cannot generate null-check error message: both 'fnLog' and 'paramName' are null !!!");
|
||||
}
|
||||
}
|
||||
144
ServerCore/HttpClient/SharedHttpClient.cs
Normal file
144
ServerCore/HttpClient/SharedHttpClient.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using Amazon.S3.Model;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public sealed class SharedHttpClient : Singleton<SharedHttpClient>
|
||||
{
|
||||
private readonly HttpClient m_http_client = new();
|
||||
|
||||
public SharedHttpClient()
|
||||
{
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 일반적인 HTTP 요청 처리 (GET, POST, PUT, DELETE 등 모든 메서드 지원)
|
||||
//=============================================================================================
|
||||
public async Task<HttpResponseMessage?> sendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var err_msg = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
return await m_http_client.SendAsync(requestMessage, cancellationToken);
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
err_msg = $"HttpRequestException !!!, Network error or invalid response, Failed to perform in sendAsync() : exception:{e} - {requestMessage.RequestUri}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
catch (TaskCanceledException e)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
err_msg = $"TaskCanceledException !!!, Request was canceled by caller, Failed to perform in sendAsync() : exception:{e} - {requestMessage.RequestUri}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
err_msg = $"TaskCanceledException !!!, Request timed out, Failed to perform in sendAsync() : exception:{e} - {requestMessage.RequestUri}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
}
|
||||
catch (TimeoutException e)
|
||||
{
|
||||
err_msg = $"TimeoutException !!!, The request exceeded the timeout limit, Failed to perform in sendAsync() : exception:{e} - {requestMessage.RequestUri}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
err_msg = $"Exception !!!, Unhandled exception during HTTP request, Failed to perform in sendAsync() : exception:{e} - {requestMessage.RequestUri}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage?> getAsync(string url, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var err_msg = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
// GET Http Method
|
||||
return await m_http_client.GetAsync(url, cancellationToken);
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
err_msg = $"HttpRequestException !!!, Network error or invalid response, Failed to perfom in getAsync() : exception:{e} - {url}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
catch (TaskCanceledException e)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
err_msg = $"TaskCanceledException !!!, Request was canceled by caller, Failed to perfom in getAsync() : exception:{e} - {url}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
err_msg = $"TaskCanceledException !!!, Request timed out, Failed to perfom in getAsync() : exception:{e} - {url}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
}
|
||||
catch (TimeoutException e)
|
||||
{
|
||||
err_msg = $"TimeoutException !!!, The request exceeded the timeout limit, Failed to perfom in getAsync() : exception:{e} - {url}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
err_msg = $"Exception !!!, Unhandled exception during HTTP GET request, Failed to perfom in getAsync() : exception:{e} - {url}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage?> postAsync(string url, HttpContent content, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var err_msg = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
// POST Http Method
|
||||
return await m_http_client.PostAsync(url, content, cancellationToken);
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
err_msg = $"HttpRequestException !!!, Network error or invalid response, Failed to perfom in getAsync() : exception:{e} - {url}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
catch (TaskCanceledException e)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
err_msg = $"TaskCanceledException !!!, Request was canceled by caller, Failed to perfom in getAsync() : exception:{e} - {url}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
err_msg = $"TaskCanceledException !!!, Request timed out, Failed to perfom in getAsync() : exception:{e} - {url}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
}
|
||||
catch (TimeoutException e)
|
||||
{
|
||||
err_msg = $"TimeoutException !!!, The request exceeded the timeout limit, Failed to perfom in getAsync() : exception:{e} - {url}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
err_msg = $"Exception !!!, Unhandled exception during HTTP GET request, Failed to perfom in getAsync() : exception:{e} - {url}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
201
ServerCore/Lambda/LambdaSafeHelper.cs
Normal file
201
ServerCore/Lambda/LambdaSafeHelper.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public static class LambdaSafeHelper
|
||||
{
|
||||
//=========================================================================================
|
||||
// with for
|
||||
//=========================================================================================
|
||||
public static void forLambda<T>(IEnumerable<T> source, Action<T> action)
|
||||
{
|
||||
if (source is IList<T> list)
|
||||
{
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var node_item = list[i];
|
||||
action(node_item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Source must implement IList<T> for indexed access !!!", nameof(source));
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task forLambdaAsync<T>(IEnumerable<T> source, Func<T, Task> action)
|
||||
{
|
||||
if (source is IList<T> list)
|
||||
{
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var node_item = list[i];
|
||||
await action(node_item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Source must implement IList<T> for indexed access !!!", nameof(source));
|
||||
}
|
||||
}
|
||||
|
||||
public static void forLambdaSafe<T>(IEnumerable<T> source, Action<T> action, Action<Exception> onException)
|
||||
{
|
||||
if (source is IList<T> list)
|
||||
{
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var node_item = list[i];
|
||||
try { action(node_item); }
|
||||
catch (Exception e) { onException(e); }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Source must implement IList<T> for indexed access !!!", nameof(source));
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task forLambdaSafeAsync<T>( IEnumerable<T> source
|
||||
, Func<T, Task> action, Func<Exception, Task> onException )
|
||||
{
|
||||
if (source is IList<T> list)
|
||||
{
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var node_item = list[i];
|
||||
try { await action(node_item); }
|
||||
catch (Exception e) { await onException(e); }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Source must implement IList<T> for indexed access !!!", nameof(source));
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// with foreach
|
||||
//=========================================================================================
|
||||
public static void forEachLambda<T>(IEnumerable<T> source, Action<T> action)
|
||||
{
|
||||
foreach (var node_item in source)
|
||||
{
|
||||
action(node_item);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task forEachLambdaAsync<T>(IEnumerable<T> source, Func<T, Task> action)
|
||||
{
|
||||
foreach (var node_item in source)
|
||||
{
|
||||
await action(node_item);
|
||||
}
|
||||
}
|
||||
|
||||
public static void forEachLambdaSafe<T>(IEnumerable<T> source, Action<T> action, Action<Exception> onException)
|
||||
{
|
||||
foreach (var node_item in source)
|
||||
{
|
||||
try { action(node_item); }
|
||||
catch (Exception e) { onException(e); }
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task forEachLambdaSafeAsync<T>(IEnumerable<T> source, Func<T, Task> action, Func<Exception, Task> onException)
|
||||
{
|
||||
foreach (var node_item in source)
|
||||
{
|
||||
try { await action(node_item); }
|
||||
catch (Exception e) { await onException(e); }
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// with while
|
||||
//=========================================================================================
|
||||
public static void whileWithConditionLambda<T>(IEnumerable<T> source, Func<T, bool> condition, Action<T> action)
|
||||
{
|
||||
using var enumerator = source.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
var node_item = enumerator.Current;
|
||||
if (false == condition(node_item)) { break; }
|
||||
|
||||
action(node_item);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task whileWithConditionLambdaAsync<T>(IEnumerable<T> source, Func<T, Task<bool>> condition, Func<T, Task> action)
|
||||
{
|
||||
using var enumerator = source.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
var node_item = enumerator.Current;
|
||||
if (false == await condition(node_item)) { break; }
|
||||
|
||||
await action(node_item);
|
||||
}
|
||||
}
|
||||
|
||||
public static void whileWithConditionLambdaSafe<T>(IEnumerable<T> source, Func<T, bool> condition, Action<T> action, Action<Exception> onException)
|
||||
{
|
||||
using var enumerator = source.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
var node_item = enumerator.Current;
|
||||
if (false == condition(node_item)) { break; }
|
||||
|
||||
try { action(node_item); }
|
||||
catch (Exception e) { onException(e); }
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task whileWithConditionLambdaSafeAsync<T>(IEnumerable<T> source, Func<T, Task<bool>> condition, Func<T, Task> action, Func<Exception, Task> onException)
|
||||
{
|
||||
using var enumerator = source.GetEnumerator();
|
||||
try
|
||||
{
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
var node_item = enumerator.Current;
|
||||
|
||||
if (false == await condition(node_item)) { break; }
|
||||
|
||||
await action(node_item);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await onException(e);
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// try-catch only
|
||||
//=========================================================================================
|
||||
|
||||
public static void lambdaSafe(Action action, Action<Exception>? onException = null)
|
||||
{
|
||||
try { action(); }
|
||||
catch (Exception e) { onException?.Invoke(e); }
|
||||
}
|
||||
|
||||
public static async Task lambdaSafeAsync(Func<Task> action, Func<Exception, Task>? onException = null)
|
||||
{
|
||||
try { await action(); }
|
||||
catch (Exception e)
|
||||
{
|
||||
if (onException != null)
|
||||
{
|
||||
await onException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
ServerCore/Log/CloudWatchLog.cs
Normal file
55
ServerCore/Log/CloudWatchLog.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using Amazon.Runtime;
|
||||
|
||||
|
||||
using NLog;
|
||||
using NLog.AWS.Logger;
|
||||
using NLog.Config;
|
||||
using NLog.Layouts;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public static class CloudWatchLog
|
||||
{
|
||||
private static bool m_is_opened = true;
|
||||
|
||||
public static bool setup( string? logGroup = null, string? logNamePattern = null, string? logLevel = null
|
||||
, string? awsRegion = null, string? awsAccessKey = null, string? awsSecretKey = null
|
||||
, Layout? layout = null )
|
||||
{
|
||||
if ( logGroup == string.Empty ||
|
||||
logNamePattern == string.Empty ||
|
||||
logLevel == string.Empty ||
|
||||
awsRegion == string.Empty ||
|
||||
awsAccessKey == string.Empty ||
|
||||
awsSecretKey == string.Empty )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var current_config = LogManager.Configuration;
|
||||
if(null == current_config)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var aws_target = new AWSTarget()
|
||||
{
|
||||
LogGroup = logGroup,
|
||||
Region = awsRegion,
|
||||
Credentials = new BasicAWSCredentials(awsAccessKey, awsSecretKey),
|
||||
Layout = layout
|
||||
};
|
||||
|
||||
var newRule = new LoggingRule(logNamePattern, LogLevel.FromString(logLevel), aws_target);
|
||||
current_config.LoggingRules.Add(newRule);
|
||||
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void setClosed() => m_is_opened = false;
|
||||
|
||||
public static bool isOpened() => m_is_opened;
|
||||
}
|
||||
224
ServerCore/Log/Logger.cs
Normal file
224
ServerCore/Log/Logger.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Fluent;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//===========================================================================================
|
||||
// 로그 처리자
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//===========================================================================================
|
||||
|
||||
public static class Log
|
||||
{
|
||||
public static string NLogFileName { get; set; } = "./Config/nlog.config";
|
||||
|
||||
private static string m_process_type = string.Empty;
|
||||
private static string m_ip_port = string.Empty;
|
||||
private static string m_default_logger_name_pattern = string.Empty;
|
||||
|
||||
private static bool m_is_initialized = false;
|
||||
|
||||
private static bool m_is_reconfigure = true;
|
||||
|
||||
public static void initLog( string processType, string defaultLoggerNamePattern = ""
|
||||
, EventHandler<LoggingConfigurationChangedEventArgs>? handler = null )
|
||||
{
|
||||
m_process_type = processType;
|
||||
m_default_logger_name_pattern = defaultLoggerNamePattern;
|
||||
|
||||
if(null != handler)
|
||||
{
|
||||
registerConfigurationChangedHandler(handler);
|
||||
}
|
||||
|
||||
m_is_initialized = true;
|
||||
}
|
||||
|
||||
public static void setIPnPort(string ip, UInt16 port)
|
||||
{
|
||||
m_ip_port = $"{ip}:{port}";
|
||||
}
|
||||
|
||||
public static NLog.Logger getLogger(string loggerName = "")
|
||||
{
|
||||
if (true == m_is_reconfigure)
|
||||
{
|
||||
reconfigureXML(NLogFileName);
|
||||
}
|
||||
|
||||
if (0 == loggerName.Length)
|
||||
{
|
||||
loggerName = m_default_logger_name_pattern;
|
||||
}
|
||||
|
||||
return LogManager.GetLogger(loggerName);
|
||||
}
|
||||
|
||||
private static void registerConfigurationChangedHandler(EventHandler<LoggingConfigurationChangedEventArgs> handler)
|
||||
{
|
||||
LogManager.ConfigurationChanged += handler;
|
||||
}
|
||||
|
||||
private static bool reconfigureXML(string xmlCofigFilePath)
|
||||
{
|
||||
if (true == m_is_reconfigure)
|
||||
{
|
||||
var to_reload_configure = new NLog.Config.XmlLoggingConfiguration(xmlCofigFilePath);
|
||||
if (null == to_reload_configure)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LogManager.Configuration = to_reload_configure;
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
|
||||
m_is_reconfigure = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
public static void shutdown()
|
||||
{
|
||||
CloudWatchLog.setClosed();
|
||||
LogManager.Shutdown();
|
||||
}
|
||||
|
||||
public static void sequence(string sender, string message)
|
||||
{
|
||||
NLog.Logger sequence_logger = LogManager.GetLogger("SequenceLogger");
|
||||
|
||||
LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Debug, "SequenceLogger", message);
|
||||
logEventInfo.Properties["sender"] = sender;
|
||||
sequence_logger.Debug(logEventInfo);
|
||||
}
|
||||
|
||||
public static void sequence(string sender, string receiver, string message, string errDesc = "")
|
||||
{
|
||||
NLog.Logger sequence_logger = LogManager.GetLogger("SequenceLogger");
|
||||
|
||||
LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Debug, "SequenceLogger", message);
|
||||
logEventInfo.Properties["sender"] = sender;
|
||||
logEventInfo.Properties["receiver"] = receiver;
|
||||
logEventInfo.Properties["errordesc"] = (errDesc == string.Empty || errDesc.Contains("Success") == true ? $"{errDesc}" : $"ERR-{errDesc}");
|
||||
sequence_logger.Debug(logEventInfo);
|
||||
}
|
||||
|
||||
|
||||
public static string getProcessType() => m_process_type;
|
||||
|
||||
public static string getIpPort() => m_ip_port;
|
||||
|
||||
public static bool isInitialized() => m_is_initialized;
|
||||
}
|
||||
|
||||
//===========================================================================================
|
||||
// 로그 관련 확장 함수
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//===========================================================================================
|
||||
|
||||
public static class NLogExtend
|
||||
{
|
||||
|
||||
public static void trace( this NLog.Logger _this
|
||||
, string message
|
||||
, [CallerMemberName] string memberName = ""
|
||||
, [CallerFilePath] string sourceFilePath = ""
|
||||
, [CallerLineNumber] Int32 sourceLineNumber = 0 )
|
||||
{
|
||||
var logEventInfo = new LogEventInfo(LogLevel.Trace, null, message);
|
||||
logEventInfo.setLogEventProperties( sourceFilePath, sourceLineNumber, memberName
|
||||
, Log.getProcessType(), Log.getIpPort() );
|
||||
|
||||
_this.Trace(logEventInfo);
|
||||
}
|
||||
|
||||
public static void debug( this NLog.Logger _this
|
||||
, string message
|
||||
, [CallerMemberName] string memberName = ""
|
||||
, [CallerFilePath] string sourceFilePath = ""
|
||||
, [CallerLineNumber] Int32 sourceLineNumber = 0 )
|
||||
{
|
||||
var logEventInfo = new LogEventInfo(LogLevel.Debug, null, message);
|
||||
logEventInfo.setLogEventProperties( sourceFilePath, sourceLineNumber, memberName
|
||||
, Log.getProcessType(), Log.getIpPort() );
|
||||
|
||||
_this.Debug(logEventInfo);
|
||||
}
|
||||
|
||||
public static void info( this NLog.Logger _this
|
||||
, string message
|
||||
, [CallerMemberName] string memberName = ""
|
||||
, [CallerFilePath] string sourceFilePath = ""
|
||||
, [CallerLineNumber] Int32 sourceLineNumber = 0 )
|
||||
{
|
||||
var logEventInfo = new LogEventInfo(LogLevel.Info, null, message);
|
||||
logEventInfo.setLogEventProperties( sourceFilePath, sourceLineNumber, memberName
|
||||
, Log.getProcessType(), Log.getIpPort() );
|
||||
|
||||
_this.Info(logEventInfo);
|
||||
}
|
||||
|
||||
public static void warn( this NLog.Logger _this
|
||||
, string message
|
||||
, [CallerMemberName] string memberName = ""
|
||||
, [CallerFilePath] string sourceFilePath = ""
|
||||
, [CallerLineNumber] Int32 sourceLineNumber = 0 )
|
||||
{
|
||||
var logEventInfo = new LogEventInfo(LogLevel.Warn, null, message);
|
||||
logEventInfo.setLogEventProperties( sourceFilePath, sourceLineNumber, memberName
|
||||
, Log.getProcessType(), Log.getIpPort() );
|
||||
|
||||
_this.Warn(logEventInfo);
|
||||
}
|
||||
|
||||
public static void error( this NLog.Logger _this
|
||||
, string message
|
||||
, [CallerMemberName] string memberName = ""
|
||||
, [CallerFilePath] string sourceFilePath = ""
|
||||
, [CallerLineNumber] Int32 sourceLineNumber = 0 )
|
||||
{
|
||||
var logEventInfo = new LogEventInfo(LogLevel.Error, null, message);
|
||||
logEventInfo.setLogEventProperties( sourceFilePath, sourceLineNumber, memberName
|
||||
, Log.getProcessType(), Log.getIpPort() );
|
||||
|
||||
_this.Error(logEventInfo);
|
||||
}
|
||||
|
||||
public static void fatal( this NLog.Logger _this
|
||||
, string message
|
||||
, [CallerMemberName] string memberName = ""
|
||||
, [CallerFilePath] string sourceFilePath = ""
|
||||
, [CallerLineNumber] Int32 sourceLineNumber = 0 )
|
||||
{
|
||||
var logEventInfo = new LogEventInfo(LogLevel.Fatal, null, message);
|
||||
logEventInfo.setLogEventProperties( sourceFilePath, sourceLineNumber, memberName
|
||||
, Log.getProcessType(), Log.getIpPort() );
|
||||
|
||||
_this.Fatal(logEventInfo);
|
||||
}
|
||||
|
||||
private static void setLogEventProperties( this LogEventInfo _this
|
||||
, string sourceFilePath, Int32 sourceLineNumber, string memberName
|
||||
, string processType, string ipPort )
|
||||
{
|
||||
_this.Properties["server"] = processType;
|
||||
_this.Properties["ip/port"] = ipPort;
|
||||
_this.Properties["memberName"] = memberName;
|
||||
_this.Properties["filePath"] = sourceFilePath;
|
||||
_this.Properties["lineNumber"] = sourceLineNumber;
|
||||
}
|
||||
}
|
||||
747
ServerCore/Math/MathHelper.cs
Normal file
747
ServerCore/Math/MathHelper.cs
Normal file
@@ -0,0 +1,747 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Numerics;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Amazon.DynamoDBv2.Model;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public static class MathHelper
|
||||
{
|
||||
// The infamous ''3.14159265358979...'' value (RO).
|
||||
public const float PI = (float)Math.PI;
|
||||
|
||||
// A representation of positive infinity (RO).
|
||||
public const float Infinity = Single.PositiveInfinity;
|
||||
|
||||
// A representation of negative infinity (RO).
|
||||
public const float NegativeInfinity = Single.NegativeInfinity;
|
||||
|
||||
// Degrees-to-radians conversion constant (RO).
|
||||
public const float Deg2Rad = PI * 2F / 360F;
|
||||
|
||||
// Radians-to-degrees conversion constant (RO).
|
||||
public const float Rad2Deg = 1F / Deg2Rad;
|
||||
|
||||
|
||||
|
||||
// Returns the sine of angle /value/ in radians.
|
||||
public static T sin<T>(T value)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = Convert.ToSingle(value);
|
||||
return (T)(object)MathF.Sin(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Convert.ToDouble(value);
|
||||
return (T)(object)Math.Sin(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Convert.ToDecimal(value);
|
||||
var round_result = Math.Round((double)result, 28);
|
||||
return (T)(object)(decimal)Math.Sin(round_result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the cosine of angle /value/ in radians.
|
||||
public static T cos<T>(T value)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = Convert.ToSingle(value);
|
||||
return (T)(object)MathF.Cos(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Convert.ToDouble(value);
|
||||
return (T)(object)Math.Cos(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Convert.ToDecimal(value);
|
||||
var round_result = Math.Round((double)result, 28);
|
||||
return (T)(object)(decimal)Math.Sin(round_result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the tangent of angle /value/ in radians.
|
||||
public static T tan<T>(T value)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = Convert.ToSingle(value);
|
||||
return (T)(object)MathF.Tan(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Convert.ToDouble(value);
|
||||
return (T)(object)Math.Tan(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Convert.ToDecimal(value);
|
||||
var round_result = Math.Round((double)result, 28);
|
||||
return (T)(object)(decimal)Math.Tan(round_result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the arc-sine of /value/ - the angle in radians whose sine is /f/.
|
||||
public static T asin<T>(T value)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = Convert.ToSingle(value);
|
||||
return (T)(object)MathF.Asin(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Convert.ToDouble(value);
|
||||
return (T)(object)Math.Asin(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Convert.ToDecimal(value);
|
||||
var round_result = Math.Round((double)result, 28);
|
||||
return (T)(object)(decimal)Math.Asin(round_result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the arc-cosine of /value/ - the angle in radians whose cosine is /f/.
|
||||
public static T acos<T>(T value)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = Convert.ToSingle(value);
|
||||
return (T)(object)MathF.Acos(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Convert.ToDouble(value);
|
||||
return (T)(object)Math.Acos(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Convert.ToDecimal(value);
|
||||
var round_result = Math.Round((double)result, 28);
|
||||
return (T)(object)(decimal)Math.Acos(round_result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the arc-tangent of /value/ - the angle in radians whose tangent is /f/.
|
||||
public static T atan<T>(T value)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = Convert.ToSingle(value);
|
||||
return (T)(object)MathF.Atan(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Convert.ToDouble(value);
|
||||
return (T)(object)Math.Atan(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Convert.ToDecimal(value);
|
||||
var round_result = Math.Round((double)result, 28);
|
||||
return (T)(object)Math.Atan(round_result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the angle in radians whose ::ref::Tan is @@y/x@@.
|
||||
public static T atan2<T>(T y, T x)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result_y = Convert.ToSingle(y);
|
||||
var result_x = Convert.ToSingle(x);
|
||||
return (T)(object)MathF.Atan2(result_y, result_x);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result_y = Convert.ToDouble(y);
|
||||
var result_x = Convert.ToDouble(x);
|
||||
return (T)(object)Math.Atan2(result_y, result_x);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result_y = Convert.ToDecimal(y);
|
||||
var round_result_y = Math.Round((double)result_y, 28);
|
||||
var result_x = Convert.ToDecimal(x);
|
||||
var round_result_x = Math.Round((double)result_x, 28);
|
||||
return (T)(object)(decimal)Math.Atan2(round_result_y, round_result_x);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : y:{y}, x:{x}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns square root of /value/.
|
||||
public static T sqrt<T>(T value)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = MathF.Sqrt(Convert.ToSingle(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Math.Sqrt(Convert.ToDouble(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Convert.ToDecimal(value);
|
||||
var round_result = Math.Round((double)result, 28);
|
||||
return (T)(object)(decimal)Math.Sqrt(round_result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the absolute value of /value/.
|
||||
public static T abs<T>(T value)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(Int16))
|
||||
{
|
||||
var result = (Int16)Math.Abs(Convert.ToInt16(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(Int32))
|
||||
{
|
||||
var result = (Int32)Math.Abs(Convert.ToInt32(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = MathF.Abs(Convert.ToSingle(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Math.Abs(Convert.ToDouble(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Math.Abs(Convert.ToDecimal(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// *listonly*
|
||||
public static T min<T>(T a, T b)
|
||||
where T : struct, IComparable<T>
|
||||
{
|
||||
return a.CompareTo(b) < 0 ? a : b;
|
||||
}
|
||||
|
||||
// Returns the smallest of two or more values.
|
||||
public static T min<T>(params T[] values)
|
||||
where T : struct, IComparable<T>
|
||||
{
|
||||
int len = values.Length;
|
||||
if (len == 0)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
|
||||
T min_value = values[0];
|
||||
for (int i = 1; i < len; i++)
|
||||
{
|
||||
if (values[i].CompareTo(min_value) < 0)
|
||||
min_value = values[i];
|
||||
}
|
||||
|
||||
return min_value;
|
||||
}
|
||||
public static T max<T>(T a, T b)
|
||||
where T : IComparable<T>
|
||||
{
|
||||
return a.CompareTo(b) > 0 ? a : b;
|
||||
}
|
||||
|
||||
public static T max<T>(params T[] values)
|
||||
where T : IComparable<T>
|
||||
{
|
||||
if (values.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one value must be provided.", nameof(values));
|
||||
}
|
||||
|
||||
T max_value = values[0];
|
||||
for (int i = 1; i < values.Length; i++)
|
||||
{
|
||||
if (values[i].CompareTo(max_value) > 0)
|
||||
{
|
||||
max_value = values[i];
|
||||
}
|
||||
}
|
||||
|
||||
return max_value;
|
||||
}
|
||||
|
||||
// Returns /f/ raised to power /p/.
|
||||
public static T pow<T>(T baseValue, T exponent)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
// 타입에 따라 적절한 연산을 수행
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var base_v = Convert.ToSingle(baseValue);
|
||||
var exponent_v = Convert.ToSingle(exponent);
|
||||
return (T)(object)MathF.Pow(base_v, exponent_v);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var base_v = Convert.ToDouble(baseValue);
|
||||
var exponent_v = Convert.ToDouble(exponent);
|
||||
return (T)(object)(double)Math.Pow(base_v, exponent_v);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var base_v = Convert.ToDecimal(baseValue);
|
||||
var exponent_v = Convert.ToDecimal(exponent);
|
||||
var round_result_base_v = Math.Round((double)base_v, 28);
|
||||
var round_result_exponent_v = Math.Round((double)exponent_v, 28);
|
||||
return (T)(object)(decimal)Math.Pow(round_result_base_v, round_result_exponent_v);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {baseValue}, {exponent}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Returns e raised to the specified power.
|
||||
public static T exp<T>(T power)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var power_v = Convert.ToSingle(power);
|
||||
return (T)(object)MathF.Exp(power_v);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var power_v = Convert.ToDouble(power);
|
||||
return (T)(object)(double)Math.Exp(power_v);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var power_v = Convert.ToDecimal(power);
|
||||
var round_result_power_v = Math.Round((double)power_v, 28);
|
||||
return (T)(object)(decimal)Math.Exp(round_result_power_v);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {power}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the logarithm of a specified number in a specified base.
|
||||
public static T log<T>(T f, T p)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var base_v = Convert.ToSingle(f);
|
||||
var power_v = Convert.ToSingle(p);
|
||||
return (T)(object)MathF.Log(base_v, power_v);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var base_v = Convert.ToDouble(f);
|
||||
var power_v = Convert.ToDouble(p);
|
||||
return (T)(object)(double)Math.Log(base_v, power_v);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var base_v = Convert.ToDecimal(f);
|
||||
var power_v = Convert.ToDecimal(p);
|
||||
var round_result_base_v = Math.Round((double)base_v, 28);
|
||||
var round_result_power_v = Math.Round((double)power_v, 28);
|
||||
return (T)(object)(decimal)Math.Log(round_result_base_v, round_result_power_v);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {f}, {p}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the natural (base e) logarithm of a specified number.
|
||||
public static T log<T>(T f)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var base_v = Convert.ToSingle(f);
|
||||
return (T)(object)MathF.Log(base_v);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var base_v = Convert.ToDouble(f);
|
||||
return (T)(object)(double)Math.Log(base_v);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var base_v = Convert.ToDecimal(f);
|
||||
var round_result_base_v = Math.Round((double)base_v, 28);
|
||||
return (T)(object)(decimal)Math.Log(round_result_base_v);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {f}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the base 10 logarithm of a specified number.
|
||||
public static T log10<T>(T f)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var base_v = Convert.ToSingle(f);
|
||||
return (T)(object)MathF.Log10(base_v);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var base_v = Convert.ToDouble(f);
|
||||
return (T)(object)(double)Math.Log10(base_v);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var base_v = Convert.ToDecimal(f);
|
||||
var round_result_base_v = Math.Round((double)base_v, 28);
|
||||
return (T)(object)(decimal)Math.Log10(round_result_base_v);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {f}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the smallest integer greater to or equal to /value/.
|
||||
public static T ceil<T>(T value)
|
||||
where T : struct
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = MathF.Ceiling(Convert.ToSingle(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Math.Ceiling(Convert.ToDouble(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Math.Ceiling(Convert.ToDecimal(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the largest integer smaller to or equal to /value/.
|
||||
public static T floor<T>(T value)
|
||||
where T : struct
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = MathF.Floor(Convert.ToSingle(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Math.Floor(Convert.ToDouble(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Math.Floor(Convert.ToDecimal(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns /value/ rounded to T float, double, decimal
|
||||
public static T round<T>(T value)
|
||||
where T : struct
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = MathF.Round(Convert.ToSingle(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Math.Round(Convert.ToDouble(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Math.Round(Convert.ToDecimal(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns /value/ rounded to T float, double, decimal
|
||||
public static T round<T>(T value, int decimalPlaces)
|
||||
where T : struct
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = MathF.Round(Convert.ToSingle(value), decimalPlaces);
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Math.Round(Convert.ToDouble(value), decimalPlaces);
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Math.Round(Convert.ToDecimal(value), decimalPlaces);
|
||||
return (T)(object)result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the smallest integer greater to or equal to /value/.
|
||||
public static T truncate<T>(T value)
|
||||
where T : struct, IComparable<T>
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = MathF.Truncate(Convert.ToSingle(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Math.Truncate(Convert.ToDouble(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Math.Truncate(Convert.ToDecimal(value));
|
||||
return (T)(object)result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
public static T roundUpOrDown<T>(T value, int decimalPlaces = 0)
|
||||
where T : struct, IComparable<T>
|
||||
{
|
||||
dynamic dynamic_value = value;
|
||||
double factor = Math.Pow(10, decimalPlaces); // 소숫점 자리수에 따라 곱할 값 설정, 0일 경우 소수점없이 올림 처리 한다 !!!
|
||||
|
||||
if (TypeHelper.NumericSignType.Positive == TypeHelper.checkNumericSignType<T>(dynamic_value))
|
||||
{
|
||||
return (T)(Math.Ceiling(dynamic_value * factor) / factor);
|
||||
}
|
||||
|
||||
return (T)(Math.Floor(dynamic_value * factor) / factor);
|
||||
}
|
||||
|
||||
// Returns the smallest integer greater to or equal to /value/.
|
||||
public static T ceilToInt<T>(T value)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = Convert.ToSingle(value);
|
||||
return (T)(object)(Int32)MathF.Ceiling(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Convert.ToDouble(value);
|
||||
return (T)(object)(Int32)Math.Ceiling(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Convert.ToDecimal(value);
|
||||
return (T)(object)(Int32)Math.Ceiling(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the largest integer smaller to or equal to /value/.
|
||||
public static T floorToInt<T>(T value)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = Convert.ToSingle(value);
|
||||
return (T)(object)(Int32)MathF.Floor(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Convert.ToDouble(value);
|
||||
return (T)(object)(Int32)Math.Floor(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Convert.ToDecimal(value);
|
||||
return (T)(object)(Int32)Math.Floor(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns /value/ rounded to the nearest integer.
|
||||
public static T roundToInt<T>(T value)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = Convert.ToSingle(value);
|
||||
return (T)(object)(Int32)MathF.Round(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Convert.ToDouble(value);
|
||||
return (T)(object)(Int32)Math.Round(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Convert.ToDecimal(value);
|
||||
return (T)(object)(Int32)Math.Round(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns /value/ truncated to the nearest integer.
|
||||
public static T truncateToInt<T>(T value)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result = Convert.ToSingle(value);
|
||||
return (T)(object)(Int32)MathF.Truncate(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result = Convert.ToDouble(value);
|
||||
return (T)(object)(Int32)Math.Round(result);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result = Convert.ToDecimal(value);
|
||||
return (T)(object)(Int32)Math.Round(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the sign of /value/.
|
||||
public static T sign<T>(T value)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
var result_v = Convert.ToSingle(value);
|
||||
return (T)(object)(result_v >= 0F ? 1F : -1F);
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var result_v = Convert.ToDouble(value);
|
||||
return (T)(object)(result_v >= 0D ? 1D : -1D);
|
||||
}
|
||||
else if (typeof(T) == typeof(decimal))
|
||||
{
|
||||
var result_v = Convert.ToDecimal(value);
|
||||
return (T)(object)(result_v >= 0M ? 1M : -1M);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Invalid Type !!! : {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Clamps a value between a minimum generic type and maximum generic type value.
|
||||
public static T clamp<T>(T value, T min, T max)
|
||||
where T : IComparable<T>
|
||||
{
|
||||
if (value.CompareTo(min) < 0)
|
||||
value = min;
|
||||
else if (value.CompareTo(max) > 0)
|
||||
value = max;
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
659
ServerCore/Math/QuaternionHelper.cs
Normal file
659
ServerCore/Math/QuaternionHelper.cs
Normal file
@@ -0,0 +1,659 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Numerics;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
[Serializable]
|
||||
public struct QuaternionHelper
|
||||
{
|
||||
const float radToDeg = (float)(180.0 / Math.PI);
|
||||
const float degToRad = (float)(Math.PI / 180.0);
|
||||
|
||||
public const float kEpsilon = 1E-06f; // should probably be used in the 0 tests in LookRotation or Slerp
|
||||
|
||||
|
||||
public Vector3 xyz
|
||||
{
|
||||
set
|
||||
{
|
||||
x = value.X;
|
||||
y = value.Y;
|
||||
z = value.Z;
|
||||
}
|
||||
get
|
||||
{
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
public float x;
|
||||
public float y;
|
||||
public float z;
|
||||
public float w;
|
||||
|
||||
public float this[Int32 index]
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
return this.x;
|
||||
case 1:
|
||||
return this.y;
|
||||
case 2:
|
||||
return this.z;
|
||||
case 3:
|
||||
return this.w;
|
||||
default:
|
||||
throw new IndexOutOfRangeException("Invalid Quaternion index: " + index + ", can use only 0,1,2,3");
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
this.x = value;
|
||||
break;
|
||||
case 1:
|
||||
this.y = value;
|
||||
break;
|
||||
case 2:
|
||||
this.z = value;
|
||||
break;
|
||||
case 3:
|
||||
this.w = value;
|
||||
break;
|
||||
default:
|
||||
throw new IndexOutOfRangeException("Invalid Quaternion index: " + index + ", can use only 0,1,2,3");
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>The identity rotation (RO).</para>
|
||||
/// </summary>
|
||||
public static QuaternionHelper identity
|
||||
{
|
||||
get
|
||||
{
|
||||
return new QuaternionHelper(0f, 0f, 0f, 1f);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Returns the euler angle representation of the rotation.</para>
|
||||
/// </summary>
|
||||
|
||||
public Vector3 eulerAngles
|
||||
{
|
||||
get
|
||||
{
|
||||
return Vector3Helper.multiple(QuaternionHelper.toEulerRad(this), degToRad);
|
||||
}
|
||||
set
|
||||
{
|
||||
this = QuaternionHelper.fromEulerRad(Vector3Helper.multiple(value, degToRad));
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the length (magnitude) of the quaternion.
|
||||
/// </summary>
|
||||
/// <seealso cref="LengthSquared"/>
|
||||
|
||||
public float Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return (float)System.Math.Sqrt(x * x + y * y + z * z + w * w);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the square of the quaternion length (magnitude).
|
||||
/// </summary>
|
||||
|
||||
public float LengthSquared
|
||||
{
|
||||
get
|
||||
{
|
||||
return x * x + y * y + z * z + w * w;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Constructs new Quaternion with given x,y,z,w components.</para>
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <param name="z"></param>
|
||||
/// <param name="w"></param>
|
||||
public QuaternionHelper(float x, float y, float z, float w)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.x = z;
|
||||
this.w = w;
|
||||
}
|
||||
/// <summary>
|
||||
/// Construct a new Quaternion from vector and w components
|
||||
/// </summary>
|
||||
/// <param name="v">The vector part</param>
|
||||
/// <param name="w">The w part</param>
|
||||
public QuaternionHelper(Vector3 v, float w)
|
||||
{
|
||||
this.x = v.X;
|
||||
this.y = v.Y;
|
||||
this.z = v.Z;
|
||||
this.w = w;
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Set x, y, z and w components of an existing Quaternion.</para>
|
||||
/// </summary>
|
||||
/// <param name="new_x"></param>
|
||||
/// <param name="new_y"></param>
|
||||
/// <param name="new_z"></param>
|
||||
/// <param name="new_w"></param>
|
||||
public void set(float new_x, float new_y, float new_z, float new_w)
|
||||
{
|
||||
this.x = new_x;
|
||||
this.y = new_y;
|
||||
this.z = new_z;
|
||||
this.w = new_w;
|
||||
}
|
||||
/// <summary>
|
||||
/// Scales the Quaternion to unit length.
|
||||
/// </summary>
|
||||
public void normalize()
|
||||
{
|
||||
float scale = 1.0f / this.Length;
|
||||
xyz = Vector3Helper.multiple(xyz, scale);
|
||||
w = w * scale;
|
||||
}
|
||||
/// <summary>
|
||||
/// Scale the given quaternion to unit length
|
||||
/// </summary>
|
||||
/// <param name="q">The quaternion to normalize</param>
|
||||
/// <returns>The normalized quaternion</returns>
|
||||
public static QuaternionHelper normalize(QuaternionHelper q)
|
||||
{
|
||||
QuaternionHelper result;
|
||||
normalize(ref q, out result);
|
||||
|
||||
return result;
|
||||
}
|
||||
/// <summary>
|
||||
/// Scale the given quaternion to unit length
|
||||
/// </summary>
|
||||
/// <param name="q">The quaternion to normalize</param>
|
||||
/// <param name="result">The normalized quaternion</param>
|
||||
public static void normalize(ref QuaternionHelper q, out QuaternionHelper result)
|
||||
{
|
||||
float scale = 1.0f / q.Length;
|
||||
result = new QuaternionHelper(Vector3Helper.multiple(q.xyz, scale), q.w * scale);
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>The dot product between two rotations.</para>
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
public static float dot(QuaternionHelper a, QuaternionHelper b)
|
||||
{
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Creates a rotation which rotates /angle/ degrees around /axis/.</para>
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="axis"></param>
|
||||
public static QuaternionHelper angleAxis(float angle, Vector3 axis)
|
||||
{
|
||||
return QuaternionHelper.angleAxis(angle, ref axis);
|
||||
}
|
||||
private static QuaternionHelper angleAxis(float degress, ref Vector3 axis)
|
||||
{
|
||||
if (Vector3Helper.sqrMagnitude(axis) == 0.0f)
|
||||
{
|
||||
return identity;
|
||||
}
|
||||
|
||||
QuaternionHelper result = identity;
|
||||
var radians = degress * degToRad;
|
||||
radians *= 0.5f;
|
||||
Vector3Helper.normalize(axis);
|
||||
axis = Vector3Helper.multiple(axis, (float)System.Math.Sin(radians));
|
||||
result.x = axis.X;
|
||||
result.y = axis.Y;
|
||||
result.z = axis.Z;
|
||||
result.w = (float)System.Math.Cos(radians);
|
||||
|
||||
return normalize(result);
|
||||
}
|
||||
public void toAngleAxis(out float angle, out Vector3 axis)
|
||||
{
|
||||
QuaternionHelper.toAxisAngleRad(this, out axis, out angle);
|
||||
angle *= radToDeg;
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Creates a rotation which rotates from /fromDirection/ to /toDirection/.</para>
|
||||
/// </summary>
|
||||
/// <param name="fromDirection"></param>
|
||||
/// <param name="toDirection"></param>
|
||||
public static QuaternionHelper fromToRotation(Vector3 fromDirection, Vector3 toDirection)
|
||||
{
|
||||
return rotateTowards(lookRotation(fromDirection), lookRotation(toDirection), float.MaxValue);
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Creates a rotation which rotates from /fromDirection/ to /toDirection/.</para>
|
||||
/// </summary>
|
||||
/// <param name="fromDirection"></param>
|
||||
/// <param name="toDirection"></param>
|
||||
public void setFromToRotation(Vector3 fromDirection, Vector3 toDirection)
|
||||
{
|
||||
this = QuaternionHelper.fromToRotation(fromDirection, toDirection);
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Creates a rotation with the specified /forward/ and /upwards/ directions.</para>
|
||||
/// </summary>
|
||||
/// <param name="forward">The direction to look in.</param>
|
||||
/// <param name="upwards">The vector that defines in which direction up is.</param>
|
||||
public static QuaternionHelper lookRotation(Vector3 forward, Vector3 upwards)
|
||||
{
|
||||
return QuaternionHelper.lookRotation(ref forward, ref upwards);
|
||||
}
|
||||
public static QuaternionHelper lookRotation(Vector3 forward)
|
||||
{
|
||||
Vector3 up = Vector3Helper.up;
|
||||
return QuaternionHelper.lookRotation(ref forward, ref up);
|
||||
}
|
||||
|
||||
// 유니티와 결과물이 다르므로 다시 구현 해야 한다.
|
||||
// from http://answers.unity3d.com/questions/467614/what-is-the-source-code-of-quaternionlookrotation.html
|
||||
private static QuaternionHelper lookRotation(ref Vector3 forward, ref Vector3 up)
|
||||
{
|
||||
Vector3Helper.normalize(forward);
|
||||
|
||||
Vector3 vector = forward.normalize();
|
||||
Vector3 vector2 = Vector3Helper.cross(up, vector).normalize();
|
||||
Vector3 vector3 = Vector3Helper.cross(vector, vector2);
|
||||
|
||||
var m00 = vector2.X;
|
||||
var m01 = vector2.Y;
|
||||
var m02 = vector2.Z;
|
||||
var m10 = vector3.X;
|
||||
var m11 = vector3.Y;
|
||||
var m12 = vector3.Z;
|
||||
var m20 = vector.X;
|
||||
var m21 = vector.Y;
|
||||
var m22 = vector.Z;
|
||||
|
||||
float num8 = (m00 + m11) + m22;
|
||||
var quaternion = new QuaternionHelper();
|
||||
if (num8 > 0f)
|
||||
{
|
||||
var num = MathHelper.sqrt(num8 + 1f);
|
||||
quaternion.w = num * 0.5f;
|
||||
num = 0.5f / num;
|
||||
quaternion.x = (m12 - m21) * num;
|
||||
quaternion.y = (m20 - m02) * num;
|
||||
quaternion.z = (m01 - m10) * num;
|
||||
return quaternion;
|
||||
}
|
||||
if ((m00 >= m11) && (m00 >= m22))
|
||||
{
|
||||
var num7 = MathHelper.sqrt(((1f + m00) - m11) - m22);
|
||||
var num4 = 0.5f / num7;
|
||||
quaternion.x = 0.5f * num7;
|
||||
quaternion.y = (m01 + m10) * num4;
|
||||
quaternion.z = (m02 + m20) * num4;
|
||||
quaternion.w = (m12 - m21) * num4;
|
||||
return quaternion;
|
||||
}
|
||||
if (m11 > m22)
|
||||
{
|
||||
var num6 = MathHelper.sqrt(((1f + m11) - m00) - m22);
|
||||
var num3 = 0.5f / num6;
|
||||
quaternion.x = (m10 + m01) * num3;
|
||||
quaternion.y = 0.5f * num6;
|
||||
quaternion.z = (m21 + m12) * num3;
|
||||
quaternion.w = (m20 - m02) * num3;
|
||||
return quaternion;
|
||||
}
|
||||
var num5 = MathHelper.sqrt(((1f + m22) - m00) - m11);
|
||||
var num2 = 0.5f / num5;
|
||||
quaternion.x = (m20 + m02) * num2;
|
||||
quaternion.y = (m21 + m12) * num2;
|
||||
quaternion.z = 0.5f * num5;
|
||||
quaternion.w = (m01 - m10) * num2;
|
||||
return quaternion;
|
||||
}
|
||||
public void setLookRotation(Vector3 view)
|
||||
{
|
||||
Vector3 up = Vector3Helper.up;
|
||||
this.setLookRotation(view, up);
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Creates a rotation with the specified /forward/ and /upwards/ directions.</para>
|
||||
/// </summary>
|
||||
/// <param name="view">The direction to look in.</param>
|
||||
/// <param name="up">The vector that defines in which direction up is.</param>
|
||||
public void setLookRotation(Vector3 view, Vector3 up)
|
||||
{
|
||||
this = QuaternionHelper.lookRotation(view, up);
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Spherically interpolates between /a/ and /b/ by t. The parameter /t/ is clamped to the range [0, 1].</para>
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <param name="t"></param>
|
||||
public static QuaternionHelper slerp(QuaternionHelper a, QuaternionHelper b, float t)
|
||||
{
|
||||
return QuaternionHelper.slerp(ref a, ref b, t);
|
||||
}
|
||||
private static QuaternionHelper slerp(ref QuaternionHelper a, ref QuaternionHelper b, float t)
|
||||
{
|
||||
if (t > 1) t = 1;
|
||||
if (t < 0) t = 0;
|
||||
return slerpUnclamped(ref a, ref b, t);
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Spherically interpolates between /a/ and /b/ by t. The parameter /t/ is not clamped.</para>
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <param name="t"></param>
|
||||
public static QuaternionHelper slerpUnclamped(QuaternionHelper a, QuaternionHelper b, float t)
|
||||
{
|
||||
return QuaternionHelper.slerpUnclamped(ref a, ref b, t);
|
||||
}
|
||||
private static QuaternionHelper slerpUnclamped(ref QuaternionHelper a, ref QuaternionHelper b, float t)
|
||||
{
|
||||
// if either input is zero, return the other.
|
||||
if (a.LengthSquared == 0.0f)
|
||||
{
|
||||
if (b.LengthSquared == 0.0f)
|
||||
{
|
||||
return identity;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
else if (b.LengthSquared == 0.0f)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
float cosHalfAngle = a.w * b.w + Vector3Helper.dot(a.xyz, b.xyz);
|
||||
|
||||
if (cosHalfAngle >= 1.0f || cosHalfAngle <= -1.0f)
|
||||
{
|
||||
// angle = 0.0f, so just return one input.
|
||||
return a;
|
||||
}
|
||||
else if (cosHalfAngle < 0.0f)
|
||||
{
|
||||
b.xyz = Vector3Helper.multiple(b.xyz, -1);
|
||||
b.w = -b.w;
|
||||
cosHalfAngle = -cosHalfAngle;
|
||||
}
|
||||
|
||||
float blendA;
|
||||
float blendB;
|
||||
if (cosHalfAngle < 0.99f)
|
||||
{
|
||||
// do proper slerp for big angles
|
||||
float halfAngle = (float)System.Math.Acos(cosHalfAngle);
|
||||
float sinHalfAngle = (float)System.Math.Sin(halfAngle);
|
||||
float oneOverSinHalfAngle = 1.0f / sinHalfAngle;
|
||||
blendA = (float)System.Math.Sin(halfAngle * (1.0f - t)) * oneOverSinHalfAngle;
|
||||
blendB = (float)System.Math.Sin(halfAngle * t) * oneOverSinHalfAngle;
|
||||
}
|
||||
else
|
||||
{
|
||||
// do lerp if angle is really small.
|
||||
blendA = 1.0f - t;
|
||||
blendB = t;
|
||||
}
|
||||
|
||||
QuaternionHelper result = new QuaternionHelper(Vector3Helper.multiple(a.xyz, blendA) + Vector3Helper.multiple(b.xyz, blendB), blendA * a.w + blendB * b.w);
|
||||
if (result.LengthSquared > 0.0f)
|
||||
return normalize(result);
|
||||
else
|
||||
return identity;
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Interpolates between /a/ and /b/ by /t/ and normalizes the result afterwards. The parameter /t/ is clamped to the range [0, 1].</para>
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <param name="t"></param>
|
||||
public static QuaternionHelper lerp(QuaternionHelper a, QuaternionHelper b, float t)
|
||||
{
|
||||
if (t > 1) t = 1;
|
||||
if (t < 0) t = 0;
|
||||
return slerp(ref a, ref b, t); // TODO: use lerp not slerp, "Because quaternion works in 4D. Rotation in 4D are linear" ???
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Interpolates between /a/ and /b/ by /t/ and normalizes the result afterwards. The parameter /t/ is not clamped.</para>
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <param name="t"></param>
|
||||
public static QuaternionHelper lerpUnclamped(QuaternionHelper a, QuaternionHelper b, float t)
|
||||
{
|
||||
return slerp(ref a, ref b, t);
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Rotates a rotation /from/ towards /to/.</para>
|
||||
/// </summary>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="to"></param>
|
||||
/// <param name="maxDegreesDelta"></param>
|
||||
public static QuaternionHelper rotateTowards(QuaternionHelper from, QuaternionHelper to, float maxDegreesDelta)
|
||||
{
|
||||
float angled = angle(from, to);
|
||||
if (angled == 0.0f) return to;
|
||||
return slerpUnclamped(from, to, MathHelper.min(1.0f, maxDegreesDelta / angled));
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Returns the Inverse of /rotation/.</para>
|
||||
/// </summary>
|
||||
/// <param name="rotation"></param>
|
||||
public static QuaternionHelper inverse(QuaternionHelper rotation)
|
||||
{
|
||||
float lengthSq = rotation.LengthSquared;
|
||||
if (lengthSq != 0.0)
|
||||
{
|
||||
float i = 1.0f / lengthSq;
|
||||
return new QuaternionHelper(Vector3Helper.multiple(rotation.xyz, -i), rotation.w * i);
|
||||
}
|
||||
return rotation;
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Returns a nicely formatted string of the Quaternion.</para>
|
||||
/// </summary>
|
||||
/// <param name="format"></param>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("({0:F1}, {1:F1}, {2:F1}, {3:F1})", this.x, this.y, this.z, this.w);
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Returns a nicely formatted string of the Quaternion.</para>
|
||||
/// </summary>
|
||||
/// <param name="format"></param>
|
||||
public string toString(string format)
|
||||
{
|
||||
return string.Format("({0}, {1}, {2}, {3})", this.x.ToString(format), this.y.ToString(format), this.z.ToString(format), this.w.ToString(format));
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Returns the angle in degrees between two rotations /a/ and /b/.</para>
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
public static float angle(QuaternionHelper a, QuaternionHelper b)
|
||||
{
|
||||
float f = QuaternionHelper.dot(a, b);
|
||||
return MathHelper.acos(MathHelper.min(MathHelper.abs(f), 1f)) * 2f * radToDeg;
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Returns a rotation that rotates z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis (in that order).</para>
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <param name="z"></param>
|
||||
public static QuaternionHelper euler(float x, float y, float z)
|
||||
{
|
||||
return QuaternionHelper.fromEulerRad(Vector3Helper.multiple(new Vector3((float)x, (float)y, (float)z), degToRad));
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Returns a rotation that rotates z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis (in that order).</para>
|
||||
/// </summary>
|
||||
/// <param name="euler"></param>
|
||||
public static QuaternionHelper euler(Vector3 euler)
|
||||
{
|
||||
return QuaternionHelper.fromEulerRad(Vector3Helper.multiple(euler, degToRad));
|
||||
}
|
||||
|
||||
// 유니티와 결과물이 다르므로 다시 구현 해야 한다.
|
||||
// from http://stackoverflow.com/questions/12088610/conversion-between-euler-quaternion-like-in-unity3d-engine
|
||||
private static Vector3 toEulerRad(QuaternionHelper q1)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(q1, () => $"q1 is null !!!");
|
||||
|
||||
float sqw = q1.w * q1.w;
|
||||
float sqx = q1.x * q1.x;
|
||||
float sqy = q1.y * q1.y;
|
||||
float sqz = q1.z * q1.z;
|
||||
float unit = sqx + sqy + sqz + sqw; // if normalised is one, otherwise is correction factor
|
||||
float test = q1.x * q1.w - q1.y * q1.z;
|
||||
Vector3 v;
|
||||
|
||||
if (test > 0.4995f * unit)
|
||||
{ // singularity at north pole
|
||||
v.Y = 2f * MathHelper.atan2(q1.y, q1.x);
|
||||
v.X = MathHelper.PI / 2;
|
||||
v.Z = 0;
|
||||
return normalizeAngles(Vector3Helper.multiple(v, MathHelper.Rad2Deg));
|
||||
}
|
||||
if (test < -0.4995f * unit)
|
||||
{ // singularity at south pole
|
||||
v.Y = -2f * MathHelper.atan2(q1.y, q1.x);
|
||||
v.X = -MathHelper.PI / 2;
|
||||
v.Z = 0;
|
||||
return normalizeAngles(Vector3Helper.multiple(v, MathHelper.Rad2Deg));
|
||||
}
|
||||
QuaternionHelper q = new QuaternionHelper(q1.w, q1.z, q1.x, q1.y);
|
||||
v.Y = MathHelper.atan2(2f * q.x * q.w + 2f * q.y * q.z, 1 - 2f * (q.z * q.z + q.w * q.w)); // Yaw
|
||||
v.X = MathHelper.asin(2f * (q.x * q.z - q.w * q.y)); // Pitch
|
||||
v.Z = MathHelper.atan2(2f * q.x * q.y + 2f * q.z * q.w, 1 - 2f * (q.y * q.y + q.z * q.z)); // Roll
|
||||
return normalizeAngles(Vector3Helper.multiple(v, MathHelper.Rad2Deg));
|
||||
}
|
||||
private static Vector3 normalizeAngles(Vector3 angles)
|
||||
{
|
||||
angles.X = normalizeAngle(angles.Y);
|
||||
angles.Y = normalizeAngle(angles.Y);
|
||||
angles.Z = normalizeAngle(angles.Z);
|
||||
return angles;
|
||||
}
|
||||
private static float normalizeAngle(float angle)
|
||||
{
|
||||
while (angle > 360)
|
||||
angle -= 360;
|
||||
while (angle < 0)
|
||||
angle += 360;
|
||||
return angle;
|
||||
}
|
||||
// from http://stackoverflow.com/questions/11492299/quaternion-to-euler-angles-algorithm-how-to-convert-to-y-up-and-between-ha
|
||||
private static QuaternionHelper fromEulerRad(Vector3 euler)
|
||||
{
|
||||
var yaw = euler.X;
|
||||
var pitch = euler.Y;
|
||||
var roll = euler.Z;
|
||||
float rollOver2 = roll * 0.5f;
|
||||
float sinRollOver2 = (float)System.Math.Sin((float)rollOver2);
|
||||
float cosRollOver2 = (float)System.Math.Cos((float)rollOver2);
|
||||
float pitchOver2 = pitch * 0.5f;
|
||||
float sinPitchOver2 = (float)System.Math.Sin((float)pitchOver2);
|
||||
float cosPitchOver2 = (float)System.Math.Cos((float)pitchOver2);
|
||||
float yawOver2 = yaw * 0.5f;
|
||||
float sinYawOver2 = (float)System.Math.Sin((float)yawOver2);
|
||||
float cosYawOver2 = (float)System.Math.Cos((float)yawOver2);
|
||||
QuaternionHelper result;
|
||||
result.x = sinYawOver2 * cosPitchOver2 * cosRollOver2 + cosYawOver2 * sinPitchOver2 * sinRollOver2;
|
||||
result.y = cosYawOver2 * sinPitchOver2 * cosRollOver2 - sinYawOver2 * cosPitchOver2 * sinRollOver2;
|
||||
result.z = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2;
|
||||
result.w = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2;
|
||||
return result;
|
||||
}
|
||||
private static void toAxisAngleRad(QuaternionHelper q, out Vector3 axis, out float angle)
|
||||
{
|
||||
if (System.Math.Abs(q.w) > 1.0f)
|
||||
q.normalize();
|
||||
angle = 2.0f * (float)System.Math.Acos(q.w); // angle
|
||||
float den = (float)System.Math.Sqrt(1.0 - q.w * q.w);
|
||||
if (den > 0.0001f)
|
||||
{
|
||||
axis = Vector3Helper.division(q.xyz, den);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This occurs when the angle is zero.
|
||||
// Not a problem: just set an arbitrary normalized axis.
|
||||
axis = new Vector3(1, 0, 0);
|
||||
}
|
||||
}
|
||||
public override Int32 GetHashCode()
|
||||
{
|
||||
return this.x.GetHashCode() ^ this.y.GetHashCode() << 2 ^ this.z.GetHashCode() >> 2 ^ this.w.GetHashCode() >> 1;
|
||||
}
|
||||
public override bool Equals(object? other)
|
||||
{
|
||||
if (!(other is QuaternionHelper))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
QuaternionHelper quaternion = (QuaternionHelper)other;
|
||||
return this.x.Equals(quaternion.x) && this.y.Equals(quaternion.y) && this.z.Equals(quaternion.z) && this.w.Equals(quaternion.w);
|
||||
}
|
||||
public bool Equals(QuaternionHelper other)
|
||||
{
|
||||
return this.x.Equals(other.x) && this.y.Equals(other.y) && this.z.Equals(other.z) && this.w.Equals(other.w);
|
||||
}
|
||||
public static QuaternionHelper operator *(QuaternionHelper lhs, QuaternionHelper rhs)
|
||||
{
|
||||
return new QuaternionHelper(lhs.w * rhs.x + lhs.x * rhs.w + lhs.y * rhs.z - lhs.z * rhs.y, lhs.w * rhs.y + lhs.y * rhs.w + lhs.z * rhs.x - lhs.x * rhs.z, lhs.w * rhs.z + lhs.z * rhs.w + lhs.x * rhs.y - lhs.y * rhs.x, lhs.w * rhs.w - lhs.x * rhs.x - lhs.y * rhs.y - lhs.z * rhs.z);
|
||||
}
|
||||
public static Vector3 operator *(QuaternionHelper rotation, Vector3 point)
|
||||
{
|
||||
float num = rotation.x * 2f;
|
||||
float num2 = rotation.y * 2f;
|
||||
float num3 = rotation.z * 2f;
|
||||
float num4 = rotation.x * num;
|
||||
float num5 = rotation.y * num2;
|
||||
float num6 = rotation.z * num3;
|
||||
float num7 = rotation.x * num2;
|
||||
float num8 = rotation.x * num3;
|
||||
float num9 = rotation.y * num3;
|
||||
float num10 = rotation.w * num;
|
||||
float num11 = rotation.w * num2;
|
||||
float num12 = rotation.w * num3;
|
||||
Vector3 result;
|
||||
result.X = (1f - (num5 + num6)) * point.X + (num7 - num12) * point.Y + (num8 + num11) * point.Z;
|
||||
result.Y = (num7 + num12) * point.X + (1f - (num4 + num6)) * point.Y + (num9 - num10) * point.Z;
|
||||
result.Z = (num8 - num11) * point.X + (num9 + num10) * point.Y + (1f - (num4 + num5)) * point.Z;
|
||||
return result;
|
||||
}
|
||||
public static bool operator ==(QuaternionHelper lhs, QuaternionHelper rhs)
|
||||
{
|
||||
return QuaternionHelper.dot(lhs, rhs) > 0.999999f;
|
||||
}
|
||||
public static bool operator !=(QuaternionHelper lhs, QuaternionHelper rhs)
|
||||
{
|
||||
return QuaternionHelper.dot(lhs, rhs) <= 0.999999f;
|
||||
}
|
||||
}
|
||||
79
ServerCore/Math/TransformHelper.cs
Normal file
79
ServerCore/Math/TransformHelper.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public class TransformHelper
|
||||
{
|
||||
public static Position rotateX(Position pos, Rotation rot)
|
||||
{
|
||||
double radian = (Math.PI * rot.Roll) / 180.0f;
|
||||
var cosValue = Math.Cos(radian);
|
||||
var sinValue = Math.Sin(radian);
|
||||
|
||||
double y = (pos.Y * cosValue) + (pos.Z * -sinValue);
|
||||
double z = (pos.Y * sinValue) + (pos.Z * cosValue);
|
||||
|
||||
Position retValue = new Position
|
||||
{
|
||||
X = pos.X,
|
||||
Y = y,
|
||||
Z = z,
|
||||
};
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
public static Position rotateY(Position pos, Rotation rot)
|
||||
{
|
||||
double radian = (Math.PI * rot.Pitch) / 180.0f;
|
||||
var cosValue = Math.Cos(radian);
|
||||
var sinValue = Math.Sin(radian);
|
||||
|
||||
double x = (pos.X * cosValue) + (pos.Z * -sinValue);
|
||||
double z = (pos.X * sinValue) + (pos.Z * cosValue);
|
||||
|
||||
Position retValue = new Position
|
||||
{
|
||||
X = x,
|
||||
Y = pos.Y,
|
||||
Z = z,
|
||||
};
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
public static Position rotateZ(Position pos, Rotation rot)
|
||||
{
|
||||
double radian = (Math.PI * rot.Yaw) / 180.0f;
|
||||
var cosValue = Math.Cos(radian);
|
||||
var sinValue = Math.Sin(radian);
|
||||
|
||||
double x = (pos.X * cosValue) + (pos.Y * -sinValue);
|
||||
double y = (pos.X * sinValue) + (pos.Y * cosValue);
|
||||
|
||||
Position retValue = new Position
|
||||
{
|
||||
X = x,
|
||||
Y = y,
|
||||
Z = pos.Z,
|
||||
};
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
public static Position rotate(Position pos, Rotation rot)
|
||||
{
|
||||
Position tempPos;
|
||||
tempPos = rotateY(pos, rot);
|
||||
tempPos = rotateZ(tempPos, rot);
|
||||
tempPos = rotateX(tempPos, rot);
|
||||
|
||||
return tempPos;
|
||||
}
|
||||
}
|
||||
376
ServerCore/Math/Vector3Helper.cs
Normal file
376
ServerCore/Math/Vector3Helper.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Numerics;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public static class Vector3Helper
|
||||
{
|
||||
public const float Infinity = float.MaxValue;
|
||||
public const float kEpsilon = 0.00001F;
|
||||
public const float kEpsilonNormalSqrt = 1e-15F;
|
||||
|
||||
private static readonly Vector3 zeroVector = new Vector3(0F, 0F, 0F);
|
||||
private static readonly Vector3 oneVector = new Vector3(1F, 1F, 1F);
|
||||
private static readonly Vector3 upVector = new Vector3(0F, 1F, 0F);
|
||||
private static readonly Vector3 downVector = new Vector3(0F, -1F, 0F);
|
||||
private static readonly Vector3 leftVector = new Vector3(-1F, 0F, 0F);
|
||||
private static readonly Vector3 rightVector = new Vector3(1F, 0F, 0F);
|
||||
private static readonly Vector3 forwardVector = new Vector3(0F, 0F, 1F);
|
||||
private static readonly Vector3 backwardVector = new Vector3(0F, 0F, -1F);
|
||||
private static readonly Vector3 positiveInfinityVector = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
|
||||
private static readonly Vector3 negativeInfinityVector = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
|
||||
|
||||
public static Vector3 zero { get { return zeroVector; } }
|
||||
|
||||
public static Vector3 normalize(this Vector3 _this)
|
||||
{
|
||||
return normalizeEx(_this);
|
||||
}
|
||||
|
||||
public static Vector3 multipleEx(this Vector3 _this, float b)
|
||||
{
|
||||
return multiple(_this, b);
|
||||
}
|
||||
|
||||
public static bool isZero(Vector3 v)
|
||||
{
|
||||
return Equals(v, zeroVector);
|
||||
}
|
||||
|
||||
public static Vector3 deepClone(Vector3 from)
|
||||
{
|
||||
return new Vector3(from.X, from.Y, from.Z);
|
||||
}
|
||||
|
||||
public static void deepCopy(Vector3 from, ref Vector3 to)
|
||||
{
|
||||
to.X = from.X;
|
||||
to.Y = from.Y;
|
||||
to.Z = from.Z;
|
||||
}
|
||||
|
||||
public static Vector3 normalizeEx(Vector3 value)
|
||||
{
|
||||
float mag = magnitude(value);
|
||||
if (mag > kEpsilon)
|
||||
{
|
||||
return new Vector3(value.X / mag, value.Y / mag, value.Z / mag);
|
||||
}
|
||||
else
|
||||
{
|
||||
return zero;
|
||||
}
|
||||
}
|
||||
|
||||
// 외적
|
||||
public static Vector3 cross(Vector3 lhs, Vector3 rhs)
|
||||
{
|
||||
return new Vector3 (
|
||||
lhs.Y * rhs.Z - lhs.Z * rhs.Y,
|
||||
lhs.Z * rhs.X - lhs.X * rhs.Z,
|
||||
lhs.X * rhs.Y - lhs.Y * rhs.X
|
||||
);
|
||||
}
|
||||
|
||||
// 외적을 이용한 a 점과 (from, to)방향 벡터의 수직 거리(수선의 발)
|
||||
public static float pointToVectorDistance(Vector3 a, Vector3 from, Vector3 to)
|
||||
{
|
||||
// from->to 방향벡터
|
||||
Vector3 line_vector = dir(to, from);
|
||||
// from->a 방향벡터
|
||||
Vector3 from_to_a_vector = dir(a, from);
|
||||
// 외적 from에서 a로의 벡터와 from to 벡터의 외적
|
||||
Vector3 crossed = cross(from_to_a_vector, line_vector);
|
||||
// 외적 결과 벡터의 크기를 라인 백터의 크기로 나누면 직선 from->to와 a점 사이의 거리가 나온다.
|
||||
return magnitude(crossed) / magnitude(line_vector);
|
||||
}
|
||||
// 외적을 이용한 (a, b)방향 벡터 점 p의 수직 거리(수선의 발)
|
||||
public static float PointToDirDistance(Vector3 dir_a_b, Vector3 dir_a_p)
|
||||
{
|
||||
// 외적 a에서 p로의 방향 벡터와 a b 방향 벡터의 외적
|
||||
Vector3 crossed = cross(dir_a_p, dir_a_b);
|
||||
// 외적 결과 벡터의 크기를 라인 백터의 크기로 나누면 직선 a->b와 c점 사이의 거리가 나온다.
|
||||
return magnitude(crossed) / magnitude(dir_a_b);
|
||||
}
|
||||
|
||||
// 점과 선분사이의 최단 거리
|
||||
public static bool pointToLineDistance(Vector3 p, Vector3 from, Vector3 to, bool is_capsule, out float dist)
|
||||
{
|
||||
dist = 0;
|
||||
|
||||
float from_to_distance = distance(from, to);
|
||||
// from == to
|
||||
if (0 == from_to_distance)
|
||||
{
|
||||
dist = distance(from, p);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector3 from_to_vector = dir(to, from);
|
||||
Vector3 from_p_vector = dir(p, from);
|
||||
Vector3 to_p_vector = dir(p, to);
|
||||
|
||||
// 수선의 발이 선분 위에 있는 경우
|
||||
if (dot(from_p_vector, from_to_vector) * dot(to_p_vector, from_to_vector) <= 0)
|
||||
{
|
||||
// 외적을 이용한 방향 벡터와 점 사이의 수직거리를 얻는다.
|
||||
dist = pointToVectorDistance(p, from, to);
|
||||
|
||||
return true;
|
||||
}
|
||||
// 수선의 발이 선분 위에 없으면 양끝점과 p의 거리 중 최단 거리를 리턴
|
||||
if (is_capsule)
|
||||
{
|
||||
dist = Math.Min(distance(from, p), distance(to, p));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//내적
|
||||
public static float dot(Vector3 lhs, Vector3 rhs) { return lhs.X * rhs.X + lhs.Y * rhs.Y + lhs.Z * rhs.Z; }
|
||||
|
||||
// 내적을 이용한 두 방향 벡터 사이의 각도
|
||||
public static float angle(Vector3 a_dir, Vector3 b_dir)
|
||||
{
|
||||
// sqrt(a) * sqrt(b) = sqrt(a * b) -- valid for real numbers
|
||||
float denominator = MathHelper.sqrt(sqrMagnitude(a_dir) * sqrMagnitude(b_dir));
|
||||
if (denominator < kEpsilonNormalSqrt)
|
||||
return 0F;
|
||||
|
||||
float dotted = MathHelper.clamp(dot(a_dir, b_dir) / denominator, -1F, 1F);
|
||||
float angle = MathHelper.acos(dotted);
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
public static Vector3 dir(Vector3 to, Vector3 from) { return new Vector3(to.X - from.X, to.Y - from.Y, to.Z - from.Z); }
|
||||
|
||||
public static float magnitude(Vector3 vector) { return MathHelper.sqrt(vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z); }
|
||||
|
||||
public static float sqrMagnitude(Vector3 vector) { return vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z; }
|
||||
|
||||
public static float distance(Vector3 a, Vector3 b)
|
||||
{
|
||||
Vector3 vec = new Vector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
|
||||
return MathHelper.sqrt(vec.X * vec.X + vec.Y * vec.Y + vec.Z * vec.Z);
|
||||
}
|
||||
|
||||
public static float distance(Vector3 a, Vector3 b, float radius_a, float radius_b)
|
||||
{
|
||||
Vector3 vec = new Vector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
|
||||
float distance = MathHelper.sqrt(vec.X * vec.X + vec.Y * vec.Y + vec.Z * vec.Z);
|
||||
|
||||
distance = distance - (radius_a + radius_b);
|
||||
distance = Math.Max(0, distance);
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
public static bool checkDistanceXZ(Vector3 sourcePos, Vector3 targetPos, float maxDiffDist, out float distance)
|
||||
{
|
||||
distance = 0;
|
||||
|
||||
var source_pos = sourcePos;
|
||||
var target_pos = targetPos;
|
||||
source_pos.Y = target_pos.Y;
|
||||
|
||||
var diff_distance = Vector3Helper.distance(source_pos, target_pos);
|
||||
if (maxDiffDist < diff_distance)
|
||||
{
|
||||
distance = diff_distance;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool equals(Vector3 a, Vector3 b)
|
||||
{
|
||||
return a.X.Equals(b.X) && a.Y.Equals(b.Y) && a.Z.Equals(b.Z);
|
||||
}
|
||||
|
||||
// Adds two vectors.
|
||||
public static Vector3 add(Vector3 a, Vector3 b)
|
||||
{
|
||||
return new Vector3(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
|
||||
}
|
||||
|
||||
public static Vector3 sub(Vector3 a, Vector3 b)
|
||||
{
|
||||
return new Vector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
|
||||
}
|
||||
|
||||
public static Vector3 multiple(Vector3 a, float b)
|
||||
{
|
||||
return new Vector3(a.X * b, a.Y * b, a.Z * b);
|
||||
}
|
||||
|
||||
public static Vector3 division(Vector3 a, float b)
|
||||
{
|
||||
return new Vector3(a.X / b, a.Y / b, a.Z / b);
|
||||
}
|
||||
|
||||
public static void dirVectorToDegree(Vector3 dir, out float degree, Vector3 toAxis)
|
||||
{
|
||||
degree = 0;
|
||||
|
||||
if (true == Vector3Helper.equals(Vector3Helper.forward, toAxis))
|
||||
{
|
||||
degree = MathHelper.atan2(dir.X, dir.Z) * (180f / MathHelper.PI);
|
||||
}
|
||||
else if (true == Vector3Helper.equals(Vector3Helper.right, toAxis))
|
||||
{
|
||||
degree = MathHelper.atan2(dir.Z, dir.X) * (180f / MathHelper.PI);
|
||||
}
|
||||
}
|
||||
|
||||
public static void degreeToDirVectorWithXY(float degree, out Vector3 dir, Vector3 toAxis)
|
||||
{
|
||||
dir = Vector3Helper.zero;
|
||||
|
||||
float rad = degree * MathHelper.Deg2Rad;
|
||||
|
||||
if (true == Vector3Helper.Equals(Vector3Helper.forward, toAxis))
|
||||
{
|
||||
float x = MathHelper.sin(rad);
|
||||
float y = MathHelper.cos(rad);
|
||||
|
||||
dir = new Vector3(x, y, 0);
|
||||
}
|
||||
else if (true == Vector3Helper.Equals(Vector3Helper.right, toAxis))
|
||||
{
|
||||
float x = MathHelper.cos(rad);
|
||||
float y = MathHelper.sin(rad);
|
||||
|
||||
dir = new Vector3(x, y, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void degreeToDirVectorWithXZ(float degree, out Vector3 dir, Vector3 toAxis)
|
||||
{
|
||||
dir = Vector3Helper.zero;
|
||||
|
||||
float rad = degree * MathHelper.Deg2Rad;
|
||||
|
||||
if (true == Vector3Helper.Equals(Vector3Helper.forward, toAxis))
|
||||
{
|
||||
float x = MathHelper.sin(rad);
|
||||
float z = MathHelper.cos(rad);
|
||||
|
||||
dir = new Vector3(x, 0, z);
|
||||
}
|
||||
else if (true == Vector3Helper.Equals(Vector3Helper.right, toAxis))
|
||||
{
|
||||
float x = MathHelper.cos(rad);
|
||||
float z = MathHelper.sin(rad);
|
||||
|
||||
dir = new Vector3(x, 0, z);
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector3 rotateVectorByDegree(float degree, Vector3 baseVector, Vector3 toAxis)
|
||||
{
|
||||
float rad = degree * MathHelper.Deg2Rad;
|
||||
|
||||
if (true == Vector3Helper.Equals(Vector3Helper.forward, toAxis))
|
||||
{
|
||||
return rotateVectorZForwardByRadian(rad, baseVector);
|
||||
}
|
||||
else if (true == Vector3Helper.Equals(Vector3Helper.right, toAxis))
|
||||
{
|
||||
return rotateVectorXRightByRadian(rad, baseVector);
|
||||
}
|
||||
|
||||
return Vector3Helper.zero;
|
||||
}
|
||||
|
||||
public static Vector3 rotateVectorZForwardByRadian(float radian, Vector3 baseVector)
|
||||
{
|
||||
//일단 2d x, z 값만 활용 한다.
|
||||
float ca = MathHelper.cos(radian);
|
||||
float sa = MathHelper.sin(radian);
|
||||
|
||||
// z축이 forward 이므로 x,z를 바꾼다.
|
||||
return new Vector3( (baseVector.Z * sa + baseVector.X * ca)
|
||||
, backwardVector.Y
|
||||
, (baseVector.Z * ca - baseVector.X * sa));
|
||||
}
|
||||
|
||||
public static Vector3 rotateVectorXRightByRadian(float radian, Vector3 baseVector)
|
||||
{
|
||||
// 일단 2d x, z 값만 활용 한다.
|
||||
float ca = MathHelper.cos(radian);
|
||||
float sa = MathHelper.sin(radian);
|
||||
|
||||
return new Vector3( (baseVector.X * ca - baseVector.Z * sa)
|
||||
, backwardVector.Y
|
||||
, (baseVector.X * sa + baseVector.Z * ca));
|
||||
}
|
||||
|
||||
// Shorthand for writing @@Vector3(0, 0, 0)@@
|
||||
// Shorthand for writing @@Vector3(1, 1, 1)@@
|
||||
public static Vector3 one { get { return oneVector; } }
|
||||
|
||||
// Shorthand for writing @@Vector3(0, 0, 1)@@
|
||||
public static Vector3 forward { get { return forwardVector; } }
|
||||
|
||||
public static Vector3 back { get { return backwardVector; } }
|
||||
|
||||
// Shorthand for writing @@Vector3(0, 1, 0)@@
|
||||
public static Vector3 up { get { return upVector; } }
|
||||
|
||||
public static Vector3 down { get { return downVector; } }
|
||||
|
||||
public static Vector3 left { get { return leftVector; } }
|
||||
|
||||
// Shorthand for writing @@Vector3(1, 0, 0)@@
|
||||
public static Vector3 right { get { return rightVector; } }
|
||||
|
||||
public static Vector3 angleOnAxisX(float angle)
|
||||
{
|
||||
return new Vector3(angle, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
public static Vector3 angleOnAxisY(float angle)
|
||||
{
|
||||
return new Vector3(0.0f, angle, 0.0f);
|
||||
}
|
||||
|
||||
public static Vector3 angleOnAxisZ(float angle)
|
||||
{
|
||||
return new Vector3(0.0f, 0.0f, angle);
|
||||
}
|
||||
|
||||
// Shorthand for writing @@Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity)@@
|
||||
public static Vector3 positiveInfinity { get { return positiveInfinityVector; } }
|
||||
|
||||
// Shorthand for writing @@Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity)@@
|
||||
public static Vector3 negativeInfinity { get { return negativeInfinityVector; } }
|
||||
|
||||
public static string toString(Vector3 a)
|
||||
{
|
||||
return string.Format("({0:F1}, {1:F1}, {2:F1})", a.X, a.Y, a.Z);
|
||||
}
|
||||
|
||||
// Returns a vector that is made from the smallest components of two vectors.
|
||||
public static Vector3 min(Vector3 lhs, Vector3 rhs)
|
||||
{
|
||||
return new Vector3(MathHelper.min(lhs.X, rhs.X), MathHelper.min(lhs.Y, rhs.Y), MathHelper.min(lhs.Z, rhs.Z));
|
||||
}
|
||||
|
||||
// Returns a vector that is made from the largest components of two vectors.
|
||||
public static Vector3 max(Vector3 lhs, Vector3 rhs)
|
||||
{
|
||||
return new Vector3(MathHelper.max(lhs.X, rhs.X), MathHelper.max(lhs.Y, rhs.Y), MathHelper.max(lhs.Z, rhs.Z));
|
||||
}
|
||||
}
|
||||
84
ServerCore/MongoDB/MongoDbConnectorBase.cs
Normal file
84
ServerCore/MongoDB/MongoDbConnectorBase.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MongoDB.Driver;
|
||||
|
||||
|
||||
using MONGO_DB_LIST = System.String;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
|
||||
public abstract class MongoDbConnectorBase : IDisposable
|
||||
{
|
||||
private IMongoClient? m_mongo_client;
|
||||
|
||||
public MongoDbConnectorBase()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<(bool, List<MONGO_DB_LIST>)> initAndVerifyDb( string connectionString
|
||||
, int minConnectionPoolSize, int maxConnectionPoolSize
|
||||
, int waitQueueTimeoutSecs )
|
||||
{
|
||||
var err_msg = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
if (true == connectionString.isNullOrWhiteSpace())
|
||||
{
|
||||
throw new ArgumentException("Connection string is null or empty.", nameof(connectionString));
|
||||
}
|
||||
|
||||
if (minConnectionPoolSize < 0 || maxConnectionPoolSize <= 0 || minConnectionPoolSize > maxConnectionPoolSize)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("Invalid pool size configuration.");
|
||||
}
|
||||
|
||||
if (waitQueueTimeoutSecs < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(waitQueueTimeoutSecs), "Wait queue timeout must be non-negative.");
|
||||
}
|
||||
|
||||
var settings = MongoClientSettings.FromConnectionString(connectionString);
|
||||
settings.MinConnectionPoolSize = minConnectionPoolSize;
|
||||
settings.MaxConnectionPoolSize = maxConnectionPoolSize;
|
||||
settings.WaitQueueTimeout = TimeSpan.FromSeconds(waitQueueTimeoutSecs);
|
||||
|
||||
var mongo_client = new MongoClient(settings);
|
||||
|
||||
var cursor = await mongo_client.ListDatabaseNamesAsync();
|
||||
var db_list = await cursor.ToListAsync();
|
||||
|
||||
m_mongo_client = mongo_client;
|
||||
|
||||
return (true, db_list);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().error($"Exception !!!, Failed to perform in MongoDbConnectorBase.initAndVerifyDb() !!! : exception:{e} - connectionString:{connectionString}");
|
||||
}
|
||||
|
||||
return (false, new List<MONGO_DB_LIST>());
|
||||
}
|
||||
|
||||
public IMongoClient getMongoClient()
|
||||
{
|
||||
NullReferenceCheckHelper.throwIfNull(m_mongo_client, () => $"m_mongo_client is null !!!");
|
||||
return m_mongo_client;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_mongo_client = null;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void disposeMongoDb()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
19
ServerCore/MongoDB/MongoDbRepository.cs
Normal file
19
ServerCore/MongoDB/MongoDbRepository.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using MongoDB.Driver;
|
||||
|
||||
|
||||
namespace ServerBase;
|
||||
|
||||
|
||||
public class MongoDbRepository<TCollection>
|
||||
where TCollection : class
|
||||
{
|
||||
protected IMongoCollection<TCollection> m_collection;
|
||||
|
||||
protected MongoDbRepository(IMongoClient client, string database, string collection)
|
||||
{
|
||||
var mongoDb = client.GetDatabase(database);
|
||||
m_collection = mongoDb.GetCollection<TCollection>(collection);
|
||||
}
|
||||
|
||||
public IMongoCollection<TCollection> getCollection() => m_collection;
|
||||
}
|
||||
224
ServerCore/Network/NetworkHelper.cs
Normal file
224
ServerCore/Network/NetworkHelper.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
|
||||
//=============================================================================================
|
||||
// 네트워크 관련 각종 지원 함수
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
public static class NetworkHelper
|
||||
{
|
||||
private static string m_ip_address_ipv4 = string.Empty;
|
||||
|
||||
public static bool parseIPnPort(this string _this, UInt16 defaultPort, out string host, out UInt16 port)
|
||||
{
|
||||
host = string.Empty;
|
||||
port = 0;
|
||||
|
||||
var splitted_value = _this.Split(":");
|
||||
if (2 == splitted_value.Length)
|
||||
{
|
||||
host = splitted_value[0];
|
||||
port = Convert.ToUInt16(splitted_value[1].Trim());
|
||||
}
|
||||
else if (1 == splitted_value.Length)
|
||||
{
|
||||
host = splitted_value[0];
|
||||
port = defaultPort;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string getEthernetLocalIPv4()
|
||||
{
|
||||
if(true == m_ip_address_ipv4.isNullOrWhiteSpace())
|
||||
{
|
||||
m_ip_address_ipv4 = NetworkHelper.getLocalIPAddress(new[] { NetworkInterfaceType.Ethernet }, AddressFamily.InterNetwork);
|
||||
}
|
||||
|
||||
return m_ip_address_ipv4;
|
||||
}
|
||||
|
||||
public static string getLocalIPAddress(IEnumerable<NetworkInterfaceType> networkTypes, AddressFamily addressFamiltyType)
|
||||
{
|
||||
string return_address = string.Empty;
|
||||
|
||||
// Get a list of all network interfaces (including Ethernet, Wi-Fi, etc.)
|
||||
NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||
|
||||
foreach (NetworkInterface network in networkInterfaces)
|
||||
{
|
||||
// Check if the network type matches the given types
|
||||
if (false == networkTypes.Contains(network.NetworkInterfaceType))
|
||||
continue;
|
||||
|
||||
// Read the IP configuration for each network
|
||||
IPInterfaceProperties properties = network.GetIPProperties();
|
||||
|
||||
// Check if the network interface is up and is not a virtual/pseudo interface
|
||||
if ( network.OperationalStatus != OperationalStatus.Up
|
||||
|| true == network.Description.ToLower().Contains("virtual")
|
||||
|| true == network.Description.ToLower().Contains("pseudo"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Each network interface may have multiple IP addresses
|
||||
foreach (IPAddressInformation address in properties.UnicastAddresses)
|
||||
{
|
||||
if ( address.Address.AddressFamily == addressFamiltyType)
|
||||
{
|
||||
if (false == IPAddress.IsLoopback(address.Address))
|
||||
{
|
||||
return_address = address.Address.ToString();
|
||||
break; // Exit after first non-loopback address found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (false == string.IsNullOrEmpty(return_address)) { break; }
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(return_address))
|
||||
{
|
||||
// If no address is found, return appropriate loopback address based on the available version
|
||||
if (true == isIPv6Available(networkTypes))
|
||||
{
|
||||
return_address = "::1"; // Fallback to IPv6 loopback address if no address found
|
||||
}
|
||||
else
|
||||
{
|
||||
return_address = "127.0.0.1"; // Fallback to IPv4 loopback address if no address found
|
||||
}
|
||||
}
|
||||
|
||||
return return_address;
|
||||
}
|
||||
|
||||
// Private IP 범위 체크 (네트워크 범위도 아규먼트로 받기)
|
||||
public static bool isPrivateIP(string ipAddress, string[] privateRanges)
|
||||
{
|
||||
if (IPAddress.TryParse(ipAddress, out IPAddress? ip))
|
||||
{
|
||||
if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
|
||||
{
|
||||
// IPv4 처리
|
||||
var octets = ip.GetAddressBytes();
|
||||
foreach (var range in privateRanges)
|
||||
{
|
||||
if(false == range.Contains(":"))
|
||||
{
|
||||
// IPv4 체크
|
||||
if (isInRangeIPv4(octets, range))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
|
||||
{
|
||||
// IPv6 처리
|
||||
var octets = ip.GetAddressBytes();
|
||||
foreach (var range in privateRanges)
|
||||
{
|
||||
if (range.Contains(":"))
|
||||
{
|
||||
if (isInRangeIPv6(octets, range))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // Public IP
|
||||
}
|
||||
|
||||
// IPv4 주소가 특정 대역에 포함되는가
|
||||
public static bool isInRangeIPv4(byte[] ipAddress, string range)
|
||||
{
|
||||
var parts = range.Split('/');
|
||||
var network = IPAddress.Parse(parts[0]);
|
||||
int prefixLength = int.Parse(parts[1]);
|
||||
|
||||
var networkBytes = network.GetAddressBytes();
|
||||
int mask = 32 - prefixLength;
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if ((networkBytes[i] & (255 << mask)) != (ipAddress[i] & (255 << mask)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// IPv6 주소가 특정 대역에 포함되는가
|
||||
public static bool isInRangeIPv6(byte[] ipAddress, string range)
|
||||
{
|
||||
var parts = range.Split('/');
|
||||
var network = IPAddress.Parse(parts[0]);
|
||||
int prefixLength = int.Parse(parts[1]);
|
||||
|
||||
var networkBytes = network.GetAddressBytes();
|
||||
int mask = 128 - prefixLength;
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if ((networkBytes[i] & (255 << mask)) != (ipAddress[i] & (255 << mask)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool isIPv6Available(IEnumerable<NetworkInterfaceType> networkTypes)
|
||||
{
|
||||
// Check if any network interface of the specified types has an IPv6 address
|
||||
NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||
|
||||
foreach (var network in networkInterfaces)
|
||||
{
|
||||
// Check if the network type matches the given types
|
||||
if (!networkTypes.Contains(network.NetworkInterfaceType))
|
||||
continue;
|
||||
|
||||
IPInterfaceProperties properties = network.GetIPProperties();
|
||||
|
||||
// Check if this network interface has any IPv6 address
|
||||
foreach (var address in properties.UnicastAddresses)
|
||||
{
|
||||
if (address.Address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
return true; // Found an IPv6 address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // No IPv6 address found
|
||||
}
|
||||
}
|
||||
|
||||
101
ServerCore/Network/NetworkInterface.cs
Normal file
101
ServerCore/Network/NetworkInterface.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// Network Interface 관련 정보를 조건에 해당하는 정보만 로딩하여 보관해 주는 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public class NetInterface
|
||||
{
|
||||
public string m_desc = "";
|
||||
public string m_ip = "";
|
||||
public string m_ipv6 = "";
|
||||
}
|
||||
|
||||
public class NetInterfaceManager
|
||||
{
|
||||
// ni num, address
|
||||
private List<NetInterface> m_nics = new List<NetInterface>();
|
||||
private string m_ip_addresses = string.Empty;
|
||||
|
||||
public NetInterfaceManager()
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
m_nics.Clear();
|
||||
|
||||
// Ethernet의 ipv6, ipv4 주소만 수집한다.
|
||||
var nics = NetworkInterface.GetAllNetworkInterfaces();
|
||||
foreach (var ni in nics)
|
||||
{
|
||||
if (ni.NetworkInterfaceType == NetworkInterfaceType.Ethernet ||
|
||||
ni.NetworkInterfaceType == NetworkInterfaceType.Loopback)
|
||||
{
|
||||
bool has_info = false;
|
||||
NetInterface nic = new NetInterface();
|
||||
nic.m_desc = ni.Description;
|
||||
|
||||
var unicast_addresses = ni.GetIPProperties().UnicastAddresses;
|
||||
foreach (var ip_info in unicast_addresses)
|
||||
{
|
||||
if (ip_info.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
nic.m_ip = ip_info.Address.ToString();
|
||||
has_info = true;
|
||||
m_ip_addresses += nic.m_ip;
|
||||
}
|
||||
else if (ip_info.Address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
// 추가하자.
|
||||
nic.m_ipv6 = ip_info.Address.ToString();
|
||||
has_info = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_info)
|
||||
{
|
||||
add(nic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void add(NetInterface nic)
|
||||
{
|
||||
m_nics.Add(nic);
|
||||
}
|
||||
|
||||
public NetInterface find(string address)
|
||||
{
|
||||
foreach (var nic in m_nics)
|
||||
{
|
||||
if ( nic.m_ip == address
|
||||
|| nic.m_ipv6 == address )
|
||||
{
|
||||
return nic;
|
||||
}
|
||||
}
|
||||
return new NetInterface();
|
||||
}
|
||||
|
||||
public string getIPAddresses() => m_ip_addresses;
|
||||
|
||||
public List<NetInterface> getNetInterfaces() => m_nics;
|
||||
}
|
||||
75
ServerCore/Pattern/Flags.cs
Normal file
75
ServerCore/Pattern/Flags.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using Axion.Collections.Concurrent;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 각종 시스템에 사용할 플래그 설정을 타입화 하여 관리할 수 있는 제네릭 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public class Flags<T>
|
||||
{
|
||||
private readonly ConcurrentHashSet<T> m_switchs = new();
|
||||
|
||||
public bool hasFlag(T flag)
|
||||
{
|
||||
return m_switchs.Contains(flag);
|
||||
}
|
||||
|
||||
public void setFlag(T flag, bool isEnable)
|
||||
{
|
||||
if (isEnable)
|
||||
{
|
||||
m_switchs.AddOrUpdate(flag);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_switchs.Remove(flag);
|
||||
}
|
||||
}
|
||||
|
||||
public bool isEmptyFlags()
|
||||
{
|
||||
return m_switchs.IsEmpty;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
m_switchs.Clear();
|
||||
}
|
||||
|
||||
public virtual Flags<T> clone()
|
||||
{
|
||||
var cloned = new Flags<T>();
|
||||
|
||||
foreach(var value in m_switchs)
|
||||
{
|
||||
cloned.getData().AddOrUpdate(value);
|
||||
}
|
||||
|
||||
return cloned;
|
||||
}
|
||||
|
||||
public virtual void deepCopy(Flags<T> other)
|
||||
{
|
||||
reset();
|
||||
|
||||
foreach (var value in other.getData())
|
||||
{
|
||||
m_switchs.AddOrUpdate(value);
|
||||
}
|
||||
}
|
||||
|
||||
public ConcurrentHashSet<T> getData() => m_switchs;
|
||||
}
|
||||
302
ServerCore/Pattern/HeirerchyNode.cs
Normal file
302
ServerCore/Pattern/HeirerchyNode.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 계층적으로 노드를 관리할 수 있는 기능을 제공하는 제네릭 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public interface IHeirerchy<T>
|
||||
{
|
||||
// 찾으려고 하는 값의 IHeirerchy<T> 를 찾는다.
|
||||
IHeirerchy<T>? findHeirerchy(T toFind);
|
||||
|
||||
// DepthNo 0 일경우 Root 이고 자식 계층이 추가될 경우 1 증가 된다.
|
||||
Int32 getDepthNo();
|
||||
|
||||
// 부모 계층을 수동으로 설정 한다.
|
||||
void setParent(IHeirerchy<T> parent);
|
||||
IHeirerchy<T>? getParent();
|
||||
|
||||
// 현재의 계층에 자식 노드을 추가 한다.
|
||||
IHeirerchy<T> addChildNode(IHeirerchy<T> child);
|
||||
// 현재의 계층에 자식 노드 목록을 반환 한다.
|
||||
List<IHeirerchy<T>> getChilds();
|
||||
|
||||
// 현재 노드에 값을 설정 한다.
|
||||
void setValue(T value);
|
||||
// 현재 노드의 값을 반환 한다.
|
||||
T getValue();
|
||||
|
||||
// 현재 계층 부터 부모의 Root 계층 까지 해당 T 타입인지 체크 한다.
|
||||
bool isUpperHeirerchy(T toCheck);
|
||||
|
||||
// 현재 계층 부터 자식의 Leaf 계층 까지 해당 T 타입인지 체크 한다.
|
||||
bool isLowerHeirerchy(T toCheck);
|
||||
|
||||
// 현재 노드가 Root 인가?
|
||||
bool isRoot();
|
||||
// 현재 노드가 최하위 노드 인가?
|
||||
bool isLeaf();
|
||||
|
||||
bool isEqual(T value);
|
||||
|
||||
// 현재 노드에서 자식 노드 계층의 모든 노드값의 체크해서 성공 & 실패 노드 목록을 반환 한다.
|
||||
bool onCheckValue(ref HashSet<T> successedNodes, ref List<IHeirerchy<T>> failedNodes);
|
||||
// 하위 노드 목록을 채워준다.
|
||||
void fillupLowers(ref List<T> lowers);
|
||||
}
|
||||
|
||||
public class HeirerchyNode<T> : IHeirerchy<T>
|
||||
{
|
||||
private T m_value;
|
||||
private IHeirerchy<T>? m_parent;
|
||||
private List<IHeirerchy<T>> m_childs = new();
|
||||
private Int32 m_depth_no = 0;
|
||||
|
||||
public HeirerchyNode(T value)
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
public HeirerchyNode(T value, IHeirerchy<T> parent)
|
||||
{
|
||||
m_value = value;
|
||||
|
||||
setParent(parent);
|
||||
}
|
||||
|
||||
public virtual bool onCheckValue(ref HashSet<T> successedNodes
|
||||
, ref List<IHeirerchy<T>> failedNodes)
|
||||
{
|
||||
var is_success = true;
|
||||
|
||||
foreach (var heirerchy_node in getChilds())
|
||||
{
|
||||
var value = heirerchy_node.getValue();
|
||||
if (true == successedNodes.Contains(value))
|
||||
{
|
||||
failedNodes.Add(heirerchy_node);
|
||||
is_success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
successedNodes.Add(value);
|
||||
|
||||
if (false == heirerchy_node.onCheckValue(ref successedNodes, ref failedNodes))
|
||||
{
|
||||
failedNodes.Add(heirerchy_node);
|
||||
is_success = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return is_success;
|
||||
}
|
||||
|
||||
public static IHeirerchy<T>? findByTraveral(IHeirerchy<T> root, T toFind)
|
||||
{
|
||||
if (true == root.isEqual(toFind))
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
foreach (var heirerchy_node in root.getChilds())
|
||||
{
|
||||
var found_heirerchy = heirerchy_node.findHeirerchy(toFind);
|
||||
if (null != found_heirerchy)
|
||||
{
|
||||
return found_heirerchy;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IHeirerchy<T>? findHeirerchy(T toFind)
|
||||
{
|
||||
if (true == isEqual(toFind))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
foreach (var heirerchy_node in getChilds())
|
||||
{
|
||||
var found_heirerchy = heirerchy_node.findHeirerchy(toFind);
|
||||
if (null != found_heirerchy)
|
||||
{
|
||||
return found_heirerchy;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IHeirerchy<T> newAndAddChildNode<THeirerchy>(T childValue) where THeirerchy : IHeirerchy<T>, new()
|
||||
{
|
||||
if (m_childs == null)
|
||||
{
|
||||
m_childs = new List<IHeirerchy<T>>();
|
||||
}
|
||||
|
||||
var to_add_child_node = new THeirerchy();
|
||||
to_add_child_node.setValue(childValue);
|
||||
|
||||
return addChildNode(to_add_child_node);
|
||||
}
|
||||
|
||||
public IHeirerchy<T> addChildNode(IHeirerchy<T> childNode)
|
||||
{
|
||||
if (m_childs == null)
|
||||
{
|
||||
m_childs = new List<IHeirerchy<T>>();
|
||||
}
|
||||
|
||||
m_childs.Add(childNode);
|
||||
childNode.setParent(this);
|
||||
|
||||
return childNode;
|
||||
}
|
||||
|
||||
public static IHeirerchy<T>? newAndAddChildNodeOfParent<THeirerchy>(IHeirerchy<T> root, T parentValue, T toAddChildValue) where THeirerchy : IHeirerchy<T>, new()
|
||||
{
|
||||
var found_parent_heirerchy = findByTraveral(root, parentValue);
|
||||
if (null == found_parent_heirerchy)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var found_node = found_parent_heirerchy as HeirerchyNode<T>;
|
||||
if (null == found_node)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return found_node.newAndAddChildNode<THeirerchy>(toAddChildValue);
|
||||
}
|
||||
|
||||
public List<IHeirerchy<T>> getChilds()
|
||||
{
|
||||
return m_childs == null ? (new List<IHeirerchy<T>>()) : m_childs;
|
||||
}
|
||||
|
||||
public IHeirerchy<T> getFirstChildNode()
|
||||
{
|
||||
return m_childs.First();
|
||||
}
|
||||
|
||||
public IHeirerchy<T> getLastChildNode()
|
||||
{
|
||||
return m_childs.Last();
|
||||
}
|
||||
|
||||
public IHeirerchy<T>? getChildNodeByIndex(Int32 index)
|
||||
{
|
||||
if (m_childs.Count <= index)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return m_childs[index];
|
||||
}
|
||||
|
||||
public virtual bool isUpperHeirerchy(T toCheck)
|
||||
{
|
||||
if (true == isEqual(toCheck))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var parent = getParent();
|
||||
if (null != parent)
|
||||
{
|
||||
if (true == parent.isUpperHeirerchy(toCheck))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool isLowerHeirerchy(T toCheck)
|
||||
{
|
||||
if (true == isEqual(toCheck))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var child_node in getChilds())
|
||||
{
|
||||
if (true == child_node.isLowerHeirerchy(toCheck))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// 하위 노드들 얻기
|
||||
public virtual void fillupLowers(ref List<T> lowers)
|
||||
{
|
||||
foreach (var child_node in getChilds())
|
||||
{
|
||||
lowers.Add(child_node.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public bool isRoot()
|
||||
{
|
||||
return getParent() == null;
|
||||
}
|
||||
|
||||
public bool isLeaf()
|
||||
{
|
||||
return getChilds().Count <= 0;
|
||||
}
|
||||
|
||||
public void setValue(T value)
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
public T getValue()
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
public bool isEqual(T value)
|
||||
{
|
||||
return m_value?.Equals(value) ?? false;
|
||||
}
|
||||
|
||||
public void setDepthNo(Int32 depthNo)
|
||||
{
|
||||
m_depth_no = depthNo;
|
||||
}
|
||||
|
||||
public Int32 getDepthNo()
|
||||
{
|
||||
return m_depth_no;
|
||||
}
|
||||
|
||||
public void setParent(IHeirerchy<T> parent)
|
||||
{
|
||||
m_parent = parent;
|
||||
|
||||
var depth_no = parent.getDepthNo() + 1;
|
||||
setDepthNo(depth_no);
|
||||
}
|
||||
|
||||
public IHeirerchy<T>? getParent()
|
||||
{
|
||||
return m_parent;
|
||||
}
|
||||
}
|
||||
30
ServerCore/Pattern/Singleton.cs
Normal file
30
ServerCore/Pattern/Singleton.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// T 타입 객체를 단일 인스턴스로 생성해 주는 제네릭 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public abstract class Singleton<T>
|
||||
where T : class, new()
|
||||
{
|
||||
// LazyThreadSafetyMode.ExecutionAndPublication : 멀티스레드 환경에서 처리시 안전하게 처리 한다 !!! (기본 설정)
|
||||
private static readonly Lazy<T> m_instance = new(() => new T());
|
||||
|
||||
public static T It => m_instance.Value;
|
||||
|
||||
public static string getName()
|
||||
{
|
||||
return nameof(T);
|
||||
}
|
||||
}
|
||||
71
ServerCore/ProudNet/ProudNetHelper.cs
Normal file
71
ServerCore/ProudNet/ProudNetHelper.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using Google.Protobuf;
|
||||
using Nettention.Proud;
|
||||
|
||||
|
||||
|
||||
using SESSION_ID = System.Int32;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
|
||||
//=============================================================================================
|
||||
// ProudNet 관련 각종 지원 함수
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public static class ProudNetHelper
|
||||
{
|
||||
private static RmiContext? m_rmi;
|
||||
|
||||
public static RmiContext compressRmi()
|
||||
{
|
||||
if (m_rmi == null)
|
||||
{
|
||||
m_rmi = RmiContext.ReliableSend.Clone();
|
||||
m_rmi.compressMode = CompressMode.CM_None;
|
||||
}
|
||||
return m_rmi;
|
||||
}
|
||||
|
||||
public static SESSION_ID getSessionId(this NetClientInfo netClientInfo)
|
||||
{
|
||||
return (SESSION_ID)netClientInfo.hostID;
|
||||
}
|
||||
|
||||
public static HostID toHostID(this SESSION_ID sessionId)
|
||||
{
|
||||
return (HostID)sessionId;
|
||||
}
|
||||
|
||||
public static SESSION_ID toSESSION_ID(this HostID hostId)
|
||||
{
|
||||
return (SESSION_ID)hostId;
|
||||
}
|
||||
|
||||
public static string getNetClientPublicIp(this Nettention.Proud.NetServer netServer, HostID hostId)
|
||||
{
|
||||
var client_public_ip = string.Empty;
|
||||
|
||||
var found_client_info = netServer.GetClientInfo(hostId);
|
||||
if (null == found_client_info)
|
||||
{
|
||||
Log.getLogger().debug($"Not found ProudNet.NetClientInfo !!! : HostId:{hostId}");
|
||||
}
|
||||
else
|
||||
{
|
||||
client_public_ip = found_client_info.tcpAddrFromServer.IPToString();
|
||||
}
|
||||
|
||||
return client_public_ip;
|
||||
}
|
||||
}
|
||||
180
ServerCore/RabbitMQ/RabbitMqConnectorBase.cs
Normal file
180
ServerCore/RabbitMQ/RabbitMqConnectorBase.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public abstract class RabbitMQConnectorBase
|
||||
{
|
||||
private readonly string m_service_name;
|
||||
private IConnection? m_connection = null;
|
||||
|
||||
private IModel? m_default_channel;
|
||||
private CancellationTokenSource m_cts = new();
|
||||
private TaskSerializer m_task_serializer = new();
|
||||
|
||||
private Int32 m_next_req_id = 1;
|
||||
private readonly Dictionary<int, (TaskCompletionSource<object>, Action<Task<object>>)> m_tcs = new();
|
||||
|
||||
private readonly Dictionary<string, IModel> m_exchange_channels = new();
|
||||
|
||||
|
||||
public RabbitMQConnectorBase( string serviceName
|
||||
, string address, int port
|
||||
, string username, string password
|
||||
, bool useSSL )
|
||||
{
|
||||
m_service_name = serviceName;
|
||||
|
||||
try
|
||||
{
|
||||
var factory = new ConnectionFactory()
|
||||
{
|
||||
HostName = address,
|
||||
Port = port,
|
||||
UserName = username,
|
||||
Password = password,
|
||||
Ssl = { ServerName = address, Enabled = useSSL, Version = System.Security.Authentication.SslProtocols.Tls12 },
|
||||
};
|
||||
|
||||
m_connection = factory.CreateConnection();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, Failed to perform in RabbitMQConnectorBase() !!! : exception:{e}"
|
||||
+ $" - servieName:{serviceName}, address:{address}, port:{port}, userName:{username}, password:{password}, useSsl:{useSSL}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
public int nextReqId()
|
||||
{
|
||||
return Interlocked.Increment(ref m_next_req_id);
|
||||
}
|
||||
|
||||
public virtual bool startConsumer()
|
||||
{
|
||||
if(null == m_connection)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_default_channel = m_connection.CreateModel();
|
||||
var declare = m_default_channel.QueueDeclare( queue: m_service_name,
|
||||
durable: true,
|
||||
exclusive: false,
|
||||
autoDelete: true,
|
||||
arguments: null );
|
||||
|
||||
var consumer = new EventingBasicConsumer(m_default_channel);
|
||||
|
||||
consumer.Received += onRecvJsonMessageFromConsumer;
|
||||
//consumer.Received += onRecvProtoMessageFromConsumer;
|
||||
|
||||
var consume_result = m_default_channel.BasicConsume( queue: m_service_name,
|
||||
autoAck: true,
|
||||
consumer: consumer );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void stop()
|
||||
{
|
||||
m_cts.Cancel();
|
||||
|
||||
foreach(var channel in m_exchange_channels.Values)
|
||||
{
|
||||
channel.Dispose();
|
||||
}
|
||||
|
||||
m_default_channel?.Dispose();
|
||||
m_connection?.Dispose();
|
||||
}
|
||||
|
||||
public IModel? createExchangeChannel( string exchangeName, string exchangeType)
|
||||
{
|
||||
if( true == m_exchange_channels.ContainsKey(exchangeName) )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if( null == m_connection )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var to_add_channel = m_connection?.CreateModel();
|
||||
if(null == to_add_channel)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
to_add_channel.ExchangeDeclare(exchange: exchangeName, type: exchangeType);
|
||||
|
||||
var queueName = to_add_channel.QueueDeclare(autoDelete: true).QueueName;
|
||||
to_add_channel.QueueBind( queue: queueName,
|
||||
exchange: exchangeName,
|
||||
routingKey: "" );
|
||||
|
||||
var consumer = new EventingBasicConsumer(to_add_channel);
|
||||
|
||||
consumer.Received += onRecvJsonMessageFromConsumer;
|
||||
//consumer.Received += onRecvProtoMessageFromConsumer;
|
||||
|
||||
to_add_channel.BasicConsume( queue: queueName,
|
||||
autoAck: true,
|
||||
consumer: consumer );
|
||||
|
||||
m_exchange_channels.Add(exchangeName, to_add_channel);
|
||||
|
||||
return to_add_channel;
|
||||
}
|
||||
|
||||
protected abstract void onRecvProtoMessageFromConsumer(object? sender, BasicDeliverEventArgs ea);
|
||||
|
||||
protected abstract void onRecvJsonMessageFromConsumer(object? sender, BasicDeliverEventArgs ea);
|
||||
|
||||
|
||||
public Task<object>? registerCompletionSource(Int32 reqId, CancellationToken cancelToken, Action<Task<object>> callback)
|
||||
{
|
||||
if (m_tcs.TryGetValue(reqId, out var taskCompletionSource) == true)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var task_cs = new TaskCompletionSource<object>();
|
||||
|
||||
cancelToken.Register(() =>
|
||||
{
|
||||
task_cs.TrySetCanceled();
|
||||
callback(task_cs.Task);
|
||||
});
|
||||
|
||||
m_tcs.Add(reqId, (task_cs, callback));
|
||||
|
||||
return task_cs.Task;
|
||||
}
|
||||
|
||||
public IModel? getExchangeChannel(string exchangeName)
|
||||
{
|
||||
m_exchange_channels.TryGetValue(exchangeName, out var found_model);
|
||||
return found_model;
|
||||
}
|
||||
|
||||
public IModel? getDefaultChannel() => m_default_channel;
|
||||
|
||||
public Dictionary<int, (TaskCompletionSource<object>, Action<Task<object>>)> getTaskCompletionSources() => m_tcs;
|
||||
|
||||
public TaskSerializer getTaskSerializer() => m_task_serializer;
|
||||
|
||||
public IConnection? getConnection() => m_connection;
|
||||
|
||||
public string getServiceName() => m_service_name;
|
||||
}
|
||||
169
ServerCore/Random/RandomHelper.cs
Normal file
169
ServerCore/Random/RandomHelper.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public static class RandomHelper
|
||||
{
|
||||
// 만분율을 기본 비율단위로 사용 한다 !!!
|
||||
public static readonly Int32 DefaultRatio = 10000;
|
||||
private static Random random = new Random();
|
||||
|
||||
public static double nextDouble(double min, double max)
|
||||
{
|
||||
return random.NextDouble() * (max - min) + min;
|
||||
}
|
||||
|
||||
public static double rangeDouble(double min, double max)
|
||||
{
|
||||
return random.NextDouble() * (max - min) + min;
|
||||
}
|
||||
|
||||
public static string randomString(Int32 length)
|
||||
{
|
||||
const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
|
||||
}
|
||||
|
||||
public static string randomName(Int32 length)
|
||||
{
|
||||
string[] consonants = { "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "sh", "zh", "t", "v", "w", "x", "z" };
|
||||
string[] vowels = { "a", "e", "i", "o", "u", "ae", "y" };
|
||||
StringBuilder Name = new StringBuilder();
|
||||
Name.Append(consonants[random.Next(consonants.Length)].ToUpper());
|
||||
Name.Append(vowels[random.Next(vowels.Length)]);
|
||||
Int32 idx = 2;
|
||||
while (idx < length)
|
||||
{
|
||||
Name.Append(consonants[random.Next(consonants.Length)]);
|
||||
Name.Append(vowels[random.Next(vowels.Length)]);
|
||||
idx += 2;
|
||||
if (length - idx == 1)
|
||||
{
|
||||
Name.Append(consonants[random.Next(consonants.Length)]);
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
return Name.ToString();
|
||||
}
|
||||
|
||||
public static Int32 number(Int32 max)
|
||||
{
|
||||
Int32 val = random.Next(0, max);
|
||||
return val;
|
||||
}
|
||||
|
||||
public static Int32 range(Int32 min, Int32 max)
|
||||
{
|
||||
Int32 val = random.Next(min, max);
|
||||
return val;
|
||||
}
|
||||
// 만분율 기반 확률 계산
|
||||
public static bool defaultProb(Int32 target_prob)
|
||||
{
|
||||
Int32 prob = random.Next(1, (Int32)DefaultRatio + 1);
|
||||
|
||||
return prob < target_prob;
|
||||
}
|
||||
// 만분율 기반 확률 계산
|
||||
public static bool defaultProb(Int64 target_prob)
|
||||
{
|
||||
return defaultProb((Int32)target_prob);
|
||||
}
|
||||
// min 과 max를 포함한 사잇값
|
||||
public static Int32 between(Int32 min, Int32 max)
|
||||
{
|
||||
return random.Next(min, max + 1);
|
||||
}
|
||||
|
||||
public static Int32 between(Int64 min, Int64 max)
|
||||
{
|
||||
return random.Next((Int32)min, (Int32)max + 1);
|
||||
}
|
||||
|
||||
public static Int32 next()
|
||||
{
|
||||
return random.Next();
|
||||
}
|
||||
|
||||
public static Int32 next(Int32 min, Int32 max)
|
||||
{
|
||||
return random.Next(min, max);
|
||||
}
|
||||
|
||||
public static float next(float min, float max)
|
||||
{
|
||||
Int32 min_int = (Int32)(min * DefaultRatio);
|
||||
Int32 max_int = (Int32)(max * DefaultRatio);
|
||||
|
||||
return (float)(between(min_int, max_int) / DefaultRatio);
|
||||
}
|
||||
|
||||
public static Int32 next(Int32 max)
|
||||
{
|
||||
return random.Next(max);
|
||||
}
|
||||
|
||||
public static void shuffleList<T>(IList<T> list)
|
||||
{
|
||||
Int32 count = list.Count;
|
||||
while (count > 1)
|
||||
{
|
||||
Int32 key = random.Next(count);
|
||||
T value = list[key];
|
||||
list[key] = list[count - 1];
|
||||
list[count - 1] = value;
|
||||
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Int32> randomIndex(Int32 count)
|
||||
{
|
||||
List<Int32> index_list = new List<Int32>();
|
||||
for (Int32 i = 0; i < count; ++i)
|
||||
{
|
||||
index_list.Add(i);
|
||||
}
|
||||
|
||||
shuffleList<Int32>(index_list);
|
||||
return index_list;
|
||||
}
|
||||
|
||||
public static Vector3 randomPointInCicle(Vector3 centerPos, float radius)
|
||||
{
|
||||
var angle = random.NextDouble() * Math.PI * 2;
|
||||
var rad = random.NextDouble() * radius;
|
||||
|
||||
float x = (float)(centerPos.X + rad * Math.Cos(angle));
|
||||
float z = (float)(centerPos.Z + rad * Math.Sin(angle));
|
||||
|
||||
return new Vector3(x, centerPos.Y, z);
|
||||
}
|
||||
|
||||
public static Vector3 randomPointInFanShape(Vector3 centerPos, float radius, float degree)
|
||||
{
|
||||
var angle = degree * MathHelper.Deg2Rad;
|
||||
var rad = random.NextDouble() * radius;
|
||||
|
||||
float x = (float)(centerPos.X + rad * Math.Cos(angle));
|
||||
float z = (float)(centerPos.Z + rad * Math.Sin(angle));
|
||||
|
||||
return new Vector3(x, centerPos.Y, z);
|
||||
}
|
||||
|
||||
public static Vector3 randomPointInRectangle(Vector3 centerPos, float xScale, float zScale)
|
||||
{
|
||||
var x = centerPos.X + (float)rangeDouble(centerPos.X - (xScale * 0.5), xScale + (xScale * 0.5));
|
||||
var z = centerPos.Z + (float)rangeDouble(centerPos.Z - (zScale * 0.5), zScale + (zScale * 0.5));
|
||||
|
||||
return new Vector3(x, centerPos.Y, z);
|
||||
}
|
||||
}
|
||||
258
ServerCore/Redis/RedisConnectorBase.cs
Normal file
258
ServerCore/Redis/RedisConnectorBase.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
||||
using Renci.SshNet;
|
||||
using StackExchange.Redis;
|
||||
using StackExchange.Redis.Extensions.Core;
|
||||
using StackExchange.Redis.Extensions.Core.Abstractions;
|
||||
using StackExchange.Redis.Extensions.Core.Configuration;
|
||||
using StackExchange.Redis.Extensions.Core.Implementations;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public abstract class RedisConnectorBase
|
||||
{
|
||||
private readonly string m_connection_string = string.Empty;
|
||||
private readonly RedisConfiguration m_redis_config;
|
||||
|
||||
private ConnectionMultiplexer? m_connector;
|
||||
|
||||
private SshClient? m_ssh_tuneling;
|
||||
private ForwardedPortLocal? m_forwarded_port_local;
|
||||
|
||||
public RedisConnectorBase(string connectionString)
|
||||
{
|
||||
m_redis_config = new RedisConfiguration()
|
||||
{
|
||||
ConnectionString = connectionString,
|
||||
};
|
||||
|
||||
m_connection_string = connectionString;
|
||||
}
|
||||
|
||||
public async Task<bool> tryConnectAsync()
|
||||
{
|
||||
if (false == isConfigureConnectionInfo())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (false == isConnected())
|
||||
{
|
||||
ConnectionMultiplexer.SetFeatureFlag("preventthreadtheft", true);
|
||||
var config_options = ConfigurationOptions.Parse(m_connection_string);
|
||||
|
||||
var connection = await ConnectionMultiplexer.ConnectAsync(config_options);
|
||||
if (null == connection)
|
||||
{
|
||||
var err_msg = $"Failed to connectAsync RedisServer !!! : ConnectString:{m_connection_string}";
|
||||
throw new Exception(err_msg);
|
||||
}
|
||||
|
||||
m_connector = connection;
|
||||
|
||||
connection.ConnectionFailed += onConnectfailed;
|
||||
connection.ErrorMessage += onError;
|
||||
connection.InternalError += onInternalError;
|
||||
connection.ConnectionRestored += onConnectionRestored;
|
||||
connection.ConfigurationChanged += onConfigurationChanged;
|
||||
connection.ConfigurationChangedBroadcast += onMasterSlaveChanged;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, Failed to perform in tryConnectAsync() !!! : exception:{e}, connectionString:{m_connection_string} - {toBasicString()}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void setConnectionMultiplexer(ConnectionMultiplexer connector) => m_connector = connector;
|
||||
|
||||
public bool tryConnect()
|
||||
{
|
||||
if (false == isConfigureConnectionInfo())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (false == isConnected())
|
||||
{
|
||||
var config_options = ConfigurationOptions.Parse(m_connection_string);
|
||||
|
||||
var connection = ConnectionMultiplexer.Connect(config_options);
|
||||
if (null == connection)
|
||||
{
|
||||
var err_msg = $"Failed to connect RedisServer !!! : ConnectString:{m_connection_string}";
|
||||
throw new Exception(err_msg);
|
||||
}
|
||||
|
||||
m_connector = connection;
|
||||
|
||||
connection.ConnectionFailed += onConnectfailed;
|
||||
connection.ErrorMessage += onError;
|
||||
connection.InternalError += onInternalError;
|
||||
connection.ConnectionRestored += onConnectionRestored;
|
||||
connection.ConfigurationChanged += onConfigurationChanged;
|
||||
connection.ConfigurationChangedBroadcast += onMasterSlaveChanged;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, try connectAsync failed !!! : Exception:{e} - {toBasicString()}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// sshHost : ec2-54-69-223-245.us-west-2.compute.amazonaws.com // SSH 서버 (Jump Host or Bastion)
|
||||
// sshPort : 2211 // SSH 포트
|
||||
// sshUsername : "ubuntu" // SSH 사용자 이름
|
||||
// privateKeyPath : @"D:\SVN\Caliverse\Metaverse\trunk\Caliverse\Server\Security\SSH-Keys\pem\USWest2-KeyPair.pem" // Private Key 보안 파일 참조 경로
|
||||
// localPort : 16379 // 로컬에서 사용할 포트
|
||||
// redisHost : "clustercfg.metaverse-qa-cluster.ocif0u.usw2.cache.amazonaws.com" // Redis 클러스터 호스트
|
||||
// redisPort : 6379 // Redis 사용 포트
|
||||
// redisPassword : "wiUaVvNwX4PhBj&8"
|
||||
//=========================================================================================
|
||||
public bool startTuneling( string sshHost, int sshPort, string sshUserName, string privateKeyPath
|
||||
, int localPort, string redisHost, int redisPort, string redisPassword )
|
||||
{
|
||||
// SSH Private Key 설정
|
||||
PrivateKeyFile privateKey = new PrivateKeyFile(privateKeyPath);
|
||||
var keyFiles = new[] { privateKey };
|
||||
|
||||
try
|
||||
{
|
||||
var ssh_client = new SshClient(sshHost, sshPort, sshUserName, keyFiles);
|
||||
|
||||
// SSH 연결 시도
|
||||
ssh_client.Connect();
|
||||
|
||||
// 포트 포워딩 설정 (로컬 포트 -> 원격 Redis 호스트)
|
||||
var portForward = new ForwardedPortLocal("127.0.0.1", (uint)localPort, redisHost, (uint)redisPort);
|
||||
ssh_client.AddForwardedPort(portForward);
|
||||
|
||||
portForward.Start();
|
||||
|
||||
m_forwarded_port_local = portForward;
|
||||
m_ssh_tuneling = ssh_client;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, Failed to function in startTuneling() !!! : Exception:{e}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void stopTuneling()
|
||||
{
|
||||
if (null != m_forwarded_port_local)
|
||||
{
|
||||
m_forwarded_port_local.Stop();
|
||||
m_forwarded_port_local = null;
|
||||
}
|
||||
|
||||
if(null != m_ssh_tuneling)
|
||||
{
|
||||
// SSH 연결 종료
|
||||
m_ssh_tuneling.Disconnect();
|
||||
m_ssh_tuneling = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool isConfigureConnectionInfo()
|
||||
{
|
||||
if ( 0 >= m_connection_string.Length )
|
||||
{
|
||||
var err_msg = $"Invalid Configure of RedisConnector, Empty m_connectionString !!! - {toBasicString()}";
|
||||
Log.getLogger().error(err_msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool isConnected()
|
||||
{
|
||||
if( null == m_connector
|
||||
|| false == m_connector.IsConnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public IDatabase? getDatabase()
|
||||
{
|
||||
if(false == isConnected())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return m_connector?.GetDatabase();
|
||||
}
|
||||
|
||||
public ConnectionMultiplexer? getConnectionMultiplexer() => m_connector;
|
||||
|
||||
#region event
|
||||
protected virtual void onConnectionRestored(object? sender, ConnectionFailedEventArgs e)
|
||||
{
|
||||
Log.getLogger().warn($"Redis connection restored !!! : {e} - {toBasicString()}");
|
||||
}
|
||||
|
||||
protected void onConnectfailed(object? sender, ConnectionFailedEventArgs e)
|
||||
{
|
||||
Log.getLogger().error($"Redis connect failed !!! : ConnectionFailed:{e.Exception} - {toBasicString()}");
|
||||
}
|
||||
|
||||
protected void onError(object? sender, RedisErrorEventArgs e)
|
||||
{
|
||||
Log.getLogger().error($"Redis connection restored !!! : RedisError:{e} - {toBasicString()}");
|
||||
}
|
||||
|
||||
protected void onInternalError(object? sender, InternalErrorEventArgs e)
|
||||
{
|
||||
Log.getLogger().error($"Redis internal error !!! : InternalError:{e} - {toBasicString()}");
|
||||
}
|
||||
|
||||
protected void onConfigurationChanged(object? sender, EndPointEventArgs e)
|
||||
{
|
||||
Log.getLogger().warn($"Redis configuration changed !!! : EndPoint:{e} - {toBasicString()}");
|
||||
}
|
||||
|
||||
protected void onMasterSlaveChanged(object? sender, EndPointEventArgs e)
|
||||
{
|
||||
Log.getLogger().warn($"Redis changed master-slave !!! : EndPoint:{e.EndPoint} - {toBasicString()}");
|
||||
}
|
||||
#endregion event
|
||||
|
||||
|
||||
public string toBasicString()
|
||||
{
|
||||
return $"RedisConnector - Class:{this.getTypeName()}, ConnectString:{m_connection_string}";
|
||||
}
|
||||
}
|
||||
39
ServerCore/Redis/RedisTransaction.cs
Normal file
39
ServerCore/Redis/RedisTransaction.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using StackExchange.Redis;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public class RedisTransaction : IDisposable
|
||||
{
|
||||
private readonly ITransaction m_transaction;
|
||||
private bool m_committed = false;
|
||||
|
||||
public RedisTransaction(IDatabase db)
|
||||
{
|
||||
m_transaction = db.CreateTransaction();
|
||||
}
|
||||
|
||||
public async Task commitAsync()
|
||||
{
|
||||
m_committed = await m_transaction.ExecuteAsync();
|
||||
}
|
||||
|
||||
// Dispose에서 트랜잭션 해제 처리
|
||||
public void Dispose()
|
||||
{
|
||||
if (false == m_committed)
|
||||
{
|
||||
// 트랜잭션을 취소
|
||||
m_transaction.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
public ITransaction getTransaction() => m_transaction;
|
||||
}
|
||||
98
ServerCore/Redis/RedisWithLuaScriptExecutorBase.cs
Normal file
98
ServerCore/Redis/RedisWithLuaScriptExecutorBase.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using StackExchange.Redis;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// Server-side transactional scripting in Redis
|
||||
//
|
||||
//=============================================================================================
|
||||
public abstract class RedisWithLuaScriptExecutorBase
|
||||
{
|
||||
private readonly RedisConnectorBase m_connector;
|
||||
|
||||
private readonly ConcurrentDictionary<string, LoadedLuaScript> m_loaded_lua_scripts = new();
|
||||
|
||||
public RedisWithLuaScriptExecutorBase(RedisConnectorBase connector)
|
||||
{
|
||||
m_connector = connector;
|
||||
}
|
||||
|
||||
public async Task<bool> tryLoadLuaScriptToRedisServer(string scriptName, string luaScript)
|
||||
{
|
||||
var err_msg = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
if (true == m_loaded_lua_scripts.ContainsKey(scriptName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var multiplexer = m_connector.getConnectionMultiplexer();
|
||||
NullReferenceCheckHelper.throwIfNull(multiplexer, () => $"multiplexer is null !!!");
|
||||
|
||||
var end_points = multiplexer.GetEndPoints();
|
||||
NullReferenceCheckHelper.throwIfNull(end_points, () => $"end_points is null !!!");
|
||||
|
||||
var curr_server = multiplexer.GetServer(end_points[0]);
|
||||
NullReferenceCheckHelper.throwIfNull(curr_server, () => $"curr_server is null !!!");
|
||||
|
||||
var prepared_lug_script = LuaScript.Prepare(luaScript);
|
||||
NullReferenceCheckHelper.throwIfNull(prepared_lug_script, () => $"prepared_lug_script is null !!!");
|
||||
|
||||
var uploaded_lua_script = await prepared_lug_script.LoadAsync(curr_server);
|
||||
NullReferenceCheckHelper.throwIfNull(uploaded_lua_script, () => $"uploaded_lua_script is null !!!");
|
||||
|
||||
m_loaded_lua_scripts[scriptName] = uploaded_lua_script;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
err_msg = $"Exception !!!, Failed to function in tryLoadScriptAsync() !!! : exception:{e}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<(bool, RedisResult?)> tryExecuteLuaScriptAsync(string scriptName, RedisKey[] keys, RedisValue[]? args = null)
|
||||
{
|
||||
RedisResult? redis_result = null;
|
||||
var err_msg = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var connected_redis_db = m_connector.getDatabase();
|
||||
NullReferenceCheckHelper.throwIfNull(connected_redis_db, () => $"connected_redis_db is null !!!");
|
||||
|
||||
if (false == m_loaded_lua_scripts.TryGetValue(scriptName, out var found_lua_script))
|
||||
{
|
||||
err_msg = $"Not found LuaScript !!! : scriptName:{scriptName}";
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
var script = found_lua_script.ExecutableScript;
|
||||
|
||||
redis_result = await connected_redis_db.ScriptEvaluateAsync(script, keys, args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
err_msg = $"Exception !!!, Failed to ScriptEvaluateAsync() !!! : exception:{e} - LuaScriptName:{scriptName}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
return (true, redis_result);
|
||||
}
|
||||
}
|
||||
886
ServerCore/Redis/RedisWithLuaScriptExecutorHelper.cs
Normal file
886
ServerCore/Redis/RedisWithLuaScriptExecutorHelper.cs
Normal file
@@ -0,0 +1,886 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
using Amazon.DynamoDBv2.DocumentModel;
|
||||
using Renci.SshNet.Security;
|
||||
using StackExchange.Redis;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public static class RedisWithLuaScriptExecutorHelper
|
||||
{
|
||||
//=============================================================================================
|
||||
// 랭킹 맴버를 추가하고 최고 순위자를 반환하고 차순위자들을 제거하지 않는다. - kangms
|
||||
//=============================================================================================
|
||||
public static async Task<(bool, string, double, string, double, bool, int)> tryPlaceTopRank( this RedisWithLuaScriptExecutorBase luaScriptExecutor
|
||||
, string rankKey, string memberId, double memberPoint
|
||||
, string blockKey )
|
||||
{
|
||||
// Lua 스크립트 정의
|
||||
var lua_script_name = "placeTopRank";
|
||||
var lua_script = @"
|
||||
local blockKey = KEYS[2]
|
||||
local rankKey = KEYS[1]
|
||||
local rankerId = ARGV[1]
|
||||
local rankerPoint = tonumber(ARGV[2])
|
||||
|
||||
local blockKeyExists = redis.call('EXISTS', blockKey)
|
||||
if blockKeyExists == 1 then
|
||||
return { 1, '', 0, '', 0 }
|
||||
end
|
||||
|
||||
redis.call('ZADD', rankKey, rankerPoint, rankerId)
|
||||
|
||||
local currentHighestRanker = redis.call('ZREVRANGE', rankKey, 0, 0, 'WITHSCORES')
|
||||
local result = {}
|
||||
local rankerTotalCount = redis.call('ZCARD', rankKey)
|
||||
|
||||
if currentHighestRanker[1] ~= nil then
|
||||
local currentHighestPoint = tonumber(currentHighestRanker[2])
|
||||
local currentHighestRankerId = currentHighestRanker[1]
|
||||
|
||||
if rankerPoint > currentHighestPoint then
|
||||
rankerTotalCount = redis.call('ZCARD', rankKey)
|
||||
result = { 0, rankerId, rankerPoint, currentHighestRankerId, currentHighestPoint, rankerTotalCount }
|
||||
else
|
||||
result = { 0, currentHighestRankerId, currentHighestPoint, '', 0, rankerTotalCount }
|
||||
end
|
||||
else
|
||||
totalRankers = redis.call('ZCARD', rankKey)
|
||||
result = { 0, rankerId, rankerPoint, '', 0, rankerTotalCount }
|
||||
end
|
||||
|
||||
return result
|
||||
";
|
||||
|
||||
var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script);
|
||||
if(false == is_success)
|
||||
{
|
||||
return (false, string.Empty, 0, string.Empty, 0, false, 0);
|
||||
}
|
||||
|
||||
bool is_blocked = false;
|
||||
string curr_ranker_id = string.Empty;
|
||||
double curr_ranker_point = 0;
|
||||
string old_ranker_id = string.Empty;
|
||||
double old_ranker_point = 0;
|
||||
bool is_top_change = false;
|
||||
int ranker_count = 0;
|
||||
|
||||
try
|
||||
{
|
||||
// 루아 스크립트 실행, KEYS[1]에 rankKey, ARGV[1]에 memberId, ARGV[2]에 memberPoint 전달
|
||||
(is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name
|
||||
, new RedisKey[] { rankKey, blockKey }
|
||||
, new RedisValue[] { memberId, memberPoint });
|
||||
if (false == is_success)
|
||||
{
|
||||
Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : rankkey:{rankKey}, memberId:{memberId}, memberPoint:{memberPoint} - LuaScriptName:{lua_script_name}");
|
||||
|
||||
return (false, string.Empty, 0, string.Empty, 0, is_top_change, 0);
|
||||
}
|
||||
NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!");
|
||||
|
||||
// 반환된 Lua 스크립트 결과: 최고가 입찰자 정보, 차순위 입찰자 정보, 순위자 개수
|
||||
is_blocked = (false == redis_result[0].IsNull) ? (bool)redis_result[0] : true;
|
||||
curr_ranker_id = (false == redis_result[1].IsNull) ? redis_result[1].ToString() : string.Empty;
|
||||
curr_ranker_point = (false == redis_result[2].IsNull) ? (double)redis_result[2] : 0;
|
||||
old_ranker_id = (false == redis_result[3].IsNull) ? redis_result[3].ToString() : string.Empty;
|
||||
old_ranker_point = (false == redis_result[4].IsNull) ? (double)redis_result[4] : 0;
|
||||
ranker_count = (false == redis_result[5].IsNull) ? (int)redis_result[5] : 0;
|
||||
|
||||
if (true == is_blocked)
|
||||
{
|
||||
var err_msg = $"Raking is Blocked !!! : rankkey:{rankKey}, memberId:{memberId}, memberPoint:{memberPoint}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().warn(err_msg);
|
||||
|
||||
return (false, curr_ranker_id, curr_ranker_point, old_ranker_id, old_ranker_point, is_top_change, ranker_count);
|
||||
}
|
||||
|
||||
// 탑랭커 변경 여부
|
||||
if (false == old_ranker_id.isNullOrWhiteSpace()
|
||||
&& curr_ranker_id != old_ranker_id)
|
||||
{
|
||||
is_top_change = true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, rankKey:{rankKey}, memberId:{memberId}, memberPoint:{memberPoint}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return (false, string.Empty, 0, string.Empty, 0, is_top_change, 0);
|
||||
}
|
||||
|
||||
return (true, curr_ranker_id, curr_ranker_point, old_ranker_id, old_ranker_point, is_top_change, ranker_count);
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 랭킹 맴버를 추가하고 최고 순위자와 직전 최고 순위자가 있다면 반환 하며, 차순위자는 제거 한다. - kangms
|
||||
//=============================================================================================
|
||||
public static async Task<(bool, string, double, string, double, bool)> tryPlaceTopRankOnly( this RedisWithLuaScriptExecutorBase luaScriptExecutor
|
||||
, string rankKey, string memberId, double memberPoint
|
||||
, string blockKey )
|
||||
{
|
||||
// Lua 스크립트 정의
|
||||
var lua_script_name = "placeTopRankOnly";
|
||||
var lua_script = @"
|
||||
local blockKey = KEYS[2]
|
||||
local rankKey = KEYS[1]
|
||||
local rankerId = ARGV[1]
|
||||
local rankerPoint = tonumber(ARGV[2])
|
||||
|
||||
local blockKeyExists = redis.call('EXISTS', blockKey)
|
||||
if blockKeyExists == 1 then
|
||||
return { 1, '', 0, '', 0, 0 }
|
||||
end
|
||||
|
||||
local currentHighestRanker = redis.call('ZREVRANGE', rankKey, 0, 0, 'WITHSCORES')
|
||||
local result = {}
|
||||
|
||||
if currentHighestRanker[1] ~= nil then
|
||||
local currentHighestPoint = tonumber(currentHighestRanker[2])
|
||||
local currentHighestRankerId = currentHighestRanker[1]
|
||||
|
||||
if rankerPoint > currentHighestPoint then
|
||||
redis.call('ZREM', rankKey, currentHighestRankerId)
|
||||
redis.call('ZADD', rankKey, rankerPoint, rankerId)
|
||||
result = { 0, rankerId, rankerPoint, currentHighestRankerId, currentHighestPoint, 1 }
|
||||
else
|
||||
result = { 0, currentHighestRankerId, currentHighestPoint, '', 0, 0 }
|
||||
end
|
||||
else
|
||||
redis.call('ZADD', rankKey, rankerPoint, rankerId)
|
||||
result = { 0, rankerId, rankerPoint, '', 0, 1 }
|
||||
end
|
||||
|
||||
return result
|
||||
";
|
||||
|
||||
var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script);
|
||||
if (false == is_success)
|
||||
{
|
||||
return (false, string.Empty, 0, string.Empty, 0, false);
|
||||
}
|
||||
|
||||
bool is_blocked = false;
|
||||
string curr_ranker_id = string.Empty;
|
||||
double curr_ranker_point = 0;
|
||||
string old_ranker_id = string.Empty;
|
||||
double old_ranker_point = 0;
|
||||
bool is_top_change = false; // 첫번째로 등록되는 랭커일 경우에도 true 로 반환 !!!
|
||||
|
||||
try
|
||||
{
|
||||
// 루아 스크립트 실행, KEYS[1]에 rankKey, ARGV[1]에 memberId, ARGV[2]에 memberPoint 전달
|
||||
(is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name
|
||||
, new RedisKey[] { rankKey, blockKey }
|
||||
, new RedisValue[] { memberId, memberPoint });
|
||||
if (false == is_success)
|
||||
{
|
||||
Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : rankkey:{rankKey}, memberId:{memberId}, memberPoint:{memberPoint}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}");
|
||||
|
||||
return (false, string.Empty, 0, string.Empty, 0, false);
|
||||
}
|
||||
NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!");
|
||||
|
||||
// 반환된 Lua 스크립트 결과: 최고가 입찰자 정보, 차순위 입찰자 정보
|
||||
is_blocked = (false == redis_result[0].IsNull) ? (bool)redis_result[0] : true;
|
||||
curr_ranker_id = (false == redis_result[1].IsNull) ? redis_result[1].ToString() : string.Empty;
|
||||
curr_ranker_point = (false == redis_result[2].IsNull) ? (double)redis_result[2] : 0;
|
||||
old_ranker_id = (false == redis_result[3].IsNull) ? redis_result[3].ToString() : string.Empty;
|
||||
old_ranker_point = (false == redis_result[4].IsNull) ? (double)redis_result[4] : 0;
|
||||
is_top_change = (false == redis_result[5].IsNull) ? (bool)redis_result[5] : false;
|
||||
|
||||
if (true == is_blocked)
|
||||
{
|
||||
var err_msg = $"Raking is Blocked !!! : rankkey:{rankKey}, memberId:{memberId}, memberPoint:{memberPoint}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().warn(err_msg);
|
||||
|
||||
return (false, curr_ranker_id, curr_ranker_point, old_ranker_id, old_ranker_point, is_top_change);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, rankKey:{rankKey}, memberId:{memberId}, memberPoint:{memberPoint} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return (false, string.Empty, 0, string.Empty, 0, false);
|
||||
}
|
||||
|
||||
|
||||
return (true, curr_ranker_id, curr_ranker_point, old_ranker_id, old_ranker_point, is_top_change);
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 탑랭커 정보를 반환하고, 랭킹 정보를 블록 상태로 설정한다. - kangms
|
||||
//=============================================================================================
|
||||
public static async Task<(bool, bool, string, double)> tryGetTopRankAndSetBlock( this RedisWithLuaScriptExecutorBase luaScriptExecutor
|
||||
, string rankKey, string blockKey )
|
||||
{
|
||||
// Lua 스크립트 정의
|
||||
var lua_script_name = "getTopRankAndSetBlock";
|
||||
var lua_script = @"
|
||||
local blockKey = KEYS[2]
|
||||
local rankKey = KEYS[1]
|
||||
|
||||
-- 블록 상태 설정 (무제한 TTL)
|
||||
redis.call('SET', blockKey, 1)
|
||||
|
||||
-- 현재 최고 랭커 조회
|
||||
local currentHighestRanker = redis.call('ZREVRANGE', rankKey, 0, 0, 'WITHSCORES')
|
||||
if currentHighestRanker[1] == nil then
|
||||
return { 0, '', 0 } -- 최고 랭커가 없을 경우
|
||||
end
|
||||
|
||||
local currentHighestRankerId = currentHighestRanker[1]
|
||||
local currentHighestPoint = tonumber(currentHighestRanker[2])
|
||||
|
||||
-- 결과 반환 (성공, 최고 랭커 ID, 점수)
|
||||
return { 1, currentHighestRankerId, currentHighestPoint }
|
||||
";
|
||||
|
||||
// Lua 스크립트를 Redis 서버에 로드
|
||||
var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script);
|
||||
if (false == is_success)
|
||||
{
|
||||
return (false, false, string.Empty, 0);
|
||||
}
|
||||
|
||||
bool is_top_found = false;
|
||||
string top_ranker_id = string.Empty;
|
||||
double top_ranker_point = 0;
|
||||
|
||||
try
|
||||
{
|
||||
// Lua 스크립트 실행, KEYS[1]에 rankKey, KEYS[2]에 blockKey 전달
|
||||
(is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name
|
||||
, new RedisKey[] { rankKey, blockKey }
|
||||
, new RedisValue[] { } );
|
||||
if (false == is_success)
|
||||
{
|
||||
Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : rankKey:{rankKey}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}");
|
||||
return (false, false, string.Empty, 0);
|
||||
}
|
||||
|
||||
NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!");
|
||||
|
||||
// Lua 스크립트 결과 처리: 최고 랭커 정보
|
||||
is_top_found = (bool)redis_result[0];
|
||||
top_ranker_id = redis_result[1]?.ToString() ?? string.Empty;
|
||||
top_ranker_point = redis_result[2].IsNull ? 0 : (double)redis_result[2];
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, rankKey:{rankKey}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return (false, false, string.Empty, 0);
|
||||
}
|
||||
|
||||
return (is_success, is_top_found, top_ranker_id, top_ranker_point);
|
||||
}
|
||||
|
||||
|
||||
//=============================================================================================
|
||||
// 랭킹 키와 블록 키를 제거 한다. - kangms
|
||||
//=============================================================================================
|
||||
public static async Task<bool> tryRemoveTopRankAndBlock( this RedisWithLuaScriptExecutorBase luaScriptExecutor
|
||||
, string rankKey, string blockKey )
|
||||
{
|
||||
// Lua 스크립트 정의
|
||||
var lua_script_name = "removeTopRankAndBlock";
|
||||
var lua_script = @"
|
||||
local rankKey = KEYS[1]
|
||||
local blockKey = KEYS[2]
|
||||
|
||||
redis.call('DEL', rankKey)
|
||||
redis.call('DEL', blockKey)
|
||||
|
||||
return 1
|
||||
";
|
||||
|
||||
// Lua 스크립트를 Redis 서버에 로드
|
||||
var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script);
|
||||
if (false == is_success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_result = false;
|
||||
|
||||
try
|
||||
{
|
||||
// Lua 스크립트 실행, KEYS[1]에 rankKey, KEYS[2]에 blockKey 전달
|
||||
(is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name
|
||||
, new RedisKey[] { rankKey, blockKey }
|
||||
, new RedisValue[] { });
|
||||
if (false == is_success)
|
||||
{
|
||||
Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : rankKey:{rankKey}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
|
||||
NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!");
|
||||
|
||||
is_result = (bool)redis_result;
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, rankKey:{rankKey}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_success;
|
||||
}
|
||||
|
||||
|
||||
//=============================================================================================
|
||||
// 데이터를 저장한 후, 즉시 Lock을 해제합니다. kangms
|
||||
//=============================================================================================
|
||||
public static async Task<bool> tryWriteAndReleaseLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor
|
||||
, string lockKey, string lockValue
|
||||
, string toWriteDataKey, string toWriteDataValue )
|
||||
{
|
||||
// lua 스크립트 레디스 set 구문 실행 구성
|
||||
var lua_script_name = "writeAndReleaseLock";
|
||||
var lua_script = @"
|
||||
local currentLock = redis.call('GET', KEYS[1]) -- Get current lock value (write lock key)
|
||||
local ownershipKey = ARGV[1] -- Ownership key (e.g., user or session ID)
|
||||
local newData = ARGV[2] -- New data to store
|
||||
|
||||
if currentLock == false then
|
||||
-- Lock doesn't exist, set lock, store data
|
||||
redis.call('SET', KEYS[1], ownershipKey)
|
||||
redis.call('SET', KEYS[2], newData) -- Store new data
|
||||
redis.call('DEL', KEYS[1]) -- Immediately release lock
|
||||
return 1
|
||||
elseif currentLock == ownershipKey then
|
||||
-- Lock exists and ownership matches, update data
|
||||
redis.call('SET', KEYS[2], newData)
|
||||
redis.call('DEL', KEYS[1]) -- Immediately release lock
|
||||
return 1
|
||||
else
|
||||
-- Lock exists but ownership doesn't match
|
||||
return 0
|
||||
end
|
||||
";
|
||||
|
||||
var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script);
|
||||
if (false == is_success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
(is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name
|
||||
, new RedisKey[] { lockKey, toWriteDataKey }
|
||||
, new RedisValue[] { lockValue, toWriteDataValue });
|
||||
if (false == is_success)
|
||||
{
|
||||
Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : lockKey:{lockKey}, toWriteDataKey:{toWriteDataKey} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!! - LuaScriptName:{lua_script_name}");
|
||||
|
||||
var return_value = (int)redis_result;
|
||||
if (0 == return_value)
|
||||
{
|
||||
Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, lockKey:{lockKey}, toWriteDataKey:{toWriteDataKey} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 데이터를 저장하면서 WriteLock을 설정하고 TTL을 유지한다. WriteLock을 해제하지 않고 유지한다. - kangms
|
||||
//=============================================================================================
|
||||
public static async Task<bool> tryWriteWithLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor
|
||||
, string lockKey, string lockValue
|
||||
, string toWriteDataKey, string toWriteDataValue
|
||||
, Int32 ttlSec )
|
||||
{
|
||||
// lua 스크립트 레디스 set 구문 실행 구성
|
||||
var lua_script_name = "writeWithLock";
|
||||
var lua_script = @"
|
||||
local currentLock = redis.call('GET', KEYS[1]) -- Get current lock value (write lock key)
|
||||
local ownershipKey = ARGV[1] -- Ownership key (e.g., user or session ID)
|
||||
local newData = ARGV[2] -- New data to store
|
||||
local ttl = tonumber(ARGV[3]) -- TTL 값 (초 단위)
|
||||
|
||||
if currentLock == false then
|
||||
-- Lock doesn't exist, set lock, store data, and set TTL
|
||||
redis.call('SET', KEYS[1], ownershipKey)
|
||||
redis.call('EXPIRE', KEYS[1], ttl) -- Set TTL for the lock
|
||||
redis.call('SET', KEYS[2], newData) -- Store new data
|
||||
return 1 -- 성공시 1 반환
|
||||
elseif currentLock == ownershipKey then
|
||||
-- Lock exists and ownership matches, update data
|
||||
redis.call('SET', KEYS[2], newData)
|
||||
return 1 -- 성공시 1 반환
|
||||
else
|
||||
-- Lock exists but ownership doesn't match
|
||||
return 0 -- 실패시 0 반환
|
||||
end
|
||||
";
|
||||
|
||||
var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script);
|
||||
if (false == is_success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
(is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name
|
||||
, new RedisKey[] { lockKey, toWriteDataKey }
|
||||
, new RedisValue[] { lockValue, toWriteDataValue, ttlSec });
|
||||
if (false == is_success)
|
||||
{
|
||||
Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : lockKey:{lockKey}, toWriteDataKey:{toWriteDataKey} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!! - LuaScriptName:{lua_script_name}");
|
||||
|
||||
var return_value = (int)redis_result;
|
||||
if (0 == return_value)
|
||||
{
|
||||
Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, lockKey:{lockKey}, toWriteDataKey:{toWriteDataKey}, ttlSec:{ttlSec} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// WriteLock 소유 여부와 상관없이 toReadDataKey로 데이터를 읽기만 한다. - kangms
|
||||
//=============================================================================================
|
||||
public static async Task<(bool, string)> tryReadWithLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor
|
||||
, string lockKey, string lockValue
|
||||
, string toReadDataKey
|
||||
, Int32 ttlSec )
|
||||
{
|
||||
// lua 스크립트 레디스 set 구문 실행 구성
|
||||
var lua_script_name = "readWithLock";
|
||||
var lua_script = @"
|
||||
local currentLock = redis.call('GET', KEYS[1]) -- 현재 잠금 상태 확인
|
||||
local ownershipKey = ARGV[1] -- 소유권 키 (예: 사용자 ID 또는 세션 ID)
|
||||
local ttl = tonumber(ARGV[2]) -- TTL 값 (초 단위)
|
||||
|
||||
if currentLock == ownershipKey then
|
||||
-- 소유권이 일치하면 TTL 갱신 및 데이터 반환
|
||||
redis.call('EXPIRE', KEYS[1], ttl) -- TTL 갱신
|
||||
local data = redis.call('GET', KEYS[2]) -- 데이터 가져오기 (nil 일수 있다)
|
||||
return { 1, data } -- 성공: {1, 데이터} 형태로 반환
|
||||
else
|
||||
-- 소유권이 일치하지 않으면 실패 메시지 반환
|
||||
return { 0, nil }
|
||||
end
|
||||
";
|
||||
|
||||
var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script);
|
||||
if (false == is_success)
|
||||
{
|
||||
return (false, string.Empty);
|
||||
}
|
||||
|
||||
var return_data = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
(is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name
|
||||
, new RedisKey[] { lockKey, toReadDataKey }
|
||||
, new RedisValue[] { lockValue, ttlSec } );
|
||||
if (false == is_success)
|
||||
{
|
||||
Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : lockKey:{lockKey}, toReadDataKey:{toReadDataKey} - LuaScriptName:{lua_script_name}");
|
||||
return (false, string.Empty);
|
||||
}
|
||||
NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!! - LuaScriptName:{lua_script_name}");
|
||||
|
||||
var return_result = (false == redis_result[0].IsNull) ? (int)redis_result[0] : 0;
|
||||
return_data = (false == redis_result[1].IsNull) ? redis_result[1].ToString() : string.Empty;
|
||||
|
||||
if (0 == return_result)
|
||||
{
|
||||
Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_result} - LuaScriptName:{lua_script_name}");
|
||||
return (false, string.Empty);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, lockKey:{lockKey}, toReadDataKey:{toReadDataKey}, ttlSec:{ttlSec} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return (false, string.Empty);
|
||||
}
|
||||
|
||||
return (true, return_data);
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// WriteLock 소유 여부와 상관없이 toReadDataKey로 데이터를 읽기만 한다. - kangms
|
||||
//=============================================================================================
|
||||
public static async Task<(bool, string)> tryReadWithNoLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor
|
||||
, string toReadDataKey )
|
||||
{
|
||||
// lua 스크립트 레디스 set 구문 실행 구성
|
||||
var lua_script_name = "readWithNoLock";
|
||||
var lua_script = @"
|
||||
local data = redis.call('GET', KEYS[1]) -- key에 해당하는 data 반환
|
||||
|
||||
if data then
|
||||
return { 1, data } -- 성공시 1과 데이터 반환
|
||||
else
|
||||
return { 0, nil } -- 실패시 0과 nil 반환
|
||||
end
|
||||
";
|
||||
|
||||
var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script);
|
||||
if (false == is_success)
|
||||
{
|
||||
return (false, string.Empty);
|
||||
}
|
||||
|
||||
var return_data = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
(is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name
|
||||
, new RedisKey[] { toReadDataKey } );
|
||||
if (false == is_success)
|
||||
{
|
||||
Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : toReadDataKey:{toReadDataKey} - LuaScriptName:{lua_script_name}");
|
||||
return (false, string.Empty);
|
||||
}
|
||||
NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!! - LuaScriptName:{lua_script_name}");
|
||||
|
||||
var return_result = (false == redis_result[0].IsNull) ? (int)redis_result[0] : 0;
|
||||
return_data = (false == redis_result[1].IsNull) ? redis_result[1].ToString() : string.Empty;
|
||||
|
||||
if (0 == return_result)
|
||||
{
|
||||
Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_result} - LuaScriptName:{lua_script_name}");
|
||||
return (false, string.Empty);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, toReadDataKey:{toReadDataKey} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return (false, string.Empty);
|
||||
}
|
||||
|
||||
return (true, return_data);
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 현재 키를 비교하고 같다면 새로운 값으로 변경 한다. - kangms
|
||||
//=============================================================================================
|
||||
public static async Task<bool> compareAndSetWithLua( this RedisWithLuaScriptExecutorBase luaScriptExecutor
|
||||
, string key
|
||||
, string expectedValue, string newValue )
|
||||
{
|
||||
// lua 스크립트 레디스 set 구문 실행 구성
|
||||
var lua_script_name = "compareAndSet";
|
||||
var lua_script = @"
|
||||
local current = redis.call('GET', KEYS[1])
|
||||
if current == nil or current == false or current == ARGV[1] then
|
||||
redis.call('SET', KEYS[1], ARGV[2])
|
||||
return 1 -- 성공시 1 반환
|
||||
else
|
||||
return 0 -- 실패시 0 반환
|
||||
end
|
||||
";
|
||||
|
||||
var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script);
|
||||
if (false == is_success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
(is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name
|
||||
, new RedisKey[] { key }
|
||||
, new RedisValue[] { expectedValue, newValue });
|
||||
if (false == is_success)
|
||||
{
|
||||
Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : key:{key}, expectedValue:{expectedValue}, newValue:{newValue} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!! - LuaScriptName:{lua_script_name}");
|
||||
|
||||
var return_value = (int)redis_result;
|
||||
if (0 == return_value)
|
||||
{
|
||||
Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, key:{key}, expectedValue:{expectedValue}, newValue:{newValue} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 현재 lockKey가 존재하는지 체크하고 lockKey가 없거나, lockKey의 lockValue값이 같을 경우 true 반환, 그 외에는 false-를 반환 한다. - kangms
|
||||
//=============================================================================================
|
||||
public static async Task<bool> tryAcquireLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor
|
||||
, string lockKey, string lockValue
|
||||
, Int32 ttlSec )
|
||||
{
|
||||
// lua 스크립트 레디스 set 구문 실행 구성
|
||||
var lua_script_name = "acquireLock";
|
||||
var lua_script = @"
|
||||
local ttl = tonumber(ARGV[2]) -- TTL 값 (초 단위)
|
||||
|
||||
local current = redis.call('GET', KEYS[1])
|
||||
if current == nil or current == false or current == ARGV[1] then
|
||||
redis.call('SET', KEYS[1], ARGV[1], 'EX', ttl)
|
||||
return 1 -- 성공시 1 반환
|
||||
else
|
||||
return 0 -- 실패시 0 반환
|
||||
end
|
||||
";
|
||||
|
||||
var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script);
|
||||
if (false == is_success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
(is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name
|
||||
, new RedisKey[] { lockKey }
|
||||
, new RedisValue[] { lockValue, ttlSec });
|
||||
if (false == is_success)
|
||||
{
|
||||
Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : lockKey:{lockKey}, lockValue:{lockValue}, ttlSec:{ttlSec} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!");
|
||||
|
||||
var return_value = (int)redis_result;
|
||||
if (0 == return_value)
|
||||
{
|
||||
Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, lockKey:{lockKey}, lockValue:{lockValue}, ttlSec:{ttlSec} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 현재 lockKey가 존재하는지 체크하고 lockKey가 있고 lockValue가 같다면 true 를 반환하고, 그 외에는 false를 반환 한다. - kangms
|
||||
//=============================================================================================
|
||||
public static async Task<bool> tyrReleaseLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor
|
||||
, string lockKey, string lockValue )
|
||||
{
|
||||
// lua 스크립트 레디스 set 구문 실행 구성
|
||||
var lua_script_name = "releaseLock";
|
||||
var lua_script = @"
|
||||
local current = redis.call('GET', KEYS[1])
|
||||
if current == ARGV[1] then
|
||||
redis.call('DEL', KEYS[1])
|
||||
return 1 -- 성공시 1 반환
|
||||
else
|
||||
return 0 -- 실패시 0 반환
|
||||
end
|
||||
";
|
||||
|
||||
var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script);
|
||||
if (false == is_success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
(is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name
|
||||
, new RedisKey[] { lockKey }
|
||||
, new RedisValue[] { lockValue });
|
||||
if (false == is_success)
|
||||
{
|
||||
Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : lockKey:{lockKey}, lockValue:{lockValue} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!");
|
||||
|
||||
var return_value = (int)redis_result;
|
||||
if (0 == return_value)
|
||||
{
|
||||
Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, lockKey:{lockKey}, lockValue:{lockValue} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 현재 lockKey가 존재하는지 체크하고 lockKey의 lockValue값이 같을 경우 ttlMSec 설정 한다. 성공시 1, 실패시 0을 반환 한다. - kangms
|
||||
//=============================================================================================
|
||||
public static async Task<bool> tyrResetTTLWithLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor
|
||||
, string lockKey, string lockValue
|
||||
, Int32 ttlMSec )
|
||||
{
|
||||
// lua 스크립트 레디스 set 구문 실행 구성
|
||||
var lua_script_name = "resetTtlWithLock";
|
||||
var lua_script = @"
|
||||
local currentLock = redis.call('GET', KEYS[1]) -- Get current lock value (write lock key)
|
||||
local ownershipKey = ARGV[1] -- Ownership key (e.g., user or session ID)
|
||||
local ttl = tonumber(ARGV[2]) -- New TTL in seconds
|
||||
|
||||
if currentLock == ownershipKey then
|
||||
-- 소유권이 일치하면 TTL 갱신
|
||||
redis.call('EXPIRE', KEYS[1], ttl)
|
||||
return 1 -- Success
|
||||
else
|
||||
-- 소유권이 일치하지 않음
|
||||
return 0 -- Failure
|
||||
end
|
||||
";
|
||||
|
||||
var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script);
|
||||
if (false == is_success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
(is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name
|
||||
, new RedisKey[] { lockKey }
|
||||
, new RedisValue[] { lockValue, ttlMSec });
|
||||
if (false == is_success)
|
||||
{
|
||||
Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : lockKey:{lockKey}, ttl:{ttlMSec} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!");
|
||||
|
||||
var return_value = (int)redis_result;
|
||||
if (0 == return_value)
|
||||
{
|
||||
Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, lockKey:{lockKey}, ttl:{ttlMSec} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//=============================================================================================
|
||||
// targetKey로 TTL을 재설정할 키를 전달받고, ttl로 새로운 TTL 값을 전달받습니다. 성공시 true, 실패시 false을 반환 한다. - kangms
|
||||
//=============================================================================================
|
||||
public static async Task<bool> tyrResetTTLWithNoLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor
|
||||
, string targetKey, Int32 ttlMSec )
|
||||
{
|
||||
// lua 스크립트 레디스 set 구문 실행 구성
|
||||
var lua_script_name = "resetTtlWithNoLock";
|
||||
var lua_script = @"
|
||||
local ttl = tonumber(ARGV[1]) -- TTL in seconds
|
||||
local exists = redis.call('EXISTS', KEYS[1])
|
||||
|
||||
if exists == 1 then
|
||||
-- 키가 존재할 경우 TTL을 재설정
|
||||
redis.call('EXPIRE', KEYS[1], ttl)
|
||||
return 1 -- 성공 시 1 반환
|
||||
else
|
||||
-- 키가 존재하지 않으면 실패
|
||||
return 0 -- 실패 시 0 반환
|
||||
end
|
||||
";
|
||||
|
||||
var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script);
|
||||
if (false == is_success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
(is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name
|
||||
, new RedisKey[] { targetKey }
|
||||
, new RedisValue[] { ttlMSec });
|
||||
if (false == is_success)
|
||||
{
|
||||
Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : targetKey:{targetKey}, ttl:{ttlMSec} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!");
|
||||
|
||||
var return_value = (int)redis_result;
|
||||
if (0 == return_value)
|
||||
{
|
||||
Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, targetKey:{targetKey}, ttl:{ttlMSec} - LuaScriptName:{lua_script_name}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
390
ServerCore/S3/S3ConnectorBase.cs
Normal file
390
ServerCore/S3/S3ConnectorBase.cs
Normal file
@@ -0,0 +1,390 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Numerics;
|
||||
using System.Net;
|
||||
|
||||
|
||||
|
||||
using Amazon.DynamoDBv2.Model;
|
||||
using Amazon.DynamoDBv2;
|
||||
using Amazon.S3;
|
||||
using Amazon.Internal;
|
||||
using Amazon;
|
||||
using Amazon.S3.Model;
|
||||
using Amazon.Runtime.Internal;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public abstract class S3ConnectorBase
|
||||
{
|
||||
private AmazonS3Client? m_s3_client;
|
||||
|
||||
private string m_access_key = string.Empty;
|
||||
private string m_secret_key = string.Empty;
|
||||
private string m_region = string.Empty;
|
||||
|
||||
public bool createS3Client(string accessKey, string secretKey, string region)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_access_key = accessKey;
|
||||
m_secret_key = secretKey;
|
||||
m_region = region;
|
||||
|
||||
var regionEndpoint = RegionEndpoint.GetBySystemName(region);
|
||||
|
||||
// 여기서 실제로 연결 시도는 하지 않는다 !!!, 단순 초기화
|
||||
m_s3_client = new AmazonS3Client(accessKey, secretKey, regionEndpoint);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().error($"Exception !!!, Failed to perform in createS3Client() : accessKey:{accessKey}, secretKey:{secretKey}, region:{region}, exception:{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<(bool, List<S3Bucket>)> getBuckets()
|
||||
{
|
||||
var buckets = new List<S3Bucket>();
|
||||
|
||||
try
|
||||
{
|
||||
NullReferenceCheckHelper.throwIfNull(m_s3_client, () => $"m_s3_client is null !!!");
|
||||
|
||||
var response = await m_s3_client.ListBucketsAsync();
|
||||
if (response.HttpStatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
Log.getLogger().fatal($"Failed to ListBucketsAsync() !!! : HttpStatusCode:{response.HttpStatusCode}");
|
||||
return (false, buckets);
|
||||
}
|
||||
|
||||
return (true, response.Buckets);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().fatal($"Exception !!!, Failed to perfrom in getBuckets() !!! : exception:{e}");
|
||||
}
|
||||
|
||||
return (false, buckets);
|
||||
}
|
||||
|
||||
public async Task<bool> createBucket(PutBucketRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
NullReferenceCheckHelper.throwIfNull(m_s3_client, () => $"m_s3_client is null !!!");
|
||||
|
||||
var response = await m_s3_client.PutBucketAsync(request);
|
||||
if (response.HttpStatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
Log.getLogger().fatal($"Failed to PutBucketAsync() !!! : HttpStatusCode:{response.HttpStatusCode}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().fatal($"Exception !!!, Failed to perfrom in createBucket() !!! : exception:{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> uploadFile(PutObjectRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
NullReferenceCheckHelper.throwIfNull(m_s3_client, () => $"m_s3_client is null !!!");
|
||||
|
||||
var response = await m_s3_client.PutObjectAsync(request);
|
||||
if (response.HttpStatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
Log.getLogger().fatal($"Failed to PutObjectAsync() !!! : HttpStatusCode:{response.HttpStatusCode}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().fatal($"Exception !!!, Failed to perform in uploadFile() : exception:{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> deleteFile(DeleteObjectRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
NullReferenceCheckHelper.throwIfNull(m_s3_client, () => $"m_s3_client is null !!!");
|
||||
|
||||
var response = await m_s3_client.DeleteObjectAsync(request);
|
||||
if (response.HttpStatusCode != HttpStatusCode.NoContent)
|
||||
{
|
||||
Log.getLogger().fatal($"Failed to DeleteObjectAsync() !!!, in deleteFile() : HttpStatusCode:{response.HttpStatusCode}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().fatal($"Exception !!!, Failed to perform in deleteFile() : exception:{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> deleteFolderFile(ListObjectsV2Request request)
|
||||
{
|
||||
try
|
||||
{
|
||||
ListObjectsV2Response response;
|
||||
|
||||
do
|
||||
{
|
||||
NullReferenceCheckHelper.throwIfNull(m_s3_client, () => $"m_s3_client is null !!!");
|
||||
|
||||
response = await m_s3_client.ListObjectsV2Async(request);
|
||||
if (response.HttpStatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
Log.getLogger().fatal($"Failed to ListObjectsV2Async() !!!, in deleteFolderFile() : HttpStatusCode:{response.HttpStatusCode}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.S3Objects.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var delete_request = new DeleteObjectsRequest
|
||||
{
|
||||
BucketName = request.BucketName,
|
||||
};
|
||||
|
||||
foreach (var s3_object in response.S3Objects)
|
||||
{
|
||||
delete_request.AddKey(s3_object.Key);
|
||||
}
|
||||
|
||||
var delete_response = await m_s3_client.DeleteObjectsAsync(delete_request);
|
||||
if (response.HttpStatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
Log.getLogger().fatal($"Failed to DeleteObjectsAsync() !!!, in deleteFolderFile() : HttpStatusCode:{response.HttpStatusCode}");
|
||||
return false;
|
||||
}
|
||||
|
||||
request.ContinuationToken = response.NextContinuationToken;
|
||||
}
|
||||
while (response.IsTruncated);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().fatal($"Exception !!!, Failed to perform in deleteFolderFile() : exception:{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<(bool, Stream?)> getFileStream(GetObjectRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
NullReferenceCheckHelper.throwIfNull(m_s3_client, () => $"m_s3_client is null !!!");
|
||||
|
||||
var response = await m_s3_client.GetObjectAsync(request);
|
||||
if (response.HttpStatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
Log.getLogger().fatal($"Failed to GetObjectAsync() !!! : HttpStatusCode:{response.HttpStatusCode}");
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
return (true, response.ResponseStream);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().fatal($"Exception !!!, Failed to perform in getFileStream() : exception:{e}");
|
||||
}
|
||||
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
private async Task<(bool, string)> generatePreSignedUrl(GetPreSignedUrlRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
NullReferenceCheckHelper.throwIfNull(m_s3_client, () => $"m_s3_client is null !!!");
|
||||
|
||||
var presigned_url = await m_s3_client.GetPreSignedURLAsync(request);
|
||||
|
||||
return (true, presigned_url);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().fatal($"Exception !!!, Failed to perform in generatePresignedUrl() : exception:{e}");
|
||||
}
|
||||
|
||||
return (false, string.Empty);
|
||||
}
|
||||
|
||||
public async Task<bool> createBucketIfNotExist(string bucketName)
|
||||
{
|
||||
if (false == await isExistBucket(bucketName))
|
||||
{
|
||||
var request = new PutBucketRequest
|
||||
{
|
||||
BucketName = bucketName,
|
||||
UseClientRegion = true,
|
||||
|
||||
};
|
||||
|
||||
if (false == await createBucket(request))
|
||||
{
|
||||
var err_msg = $"Failed to createBucket() !!! : bucketName:{bucketName}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> isExistBucket(string bucketName)
|
||||
{
|
||||
(var is_success, var buckets) = await getBuckets();
|
||||
if (false == is_success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var bucket in buckets)
|
||||
{
|
||||
if (bucket.BucketName == bucketName) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> tryUploadFile(string bucketName, string s3Key, string data)
|
||||
{
|
||||
var err_msg = string.Empty;
|
||||
|
||||
using (var memory_stream = data.toStream())
|
||||
{
|
||||
var request = new PutObjectRequest
|
||||
{
|
||||
BucketName = bucketName,
|
||||
Key = s3Key,
|
||||
InputStream = memory_stream,
|
||||
};
|
||||
|
||||
if (false == await uploadFile(request))
|
||||
{
|
||||
err_msg = $"Failed to uploadFile() !!!, in tryUploadFile() : bucketName:{bucketName}, s3Key:{s3Key}";
|
||||
Log.getLogger().error(err_msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> tryDeleteFile(string bucketName, string s3Key)
|
||||
{
|
||||
var request = new DeleteObjectRequest
|
||||
{
|
||||
BucketName = bucketName,
|
||||
Key = s3Key,
|
||||
};
|
||||
|
||||
if (false == await deleteFile(request))
|
||||
{
|
||||
var err_msg = $"Failed to deleteFile() !!!, in tryDeleteFile() : bucketName:{bucketName}, s3Key:{s3Key}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> tryDeleteFolderFile(string bucketName, string prefix)
|
||||
{
|
||||
var request = new ListObjectsV2Request
|
||||
{
|
||||
BucketName = bucketName,
|
||||
Prefix = prefix,
|
||||
};
|
||||
|
||||
if (false == await deleteFolderFile(request))
|
||||
{
|
||||
var err_msg = $"Failed to deleteFolderFile() !!!, in tryDeleteFolderFile() : bucketName:{bucketName}, prefix:{prefix}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<(bool, string)> tryGetFileData(string bucketName, string s3Key)
|
||||
{
|
||||
var request = new GetObjectRequest
|
||||
{
|
||||
BucketName = bucketName,
|
||||
Key = s3Key,
|
||||
};
|
||||
|
||||
(var is_success, var stream) = await getFileStream(request);
|
||||
if (false == is_success)
|
||||
{
|
||||
var err_msg = $"Failed to getFile() !!! - bucketName:{bucketName}, s3Key:{s3Key}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return (false, string.Empty);
|
||||
}
|
||||
NullReferenceCheckHelper.throwIfNull(stream, () => $"stream is null !!! - bucketName:{bucketName}, s3Key:{s3Key}");
|
||||
|
||||
return (true, stream.toString());
|
||||
}
|
||||
|
||||
public async Task<(bool, string)> getPresignedUrl( string bucketName, string s3Key
|
||||
, HttpVerb verbType, DateTime toExpireTime )
|
||||
{
|
||||
var err_msg = string.Empty;
|
||||
|
||||
var request = new GetPreSignedUrlRequest
|
||||
{
|
||||
BucketName = bucketName,
|
||||
Key = s3Key,
|
||||
Verb = verbType,
|
||||
Expires = toExpireTime
|
||||
};
|
||||
|
||||
(var is_success, var presigned_url) = await generatePreSignedUrl(request);
|
||||
if(false == is_success)
|
||||
{
|
||||
err_msg = $"Failed to generatePreSignedUrl() !!! : bucketName:{bucketName}, s3Key:{s3Key}, httpVerbType:{verbType}, toExpireTime:{toExpireTime.toStringWithUtcIso8601()}";
|
||||
Log.getLogger().error(err_msg);
|
||||
return (false, presigned_url);
|
||||
}
|
||||
|
||||
return (true, presigned_url);
|
||||
}
|
||||
|
||||
public AmazonS3Client? getAmazonS3Client() => m_s3_client;
|
||||
|
||||
|
||||
public string toBasicString()
|
||||
{
|
||||
return $"{this.getTypeName()}, AccessKey:{m_access_key}, SecretKey:{m_secret_key}, Region:{m_region}";
|
||||
}
|
||||
}
|
||||
177
ServerCore/Security/EncryptionHelper.cs
Normal file
177
ServerCore/Security/EncryptionHelper.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public class EncryptionHelper
|
||||
{
|
||||
public static string encryptTextByDES(string text, byte[] encryptKey, byte[] encryptIv)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create a MemoryStream.
|
||||
using (MemoryStream mStream = new MemoryStream())
|
||||
{
|
||||
// Create a new DES object.
|
||||
using (var des = DES.Create())
|
||||
// Create a DES encryptor from the key and IV
|
||||
using (ICryptoTransform encryptor = des.CreateEncryptor(encryptKey, encryptIv))
|
||||
// Create a CryptoStream using the MemoryStream and encryptor
|
||||
using (var cStream = new CryptoStream(mStream, encryptor, CryptoStreamMode.Write))
|
||||
{
|
||||
// Convert the provided string to a byte array.
|
||||
byte[] toEncrypt = Encoding.UTF8.GetBytes(text);
|
||||
|
||||
// Write the byte array to the crypto stream and flush it.
|
||||
cStream.Write(toEncrypt, 0, toEncrypt.Length);
|
||||
|
||||
// Ending the using statement for the CryptoStream completes the encryption.
|
||||
}
|
||||
|
||||
// Get an array of bytes from the MemoryStream that holds the encrypted data.
|
||||
byte[] ret = mStream.ToArray();
|
||||
|
||||
// Return the encrypted buffer.
|
||||
return Convert.ToBase64String(ret);
|
||||
}
|
||||
}
|
||||
catch (CryptographicException e)
|
||||
{
|
||||
Log.getLogger().error($"Exception !!!, A Cryptographic error occurred : {e}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static string decryptTextByDES(string encrypted, byte[] encryptKey, byte[] encryptIv)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create a buffer to hold the decrypted data.
|
||||
// DES-encrypted data will always be slightly bigger than the decrypted data.
|
||||
//byte[] StrByte = Encoding.UTF8.GetBytes(encrypted);
|
||||
var encryptedBytes = Convert.FromBase64String(encrypted);
|
||||
byte[] decrypted = new byte[encrypted.Length];
|
||||
int offset = 0;
|
||||
|
||||
// Create a new MemoryStream using the provided array of encrypted data.
|
||||
using (MemoryStream mStream = new MemoryStream(encryptedBytes))
|
||||
{
|
||||
// Create a new DES object.
|
||||
using (var des = DES.Create())
|
||||
// Create a DES decryptor from the key and IV
|
||||
using (ICryptoTransform decryptor = des.CreateDecryptor(encryptKey, encryptIv))
|
||||
// Create a CryptoStream using the MemoryStream and decryptor
|
||||
using (var cStream = new CryptoStream(mStream, decryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
// Keep reading from the CryptoStream until it finishes (returns 0).
|
||||
int read = 1;
|
||||
|
||||
while (read > 0)
|
||||
{
|
||||
read = cStream.Read(decrypted, offset, decrypted.Length - offset);
|
||||
offset += read;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the buffer into a string and return it.
|
||||
return Encoding.UTF8.GetString(decrypted, 0, offset);
|
||||
}
|
||||
catch (CryptographicException e)
|
||||
{
|
||||
Log.getLogger().error($"Exception !!!, A Cryptographic error occurred : {e}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static string encryptTextByAES(string plainText, byte[] encryptKey, byte[] encryptIv)
|
||||
{
|
||||
// Check arguments.
|
||||
if (plainText == null || plainText.Length <= 0)
|
||||
NullReferenceCheckHelper.throwIfNull(plainText, () => $"plainText is null !!!");
|
||||
if (encryptKey == null || encryptKey.Length <= 0)
|
||||
NullReferenceCheckHelper.throwIfNull(encryptKey, () => $"encryptKey is null !!!");
|
||||
if (encryptIv == null || encryptIv.Length <= 0)
|
||||
NullReferenceCheckHelper.throwIfNull(encryptIv, () => $"encryptIv is null !!!");
|
||||
|
||||
byte[] encrypted;
|
||||
|
||||
// Create an Aes object
|
||||
// with the specified key and IV.
|
||||
using (Aes aesAlg = Aes.Create())
|
||||
{
|
||||
aesAlg.Key = encryptKey;
|
||||
aesAlg.IV = encryptIv;
|
||||
|
||||
// Create an encryptor to perform the stream transform.
|
||||
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
|
||||
|
||||
// Create the streams used for encryption.
|
||||
using (MemoryStream msEncrypt = new MemoryStream())
|
||||
{
|
||||
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
|
||||
{
|
||||
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
|
||||
{
|
||||
//Write all data to the stream.
|
||||
swEncrypt.Write(plainText);
|
||||
}
|
||||
encrypted = msEncrypt.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the encrypted bytes from the memory stream.
|
||||
return Convert.ToBase64String(encrypted);
|
||||
}
|
||||
|
||||
public static string decryptTextByAES(string cipherText, byte[] encryptKey, byte[] encryptIv)
|
||||
{
|
||||
// Check arguments.
|
||||
if (cipherText == null || cipherText.Length <= 0)
|
||||
NullReferenceCheckHelper.throwIfNull(cipherText, () => $"cipherText is null !!!");
|
||||
if (encryptKey == null || encryptKey.Length <= 0)
|
||||
NullReferenceCheckHelper.throwIfNull(encryptKey, () => $"encryptKey is null !!!");
|
||||
if (encryptIv == null || encryptIv.Length <= 0)
|
||||
NullReferenceCheckHelper.throwIfNull(encryptIv, () => $"encryptIv is null !!!");
|
||||
|
||||
// Declare the string used to hold
|
||||
// the decrypted text.
|
||||
string plaintext = string.Empty;
|
||||
byte[] chipherBin = Convert.FromBase64String(cipherText);
|
||||
|
||||
// Create an Aes object
|
||||
// with the specified key and IV.
|
||||
using (Aes aesAlg = Aes.Create())
|
||||
{
|
||||
aesAlg.Key = encryptKey;
|
||||
aesAlg.IV = encryptIv;
|
||||
|
||||
// Create a decryptor to perform the stream transform.
|
||||
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
|
||||
|
||||
// Create the streams used for decryption.
|
||||
using (MemoryStream msDecrypt = new MemoryStream(chipherBin))
|
||||
{
|
||||
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
|
||||
{
|
||||
|
||||
// Read the decrypted bytes from the decrypting stream
|
||||
// and place them in a string.
|
||||
plaintext = srDecrypt.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
}
|
||||
82
ServerCore/ServerCore.csproj
Normal file
82
ServerCore/ServerCore.csproj
Normal file
@@ -0,0 +1,82 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Deterministic>true</Deterministic>
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
<Configurations>Debug;Release;Shipping</Configurations>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn></NoWarn>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DefineConstants>$(DefineConstants);</DefineConstants>
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DefineConstants>$(DefineConstants);</DefineConstants>
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Shipping|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DefineConstants>$(DefineConstants);</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AsyncStateMachine" />
|
||||
<PackageReference Include="AWS.Logger.NLog" />
|
||||
<PackageReference Include="AWSSDK.Core" />
|
||||
<PackageReference Include="AWSSDK.DynamoDBv2" />
|
||||
<PackageReference Include="AWSSDK.OpenSearchService" />
|
||||
<PackageReference Include="AWSSDK.S3" />
|
||||
<PackageReference Include="Axion.ConcurrentHashSet" />
|
||||
<PackageReference Include="CommandLineParser" />
|
||||
<PackageReference Include="Google.Protobuf" />
|
||||
<PackageReference Include="JWT" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" />
|
||||
<PackageReference Include="MongoDB.Analyzer" />
|
||||
<PackageReference Include="MongoDB.Bson" />
|
||||
<PackageReference Include="MongoDB.Driver" />
|
||||
<PackageReference Include="MySqlConnector" />
|
||||
<PackageReference Include="NeoSmart.AsyncLock" />
|
||||
<PackageReference Include="Otp.NET" />
|
||||
<PackageReference Include="DotNet.MultiMap" />
|
||||
<PackageReference Include="DumpExtensions" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="Nito.AsyncEx" />
|
||||
<PackageReference Include="NLog" />
|
||||
<PackageReference Include="RabbitMQ.Client" />
|
||||
<PackageReference Include="SSH.NET" />
|
||||
<PackageReference Include="StackExchange.Redis" />
|
||||
<PackageReference Include="StackExchange.Redis.Extensions.Core" />
|
||||
<PackageReference Include="StackExchange.Redis.Extensions.Newtonsoft" />
|
||||
<PackageReference Include="StackExchange.Redis.MultiplexerPool" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
|
||||
<PackageReference Include="YamlDotNet" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="ProudDotNetClient">
|
||||
<HintPath>..\..\ThirdPartyPackages\ProudNet\1.9.58941\ProudNet\lib\DotNet\ProudDotNetClient.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ProudDotNetServer">
|
||||
<HintPath>..\..\ThirdPartyPackages\ProudNet\1.9.58941\ProudNet\lib\DotNet\ProudDotNetServer.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="nlog.config">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
542
ServerCore/State/HFSMBase.cs
Normal file
542
ServerCore/State/HFSMBase.cs
Normal file
@@ -0,0 +1,542 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using AsyncStateMachine;
|
||||
using AsyncStateMachine.Behaviours;
|
||||
using AsyncStateMachine.Callbacks;
|
||||
using AsyncStateMachine.Contracts;
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// Hierachical Finite State Machines
|
||||
//=============================================================================================
|
||||
|
||||
public abstract class HFSMBase<TTrigger, TState>
|
||||
where TTrigger : struct
|
||||
where TState : struct
|
||||
{
|
||||
private StateMachineConfiguration<TTrigger, TState>? m_sm_config;
|
||||
private StateMachine<TTrigger, TState>? m_sm;
|
||||
|
||||
public delegate bool CB_Init(HFSMBase<TTrigger, TState> initHFSM);
|
||||
public delegate void CB_Enter();
|
||||
public delegate void CB_Tick(HFSMBase<TTrigger, TState> initHFSM);
|
||||
public delegate void CB_Exit();
|
||||
public delegate void CB_Transition(HFSMBase<TTrigger, TState> initHFSM);
|
||||
|
||||
private CB_Init? m_cbInit;
|
||||
private CB_Tick? m_cb_update_nullable;
|
||||
private readonly ConcurrentDictionary<TState, Action> m_update_handlers = new();
|
||||
private readonly ConcurrentDictionary<TState, CB_Transition> m_transition_handlers = new();
|
||||
private readonly SimpleTimer m_state_timer = new();
|
||||
|
||||
private uint m_hfsm_id = 0;
|
||||
|
||||
private bool m_is_disable = false;
|
||||
|
||||
private bool m_is_paused = false;
|
||||
|
||||
internal class HFSMInstantId : InstantIdGeneratorWith32Bit { }
|
||||
|
||||
public HFSMBase()
|
||||
{ }
|
||||
|
||||
private bool createStateMachine(TState rootState)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_sm_config = new StateMachineConfiguration<TTrigger, TState>(rootState);
|
||||
m_sm = new StateMachine<TTrigger, TState>(m_sm_config);
|
||||
m_hfsm_id = Singleton<HFSMInstantId>.It.nextId();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"Failed to create StateMachine !!! : Exception:{e}";
|
||||
Log.getLogger().fatal(err_msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> initHfsm(TState root, CB_Init cb_init)
|
||||
{
|
||||
var is_success = createStateMachine(root);
|
||||
if (false == is_success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_cbInit = cb_init;
|
||||
|
||||
is_success = m_cbInit(this);
|
||||
if (false == is_success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
onSubscribeStateChangeNotify();
|
||||
|
||||
if (m_sm == null)
|
||||
return false;
|
||||
|
||||
await m_sm.InitializeAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void onSubscribeStateChangeNotify() { }
|
||||
|
||||
public StateMachineConfiguration<TTrigger, TState>? getStateMachineConfiguration() => m_sm_config;
|
||||
|
||||
public StateMachine<TTrigger, TState>? getStateMachine() => m_sm;
|
||||
|
||||
public uint getHfsmId() => m_hfsm_id;
|
||||
|
||||
public SimpleTimer getStateTimer() => m_state_timer;
|
||||
|
||||
public TState? getState() { return m_sm?.CurrentState; }
|
||||
|
||||
public bool isState(TState stateType) { return m_sm?.CurrentState.Equals(stateType) ?? false; }
|
||||
|
||||
public async Task<bool> isInStateAsync(TState stateType)
|
||||
{
|
||||
if (m_sm == null)
|
||||
return false;
|
||||
|
||||
return await m_sm.InStateAsync(stateType);
|
||||
}
|
||||
|
||||
public void setDisable(bool isDisable)
|
||||
{
|
||||
m_is_disable = isDisable;
|
||||
}
|
||||
|
||||
public bool isDisable() => m_is_disable;
|
||||
|
||||
public void bindUpdate(CB_Tick cb)
|
||||
{
|
||||
m_cb_update_nullable = cb;
|
||||
}
|
||||
|
||||
private void doUpdate()
|
||||
{
|
||||
m_cb_update_nullable?.Invoke(this);
|
||||
}
|
||||
|
||||
public bool onUpdateHFSM(StateMachine<TTrigger, TState> stateManchine, Action fnUpdate)
|
||||
{
|
||||
var curr_state = stateManchine.CurrentState;
|
||||
if (curr_state == null)
|
||||
return false;
|
||||
|
||||
if (true == m_update_handlers.ContainsKey((TState)curr_state))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false == m_update_handlers.TryAdd((TState)curr_state, fnUpdate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void doCurrentUpdate()
|
||||
{
|
||||
var curr_state = getState();
|
||||
if (curr_state == null)
|
||||
return;
|
||||
|
||||
if (true != m_update_handlers.TryGetValue((TState)curr_state, out var update_handler))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
update_handler?.Invoke();
|
||||
}
|
||||
|
||||
public async Task fireAsyncTo(TTrigger trigger)
|
||||
{
|
||||
if (true == isDisable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sm == null)
|
||||
return;
|
||||
|
||||
await m_sm.FireAsync(trigger);
|
||||
}
|
||||
|
||||
public void startStateTimer(int msec)
|
||||
{
|
||||
m_state_timer.activate(msec);
|
||||
}
|
||||
|
||||
public void stopStateTimer()
|
||||
{
|
||||
m_state_timer.deactivate();
|
||||
}
|
||||
|
||||
public void resetStateTimer()
|
||||
{
|
||||
m_state_timer.reset();
|
||||
}
|
||||
|
||||
public void pauseStateTimer()
|
||||
{
|
||||
m_state_timer.pause();
|
||||
}
|
||||
|
||||
public void unpauseStateTimer()
|
||||
{
|
||||
m_state_timer.unpause();
|
||||
}
|
||||
|
||||
public int getReaminTimeMSec()
|
||||
{
|
||||
return (int)m_state_timer.getRemainTimeMSec();
|
||||
}
|
||||
|
||||
public bool isExpiredStateTimer()
|
||||
{
|
||||
if (m_state_timer.expired())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void onPause()
|
||||
{
|
||||
m_is_paused = true;
|
||||
|
||||
pauseStateTimer();
|
||||
}
|
||||
|
||||
public virtual void onUnpause()
|
||||
{
|
||||
m_is_paused = false;
|
||||
|
||||
unpauseStateTimer();
|
||||
}
|
||||
|
||||
public bool isPausing()
|
||||
{
|
||||
return m_is_paused;
|
||||
}
|
||||
|
||||
public virtual void onTick()
|
||||
{
|
||||
if (true == isDisable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (true == isPausing())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
doCurrentUpdate();
|
||||
doUpdate();
|
||||
}
|
||||
|
||||
}//HFSMBase
|
||||
|
||||
//=============================================================================================
|
||||
/// Compound HFSM
|
||||
//=============================================================================================
|
||||
public class CompoundHFSM<HFSMType, TTrigger, TState>
|
||||
where HFSMType : notnull
|
||||
where TTrigger : struct
|
||||
where TState : struct
|
||||
{
|
||||
private readonly ConcurrentDictionary<HFSMType, ConcurrentBag<HFSMBase<TTrigger, TState>>> m_hfsm_groups = new();
|
||||
|
||||
private class PriorityUpdate
|
||||
{
|
||||
public HFSMType HFSMTypeValue { get; set; }
|
||||
public int SeqNo { get; set; }
|
||||
|
||||
public PriorityUpdate(HFSMType hFSMTypeValue, int seqNo)
|
||||
{
|
||||
HFSMTypeValue = hFSMTypeValue;
|
||||
SeqNo = seqNo;
|
||||
}
|
||||
}
|
||||
|
||||
protected ConcurrentDictionary<HFSMType, int>? m_priority_hfsm_updates_nullable;
|
||||
private readonly ConcurrentBag<ConcurrentBag<HFSMBase<TTrigger, TState>>> m_update_hfsms = new();
|
||||
|
||||
|
||||
public CompoundHFSM()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<HFSMBase<TTrigger, TState>?> createHfsm<THFSM>( TState rootState
|
||||
, HFSMBase<TTrigger, TState>.CB_Init cbInitHFSM)
|
||||
where THFSM : HFSMBase<TTrigger, TState>, new()
|
||||
{
|
||||
var new_hfsm = new THFSM();
|
||||
if (true != await new_hfsm.initHfsm(rootState, cbInitHFSM))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new_hfsm;
|
||||
}
|
||||
|
||||
public bool addHFSM(HFSMType hfsmType, HFSMBase<TTrigger, TState> stateMachine)
|
||||
{
|
||||
if (null == stateMachine)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var hfsm_list = findHFSMs(hfsmType);
|
||||
if (hfsm_list == null)
|
||||
{
|
||||
hfsm_list = new ConcurrentBag<HFSMBase<TTrigger, TState>>();
|
||||
m_hfsm_groups.TryAdd(hfsmType, hfsm_list);
|
||||
}
|
||||
|
||||
hfsm_list.Add(stateMachine);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool removeHFSM(HFSMType hfsmType, uint hfsmId)
|
||||
{
|
||||
var hfsms = findHFSMs(hfsmType);
|
||||
if (hfsms == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var hfsm in hfsms)
|
||||
{
|
||||
if (hfsm.getHfsmId() == hfsmId)
|
||||
{
|
||||
hfsms.TryTake(out _);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool removeHFSM(HFSMType hfsmType, HFSMBase<TTrigger, TState> stateMachine)
|
||||
{
|
||||
if (null == stateMachine)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return removeHFSM(hfsmType, stateMachine.getHfsmId());
|
||||
}
|
||||
|
||||
public bool removeHFSMBy(HFSMType hfsmType, out ConcurrentBag<HFSMBase<TTrigger, TState>>? removeHFSMs)
|
||||
{
|
||||
removeHFSMs = null;
|
||||
if (false == m_hfsm_groups.TryRemove(hfsmType, out var removed_hfsms))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
removeHFSMs = removed_hfsms;
|
||||
return true;
|
||||
}
|
||||
|
||||
public HFSMBase<TTrigger, TState>? lookupHFSM(HFSMType hfsmType, int hfsmId)
|
||||
{
|
||||
var hfsms = findHFSMs(hfsmType);
|
||||
if (hfsms == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var found_hfsm = hfsms.First(delegate (HFSMBase<TTrigger, TState> hfsm)
|
||||
{
|
||||
return hfsm.getHfsmId() == hfsmId;
|
||||
});
|
||||
if (null == found_hfsm)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return found_hfsm;
|
||||
}
|
||||
|
||||
public void setPriorityUpdates(ConcurrentDictionary<HFSMType, int> prioritys)
|
||||
{
|
||||
m_priority_hfsm_updates_nullable = prioritys;
|
||||
}
|
||||
|
||||
public void resetUpdateHFSMs()
|
||||
{
|
||||
m_update_hfsms.Clear();
|
||||
}
|
||||
|
||||
public bool getCurrentState(HFSMType hfsmType, int hfsmId, out TState? state)
|
||||
{
|
||||
state = default;
|
||||
|
||||
var hfsms = findHFSMs(hfsmType);
|
||||
if (hfsms == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var hfsm in hfsms)
|
||||
{
|
||||
if (hfsmId == hfsm.getHfsmId())
|
||||
{
|
||||
state = hfsm.getState();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> isCurrentState(HFSMType hfsmType, TState state)
|
||||
{
|
||||
var hfsms = findHFSMs(hfsmType);
|
||||
if (hfsms == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var hfsm in hfsms)
|
||||
{
|
||||
if (true == await hfsm.isInStateAsync(state))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task fireAsyncTo(HFSMType hfsmType, int hfsmId, TTrigger trigger)
|
||||
{
|
||||
var hfsms = findHFSMs(hfsmType);
|
||||
if (hfsms == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var hfsm in hfsms)
|
||||
{
|
||||
if (hfsmId == hfsm.getHfsmId())
|
||||
{
|
||||
await hfsm.fireAsyncTo(trigger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task fireToGroup(HFSMType hfsmType, TTrigger trigger)
|
||||
{
|
||||
var hfsms = findHFSMs(hfsmType);
|
||||
if (hfsms == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var hfsm in hfsms)
|
||||
{
|
||||
await hfsm.fireAsyncTo(trigger);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task fireToAll(TTrigger trigger)
|
||||
{
|
||||
foreach (var pair_hfsm_group in m_hfsm_groups)
|
||||
{
|
||||
foreach (var fsm in pair_hfsm_group.Value)
|
||||
{
|
||||
await fsm.fireAsyncTo(trigger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onTick()
|
||||
{
|
||||
if (0 == m_update_hfsms.Count)
|
||||
{
|
||||
if (null != m_priority_hfsm_updates_nullable)
|
||||
{
|
||||
//우선 순위가 설정된 경우, 우선 순위값을 list 로 만든다.
|
||||
var toSortList = new List<PriorityUpdate>();
|
||||
foreach (var pair_priority_info in m_priority_hfsm_updates_nullable)
|
||||
{
|
||||
var priorityUpdate = new PriorityUpdate(pair_priority_info.Key, pair_priority_info.Value);
|
||||
toSortList.Add(priorityUpdate);
|
||||
}
|
||||
|
||||
//우선 순위값을 비교하여 정렬 한다.
|
||||
toSortList.Sort(delegate (PriorityUpdate a, PriorityUpdate b)
|
||||
{
|
||||
if (a.SeqNo == b.SeqNo)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (a.SeqNo > b.SeqNo)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
});
|
||||
|
||||
//정렬된 순서대로 HFSM Group 을 추가 한다.
|
||||
foreach (var value in toSortList)
|
||||
{
|
||||
var hfsms = findHFSMs(value.HFSMTypeValue);
|
||||
if (hfsms != null)
|
||||
{
|
||||
m_update_hfsms.Add(hfsms);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//우선 순위가 설정되지 않은 경우
|
||||
foreach (var hfsm_group in m_hfsm_groups)
|
||||
{
|
||||
m_update_hfsms.Add(hfsm_group.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//등록된 순서대로 HFSM Group 을 갱신 한다.
|
||||
foreach (var hfsm_list in m_update_hfsms)
|
||||
{
|
||||
foreach (var hfsm in hfsm_list)
|
||||
{
|
||||
hfsm.onTick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ConcurrentBag<HFSMBase<TTrigger, TState>>? findHFSMs(HFSMType fsmType)
|
||||
{
|
||||
m_hfsm_groups.TryGetValue(fsmType, out var found_group);
|
||||
return found_group;
|
||||
}
|
||||
|
||||
}//CompoundHFSM
|
||||
//ServerCore
|
||||
38
ServerCore/State/HFSMHelper.cs
Normal file
38
ServerCore/State/HFSMHelper.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using AsyncStateMachine;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public static class HFSMHelper
|
||||
{
|
||||
public static bool isChangedState<TTrigger, TState>(this Transition<TTrigger, TState> transition)
|
||||
where TTrigger : struct
|
||||
where TState : struct
|
||||
{
|
||||
if(null == transition)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(true == transition.Source.Equals(transition.Destination))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string toBasicString<TTrigger, TState>(this Transition<TTrigger, TState> transition)
|
||||
where TTrigger : struct
|
||||
where TState : struct
|
||||
{
|
||||
return $"Transition: from:{transition.Source} => to:{transition.Destination} - Trigger:{transition.Trigger.ToString()}";
|
||||
}
|
||||
}
|
||||
123
ServerCore/Sync/AtomicGuard.cs
Normal file
123
ServerCore/Sync/AtomicGuard.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
|
||||
|
||||
using GAURD_KEY = System.String;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 병렬 프로그래밍 환경에서 특정 로직이 동시에 실행되지 않도록 보호하는 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public class AtomicGuard : IDisposable
|
||||
{
|
||||
private string m_try_guard_key = string.Empty;
|
||||
private string m_using_guard_key = string.Empty;
|
||||
|
||||
private AtomicString? m_guard_key_nullable;
|
||||
private AtomicBool? m_atomic_bool_nullable;
|
||||
|
||||
public AtomicGuard(GAURD_KEY guardKey)
|
||||
{
|
||||
m_try_guard_key = guardKey;
|
||||
}
|
||||
|
||||
public AtomicGuard(AtomicString guardKey)
|
||||
{
|
||||
m_guard_key_nullable = guardKey;
|
||||
}
|
||||
|
||||
public AtomicGuard(AtomicString guardKey, AtomicBool atomicBool)
|
||||
{
|
||||
m_guard_key_nullable = guardKey;
|
||||
m_atomic_bool_nullable = atomicBool;
|
||||
}
|
||||
|
||||
public bool tryGuard()
|
||||
{
|
||||
if (null == m_guard_key_nullable)
|
||||
{
|
||||
var err_msg = $"m_guard_key_nullable is null !!! in tryGuard() - guardKey:{m_try_guard_key}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(false == m_guard_key_nullable.compareAndSet(string.Empty, m_try_guard_key))
|
||||
{
|
||||
var err_msg = $"Failed to in compareAndSet() !!! in tryGuard() - guardKey:{m_try_guard_key}";
|
||||
Log.getLogger().info(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (null == m_atomic_bool_nullable)
|
||||
{
|
||||
var err_msg = $"m_atomic_bool_nullable is null !!! in tryGuard() - guardKey:{m_try_guard_key}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false == m_atomic_bool_nullable.set(true))
|
||||
{
|
||||
var err_msg = $"Failed to set true !!! in tryGuard() - guardKey:{m_try_guard_key}";
|
||||
Log.getLogger().info(err_msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
m_using_guard_key = m_try_guard_key;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (null == m_guard_key_nullable)
|
||||
{
|
||||
var err_msg = $"m_guard_key_nullable is null !!! in Dispose() - guardKey:{m_try_guard_key}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var guard_key = m_guard_key_nullable.Value;
|
||||
|
||||
if (false == m_guard_key_nullable.compareAndSet(m_using_guard_key, string.Empty))
|
||||
{
|
||||
var err_msg = $"Failed to compareAndSet() !!! in Dispose() : usingGuardKey:{m_using_guard_key} - guardKey:{guard_key}";
|
||||
Log.getLogger().info(err_msg);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (null == m_atomic_bool_nullable)
|
||||
{
|
||||
var err_msg = $"m_atomic_bool_nullable is null !!! in Dispose() - guardKey:{guard_key}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (false == m_atomic_bool_nullable.set(false))
|
||||
{
|
||||
var err_msg = $"Failed to set false in Dispose() !!! - guardKey:{guard_key}";
|
||||
Log.getLogger().error(err_msg);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void bindAtomic(AtomicString guardKey, AtomicBool atomicBool)
|
||||
{
|
||||
m_guard_key_nullable = guardKey;
|
||||
m_atomic_bool_nullable = atomicBool;
|
||||
}
|
||||
}
|
||||
35
ServerCore/Sync/AutoLock.cs
Normal file
35
ServerCore/Sync/AutoLock.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 병렬 프로그래밍 환경에서 특정 로직이 동시에 실행되지 않도록 Monitor를 활용하여 보호하는 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public class AutoLock : IDisposable
|
||||
{
|
||||
private readonly object m_lock;
|
||||
|
||||
public AutoLock(object tolockObject)
|
||||
{
|
||||
m_lock = tolockObject;
|
||||
|
||||
Monitor.Enter(m_lock);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Monitor.Exit(m_lock);
|
||||
}
|
||||
}
|
||||
54
ServerCore/Sync/Counter.cs
Normal file
54
ServerCore/Sync/Counter.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public class Counter
|
||||
{
|
||||
private uint m_count = 0;
|
||||
|
||||
private readonly uint m_min;
|
||||
private readonly uint m_max;
|
||||
|
||||
public Counter(uint min = uint.MinValue, uint max = uint.MaxValue)
|
||||
{
|
||||
m_min = min;
|
||||
m_max = max;
|
||||
}
|
||||
|
||||
public uint incCount()
|
||||
{
|
||||
var value = Volatile.Read(ref m_count);
|
||||
if(value >= m_max)
|
||||
{
|
||||
m_count = m_max;
|
||||
return m_count;
|
||||
}
|
||||
|
||||
return Interlocked.Increment(ref m_count);
|
||||
}
|
||||
|
||||
public uint decCount()
|
||||
{
|
||||
var value = Volatile.Read(ref m_count);
|
||||
if (value <= m_min)
|
||||
{
|
||||
m_count = m_min;
|
||||
return m_count;
|
||||
}
|
||||
|
||||
return Interlocked.Decrement(ref m_count);
|
||||
}
|
||||
|
||||
public uint getCount()
|
||||
{
|
||||
return Volatile.Read(ref m_count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
53
ServerCore/Sync/InstantIdGenerator.cs
Normal file
53
ServerCore/Sync/InstantIdGenerator.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public class InstantIdGeneratorWith32Bit
|
||||
{
|
||||
private UInt32 m_index = 0;
|
||||
public InstantIdGeneratorWith32Bit()
|
||||
{
|
||||
}
|
||||
|
||||
public UInt32 nextId()
|
||||
{
|
||||
Interlocked.Increment(ref m_index);
|
||||
|
||||
if(m_index == UInt32.MaxValue)
|
||||
{
|
||||
throw new OverflowException("UInt32.MaxValue has been reached, at InstantIdGeneratorWith32Bit.nextId() !!!");
|
||||
}
|
||||
|
||||
return m_index;
|
||||
}
|
||||
|
||||
}//InstantIdGeneratorWith32Bit
|
||||
|
||||
public class InstantIdGeneratorWith64Bit
|
||||
{
|
||||
private UInt64 m_index = 0;
|
||||
public InstantIdGeneratorWith64Bit()
|
||||
{
|
||||
}
|
||||
|
||||
public UInt64 nextId()
|
||||
{
|
||||
Interlocked.Increment(ref m_index);
|
||||
|
||||
if (m_index >= UInt64.MaxValue)
|
||||
{
|
||||
throw new OverflowException("UInt64.MaxValue has been reached, at InstantIdGeneratorWith64Bit.nextId() !!!");
|
||||
}
|
||||
|
||||
return m_index;
|
||||
}
|
||||
|
||||
}//InstantIdGeneratorWith64Bit
|
||||
178
ServerCore/Sync/SyncState.cs
Normal file
178
ServerCore/Sync/SyncState.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 병렬 프로그래밍 환경에서 상태값을 동기화할 수 있게 지원하는 제네릭 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
public class SyncState<T> where T : notnull
|
||||
{
|
||||
private T m_state;
|
||||
private readonly object m_mutex = new object(); // mutex
|
||||
|
||||
public SyncState(T state)
|
||||
{
|
||||
m_state = state;
|
||||
}
|
||||
|
||||
public bool isState(T state)
|
||||
{
|
||||
lock (m_mutex)
|
||||
{
|
||||
return (m_state.Equals(state));
|
||||
}
|
||||
}
|
||||
|
||||
public T setState(T state)
|
||||
{
|
||||
lock (m_mutex)
|
||||
{
|
||||
T old_state = m_state;
|
||||
m_state = state;
|
||||
return old_state;
|
||||
}
|
||||
}
|
||||
|
||||
public T getState()
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
// 같지 않으면 변경
|
||||
// exchange..(disconnected, out old_state)
|
||||
public bool exchangeNotEqual(T to_state, out T old_state)
|
||||
{
|
||||
lock (m_mutex)
|
||||
{
|
||||
if (false == m_state.Equals(to_state))
|
||||
{
|
||||
old_state = m_state;
|
||||
m_state = to_state;
|
||||
return true;
|
||||
}
|
||||
|
||||
old_state = m_state;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 같지 않으면 변경, 예외인 경우는 변경안함
|
||||
// exchange...(disconnected, connecting, out old_state)
|
||||
public bool exchangeNotEqualExcept(T to_state, T except_state, out T old_state)
|
||||
{
|
||||
lock (m_mutex)
|
||||
{
|
||||
// except state
|
||||
if (true == m_state.Equals(except_state))
|
||||
{
|
||||
old_state = m_state;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false == m_state.Equals(to_state))
|
||||
{
|
||||
old_state = m_state;
|
||||
m_state = to_state;
|
||||
return true;
|
||||
}
|
||||
|
||||
old_state = m_state;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string? toString()
|
||||
{
|
||||
return Enum.GetName(typeof(T), m_state);
|
||||
}
|
||||
}
|
||||
|
||||
public class AtomicString
|
||||
{
|
||||
private string m_value;
|
||||
|
||||
public AtomicString(string value = "")
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
public string Value
|
||||
{
|
||||
get { return m_value; }
|
||||
}
|
||||
|
||||
public void set(string newValue)
|
||||
{
|
||||
Interlocked.Exchange(ref m_value, newValue);
|
||||
}
|
||||
|
||||
public bool compareAndSet(string expectedValue, string newValue)
|
||||
{
|
||||
return Interlocked.CompareExchange(ref m_value, newValue, expectedValue) == expectedValue;
|
||||
}
|
||||
}
|
||||
|
||||
public class AtomicInt32
|
||||
{
|
||||
private Int32 m_value;
|
||||
|
||||
public AtomicInt32(Int32 value = 0)
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
public int Value
|
||||
{
|
||||
get { return m_value; }
|
||||
}
|
||||
|
||||
public void set(int newValue)
|
||||
{
|
||||
Interlocked.Exchange(ref m_value, newValue);
|
||||
}
|
||||
|
||||
public int increment()
|
||||
{
|
||||
return Interlocked.Increment(ref m_value);
|
||||
}
|
||||
|
||||
public int decrement()
|
||||
{
|
||||
return Interlocked.Decrement(ref m_value);
|
||||
}
|
||||
|
||||
public int add(int value)
|
||||
{
|
||||
return Interlocked.Add(ref m_value, value);
|
||||
}
|
||||
|
||||
public bool compareAndSet(int expectedValue, int newValue)
|
||||
{
|
||||
return Interlocked.CompareExchange(ref m_value, newValue, expectedValue) == expectedValue;
|
||||
}
|
||||
}
|
||||
|
||||
public class AtomicBool
|
||||
{
|
||||
private Int32 m_value = 0;
|
||||
|
||||
public AtomicBool(bool value)
|
||||
{
|
||||
m_value = value ? 1 : 0;
|
||||
}
|
||||
|
||||
public bool Value
|
||||
{
|
||||
get { return m_value == 1; }
|
||||
}
|
||||
|
||||
public bool set(bool value)
|
||||
{
|
||||
var comparand = value ? 0 : 1;
|
||||
var in_value = value ? 1 : 0;
|
||||
|
||||
var result = Interlocked.CompareExchange(ref m_value, in_value, comparand);
|
||||
|
||||
return comparand == result;
|
||||
}
|
||||
}
|
||||
434
ServerCore/Sync/Synchronizer.cs
Normal file
434
ServerCore/Sync/Synchronizer.cs
Normal file
@@ -0,0 +1,434 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 병렬 프로그래밍 환경에서 특정 객체의 Read & Write 처리를 ReaderWriterLockSlim 활용하여
|
||||
// 동기화해주는 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public class Synchronizer<TObject>
|
||||
where TObject : class
|
||||
{
|
||||
private readonly ReaderWriterLockSlim m_rw_lock;
|
||||
|
||||
private readonly TObject m_value;
|
||||
|
||||
public Synchronizer(TObject value)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(value, () => $"value is null !!!");
|
||||
|
||||
m_value = value;
|
||||
|
||||
var dynamic_obj = (value as dynamic);
|
||||
ArgumentNullException.ThrowIfNull(dynamic_obj, "dynamic_obj is null !!!");
|
||||
|
||||
m_rw_lock = dynamic_obj.getRWLock();
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 호출자의 읽기를 위한 로직을 동기화 해준다.
|
||||
// ReaderWriterLockSlim의 EnterReadLock() 호출하여 동기화 해준다.
|
||||
//=============================================================================================
|
||||
public TResult callReadFunc<TResult>(Func<TObject, TResult> readFunc)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(readFunc, () => $"readFunc is null !!!");
|
||||
|
||||
m_rw_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return readFunc(m_value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_rw_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 호출자의 쓰기를 위한 로직을 동기화 해준다.
|
||||
// ReaderWriterLockSlim의 EnterWriteLock() 호출하여 동기화 해준다.
|
||||
//=============================================================================================
|
||||
public TResult callWriteFunc<TResult>(Func<TObject, TResult> writeFunc)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(writeFunc, () => $"writeFunc is null !!!");
|
||||
|
||||
m_rw_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
return writeFunc(m_value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_rw_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable tryReadLock()
|
||||
{
|
||||
m_rw_lock.EnterReadLock();
|
||||
|
||||
return new Unlocker(m_rw_lock, isReadLock: true);
|
||||
}
|
||||
|
||||
public IDisposable tryWriteLock()
|
||||
{
|
||||
m_rw_lock.EnterWriteLock();
|
||||
|
||||
return new Unlocker(m_rw_lock, isReadLock: false);
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// Synchronizer를 복수개로 처리할 때 사용하는 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public class MultipleSynchronizer<TObject>
|
||||
where TObject : class
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, Synchronizer<TObject>> m_synchronizers = new();
|
||||
|
||||
public MultipleSynchronizer(Dictionary<string, TObject> toSyncObjects)
|
||||
{
|
||||
foreach (var each in toSyncObjects)
|
||||
{
|
||||
var synchronizer = new Synchronizer<TObject>(each.Value);
|
||||
m_synchronizers[each.Key] = synchronizer;
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 등록된 객체중에서 locks 파라메터에 의해 선택적으로 Read & Write Lock을 설정해 준다.
|
||||
//=============================================================================================
|
||||
|
||||
public MultiLock tryLockAll(IEnumerable<(string Key, bool IsReadLock)> locks)
|
||||
{
|
||||
return new MultiLock(m_synchronizers, locks);
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 등록된 모든 객체를 Read Lock 설정을 해준다.
|
||||
//=============================================================================================
|
||||
public MultiReadLock tryReadLockAll()
|
||||
{
|
||||
return new MultiReadLock(m_synchronizers);
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 등록된 모든 객체를 Write Lock 설정을 해준다.
|
||||
//=============================================================================================
|
||||
public MultiWriteLock tryWriteLockAll()
|
||||
{
|
||||
return new MultiWriteLock(m_synchronizers);
|
||||
}
|
||||
|
||||
public Synchronizer<TObject> this[string index] => m_synchronizers[index];
|
||||
|
||||
|
||||
public class MultiLock : IDisposable
|
||||
{
|
||||
private readonly List<IDisposable> m_lock_objects;
|
||||
|
||||
public MultiLock(ConcurrentDictionary<string, Synchronizer<TObject>> syncObjects, IEnumerable<(string Key, bool IsReadLock)> locks)
|
||||
{
|
||||
m_lock_objects = new List<IDisposable>();
|
||||
|
||||
foreach (var (key, isReadLock) in locks)
|
||||
{
|
||||
if (true == syncObjects.TryGetValue(key, out var found_object))
|
||||
{
|
||||
m_lock_objects.Add(isReadLock ? found_object.tryReadLock() : found_object.tryWriteLock());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var lock_obj in m_lock_objects)
|
||||
{
|
||||
lock_obj.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MultiReadLock : IDisposable
|
||||
{
|
||||
private readonly List<IDisposable> m_lock_objects;
|
||||
|
||||
public MultiReadLock(IEnumerable<KeyValuePair<string, Synchronizer<TObject>>> syncObjects)
|
||||
{
|
||||
m_lock_objects = new List<IDisposable>();
|
||||
|
||||
foreach (var each in syncObjects)
|
||||
{
|
||||
m_lock_objects.Add(each.Value.tryReadLock());
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var lock_obj in m_lock_objects)
|
||||
{
|
||||
lock_obj.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MultiWriteLock : IDisposable
|
||||
{
|
||||
private readonly List<IDisposable> m_lock_objects;
|
||||
|
||||
public MultiWriteLock(IEnumerable<KeyValuePair<string, Synchronizer<TObject>>> syncObjects)
|
||||
{
|
||||
m_lock_objects = new List<IDisposable>();
|
||||
|
||||
foreach (var each in syncObjects)
|
||||
{
|
||||
m_lock_objects.Add(each.Value.tryWriteLock());
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var lock_obj in m_lock_objects)
|
||||
{
|
||||
lock_obj.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 병렬 프로그래밍 환경에서 특정 객체의 Read & Write 처리를 ReaderWriterLockSlim 활용하여
|
||||
// async 기반으로 동기화해주는 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public class SynchronizerAsync<TObject>
|
||||
where TObject : class
|
||||
{
|
||||
private readonly ReaderWriterLockSlim m_rw_lock;
|
||||
|
||||
private readonly TObject m_value;
|
||||
|
||||
public SynchronizerAsync(TObject value)
|
||||
{
|
||||
m_value = value;
|
||||
m_rw_lock = (value as dynamic).getRWLock();
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 호출자의 읽기를 위한 로직을 async 기반으로 동기화 해준다.
|
||||
// ReaderWriterLockSlim의 tryReadLockAsync() 호출하여 동기화 해준다.
|
||||
//=============================================================================================
|
||||
public async Task<TResult> callReadFuncAsync<TResult>(Func<TObject, Task<TResult>> readFunc)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(readFunc, () => $"readFunc is null !!!");
|
||||
|
||||
await m_rw_lock.tryReadLockAsync();
|
||||
try
|
||||
{
|
||||
return await readFunc(m_value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_rw_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 호출자의 쓰기를 위한 로직을 async 기반으로 동기화 해준다.
|
||||
// ReaderWriterLockSlim의 tryWriteLockAsync() 호출하여 동기화 해준다.
|
||||
//=============================================================================================
|
||||
public async Task<TResult> callWriteFuncAsync<TResult>(Func<TObject, Task<TResult>> writeFunc)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(writeFunc, () => $"writeFunc is null !!!");
|
||||
|
||||
await m_rw_lock.tryWriteLockAsync();
|
||||
try
|
||||
{
|
||||
return await writeFunc(m_value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_rw_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IDisposable> tryReadLockAsync()
|
||||
{
|
||||
return await m_rw_lock.tryReadLockAsync();
|
||||
}
|
||||
|
||||
public async Task<IDisposable> tryWriteLockAsync()
|
||||
{
|
||||
return await m_rw_lock.tryWriteLockAsync();
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// SynchronizerAsync를 복수개로 처리할 때 사용하는 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public class MultipleSynchronizerAsync<TObject>
|
||||
where TObject : class
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, SynchronizerAsync<TObject>> m_synchronizers = new();
|
||||
|
||||
public MultipleSynchronizerAsync(Dictionary<string, TObject> toSyncObjects)
|
||||
{
|
||||
foreach (var each in toSyncObjects)
|
||||
{
|
||||
var synchronizer = new SynchronizerAsync<TObject>(each.Value);
|
||||
m_synchronizers[each.Key] = synchronizer;
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 등록된 객체중에서 locks 파라메터에 의해 선택적으로 Read & Write Lock을 설정해 준다.
|
||||
//=============================================================================================
|
||||
|
||||
public MultiLockAsync tryLockAllAsync(IEnumerable<(string Key, bool IsReadLock)> locks)
|
||||
{
|
||||
return new MultiLockAsync(m_synchronizers, locks);
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 등록된 모든 객체를 Read Lock 설정을 해준다.
|
||||
//=============================================================================================
|
||||
public MultiReadLockAsync tryReadLockAllAsync()
|
||||
{
|
||||
return new MultiReadLockAsync(m_synchronizers);
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 등록된 모든 객체를 Write Lock 설정을 해준다.
|
||||
//=============================================================================================
|
||||
public MultiWriteLockAsync tryWriteLockAllAsync()
|
||||
{
|
||||
return new MultiWriteLockAsync(m_synchronizers);
|
||||
}
|
||||
|
||||
public SynchronizerAsync<TObject> this[string index] => m_synchronizers[index];
|
||||
|
||||
|
||||
public class MultiLockAsync : IDisposable
|
||||
{
|
||||
private readonly List<IDisposable> m_lock_objects;
|
||||
|
||||
public MultiLockAsync(ConcurrentDictionary<string, SynchronizerAsync<TObject>> syncObjects, IEnumerable<(string Key, bool IsReadLock)> locks)
|
||||
{
|
||||
m_lock_objects = new List<IDisposable>();
|
||||
|
||||
foreach (var (key, isReadLock) in locks)
|
||||
{
|
||||
if (true == syncObjects.TryGetValue(key, out var found_object))
|
||||
{
|
||||
m_lock_objects.Add(isReadLock ? found_object.tryReadLockAsync() : found_object.tryWriteLockAsync());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var lock_obj in m_lock_objects)
|
||||
{
|
||||
lock_obj.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MultiReadLockAsync : IDisposable
|
||||
{
|
||||
private readonly List<IDisposable> m_lock_objects;
|
||||
|
||||
public MultiReadLockAsync(IEnumerable<KeyValuePair<string, SynchronizerAsync<TObject>>> syncObjects)
|
||||
{
|
||||
m_lock_objects = new List<IDisposable>();
|
||||
|
||||
foreach (var each in syncObjects)
|
||||
{
|
||||
m_lock_objects.Add(each.Value.tryReadLockAsync());
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var lock_obj in m_lock_objects)
|
||||
{
|
||||
lock_obj.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MultiWriteLockAsync : IDisposable
|
||||
{
|
||||
private readonly List<IDisposable> m_lock_objects;
|
||||
|
||||
public MultiWriteLockAsync(IEnumerable<KeyValuePair<string, SynchronizerAsync<TObject>>> syncObjects)
|
||||
{
|
||||
m_lock_objects = new List<IDisposable>();
|
||||
|
||||
foreach (var each in syncObjects)
|
||||
{
|
||||
m_lock_objects.Add(each.Value.tryWriteLockAsync());
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var lock_obj in m_lock_objects)
|
||||
{
|
||||
lock_obj.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// Unlocker가 소멸될 때 ReaderWriterLockSlim가 Exit 될 수 있도록 해주는 클래스 이다.
|
||||
//=============================================================================================
|
||||
public class Unlocker : IDisposable
|
||||
{
|
||||
private readonly ReaderWriterLockSlim m_ref_rw_lock;
|
||||
private readonly bool m_is_read_lock;
|
||||
|
||||
public Unlocker(ReaderWriterLockSlim readWriteLockObject, bool isReadLock)
|
||||
{
|
||||
m_ref_rw_lock = readWriteLockObject;
|
||||
m_is_read_lock = isReadLock;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_is_read_lock)
|
||||
{
|
||||
m_ref_rw_lock.ExitReadLock();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ref_rw_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
35
ServerCore/Sync/SynchronizerHelper.cs
Normal file
35
ServerCore/Sync/SynchronizerHelper.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public static class SynchronizerHelper
|
||||
{
|
||||
public static async Task<IDisposable> tryReadLockAsync(this ReaderWriterLockSlim rwLock)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(rwLock, () => $"rwLock is null !!!");
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
rwLock.EnterReadLock();
|
||||
return (IDisposable) new Unlocker(rwLock, false);
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task<IDisposable> tryWriteLockAsync(this ReaderWriterLockSlim rwLock)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(rwLock, () => $"rwLock is null !!!");
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
rwLock.EnterWriteLock();
|
||||
return (IDisposable) new Unlocker(rwLock, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
40
ServerCore/Sync/TimeSyncHelper.cs
Normal file
40
ServerCore/Sync/TimeSyncHelper.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 서버로 부터 수신한 시간을 기준 시간으로 설정시켜
|
||||
// 서버 시간을 기준으로 동기화되어 관리될 수 있는 기능을 지원하는 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
public class TimeSyncHelper
|
||||
{
|
||||
private readonly string m_server_type = string.Empty;
|
||||
private DateTime m_server_time = ConstDateTimeValue.MinTime;
|
||||
private System.Diagnostics.Stopwatch m_stop_watch = new System.Diagnostics.Stopwatch();
|
||||
public TimeSyncHelper(string serverType)
|
||||
{
|
||||
m_server_type = serverType;
|
||||
}
|
||||
public void setupFromTimeServer(DateTime serverTime, TimeSpan ping)
|
||||
{
|
||||
m_server_time = serverTime.toUtcTime().AddMilliseconds(ping.TotalMilliseconds / 2);
|
||||
m_stop_watch = m_stop_watch ?? new System.Diagnostics.Stopwatch();
|
||||
m_stop_watch.Reset();
|
||||
m_stop_watch.Start();
|
||||
}
|
||||
|
||||
public DateTime getCurrentByTimeServer()
|
||||
{
|
||||
return m_server_time + m_stop_watch.Elapsed;
|
||||
}
|
||||
|
||||
}
|
||||
31
ServerCore/Task/LogicThread.cs
Normal file
31
ServerCore/Task/LogicThread.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using Nito.AsyncEx;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public class LogicThread
|
||||
{
|
||||
static AsyncContextThread? Thread;
|
||||
static public TaskFactory Factory = Task.Factory;
|
||||
|
||||
static public void start(bool singleThreaded)
|
||||
{
|
||||
if (singleThreaded == true)
|
||||
{
|
||||
Thread = new AsyncContextThread();
|
||||
Factory = Thread.Factory;
|
||||
}
|
||||
}
|
||||
|
||||
static public void join()
|
||||
{
|
||||
Thread?.Join();
|
||||
}
|
||||
}
|
||||
24
ServerCore/Task/MultiThreadHelper.cs
Normal file
24
ServerCore/Task/MultiThreadHelper.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 멀티스레드 관련 각종 확장 함수
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public static class MultiThreadHelper
|
||||
{
|
||||
public static int getThreadOptimalCount()
|
||||
{
|
||||
return Convert.ToInt32(Math.Ceiling(Environment.ProcessorCount * 0.75 * 2.0));
|
||||
}
|
||||
}
|
||||
244
ServerCore/Task/TaskSerializer.cs
Normal file
244
ServerCore/Task/TaskSerializer.cs
Normal file
@@ -0,0 +1,244 @@
|
||||
//#define LOCK_LOG_ON // Lock 관련 로그 활성화 설정
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using Axion.Collections.Concurrent;
|
||||
using NeoSmart.AsyncLock;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// TaskSerializer에 Func<Task> 함수를 담기위한 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
public class LogicFunction
|
||||
{
|
||||
private readonly string m_trans_id;
|
||||
private readonly Func<Task> m_logic_func;
|
||||
private readonly string m_logic_func_name;
|
||||
|
||||
public LogicFunction(Func<Task> logicFunc, string logicFuncName)
|
||||
{
|
||||
m_trans_id = string.Empty;
|
||||
m_logic_func = logicFunc;
|
||||
m_logic_func_name = logicFuncName;
|
||||
}
|
||||
|
||||
public LogicFunction(string transId, Func<Task> logicFunc, string logicFuncName)
|
||||
{
|
||||
m_trans_id = transId;
|
||||
m_logic_func = logicFunc;
|
||||
m_logic_func_name = logicFuncName;
|
||||
}
|
||||
|
||||
public string getTransId() => m_trans_id;
|
||||
|
||||
public Func<Task> getLogicFunc() => m_logic_func;
|
||||
|
||||
public string getLogicFuncName() => m_logic_func_name;
|
||||
|
||||
public string toBasicString()
|
||||
{
|
||||
return $"LogicFunc(TransId:{m_trans_id}, LogicFuncName:{m_logic_func_name})";
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// Func<Task> 함수를 등록하면 단일 Task 기반으로 실행시켜주는 클래스 이다 !!!
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
public class TaskSerializer
|
||||
{
|
||||
private readonly ConcurrentHashSet<string> m_waiting_trans_ids = new();
|
||||
|
||||
private readonly ConcurrentQueue<LogicFunction> m_queue_logic_funcs = new();
|
||||
private readonly SemaphoreSlim m_semaphore = new SemaphoreSlim(0);
|
||||
|
||||
private Task? m_processing_task;
|
||||
|
||||
private AsyncLock m_lock = new();
|
||||
|
||||
|
||||
public TaskSerializer()
|
||||
{}
|
||||
|
||||
public async Task<bool> postLogicFuncWithTransId(string transId, Func<Task> logicFunc, string logicFuncName = "")
|
||||
{
|
||||
var logic_func = new LogicFunction(transId, logicFunc, logicFuncName);
|
||||
|
||||
if (false == m_waiting_trans_ids.TryAdd(transId, out _))
|
||||
{
|
||||
Log.getLogger().warn( $"Falied to Enqueue LogicFunc in TaskSerializer.postLogicFuncWithTransId() !!! : {logic_func.toBasicString()}"
|
||||
+ $" - transId:{transId}, QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await enqueueLogicFunc(logic_func);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().error( $"Failed to enqueueLogicFunc() in TaskSerializer.postLogicFuncWithTransId() !!!, Exception:{e}, {logic_func.toBasicString()}"
|
||||
+ $" - transId:{transId}, QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}" );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> postLogicFunc(Func<Task> logicFunc, string logicFuncName = "")
|
||||
{
|
||||
var logic_func = new LogicFunction(logicFunc, logicFuncName);
|
||||
|
||||
try
|
||||
{
|
||||
await enqueueLogicFunc(logic_func);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().error( $"Failed to enqueueLogicFunc() in TaskSerializer.postLogicFunc() !!!, Exception:{e}, {logic_func.toBasicString()}"
|
||||
+ $" - QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}" );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task enqueueLogicFunc(LogicFunction logicFunc)
|
||||
{
|
||||
m_queue_logic_funcs.Enqueue(logicFunc);
|
||||
m_semaphore.Release();
|
||||
|
||||
#if TASK_SERIALIZER_LOG_ON
|
||||
Log.getLogger().debug( $"Enqueue LogicFunc in TaskSerializer.enqueueLogicFunc() !!! : {logicFunc.toBasicString()}"
|
||||
+ $" - QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}");
|
||||
#endif//TASK_SERIALIZER_LOG_ON
|
||||
|
||||
#if LOCK_LOG_ON
|
||||
Log.getLogger().debug( $"LOCK TRY in TaskSerializer.enqueueLogicFunc() !!! : {logicFunc.toBasicString()}"
|
||||
+ $" - QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}");
|
||||
#endif//LOCK_LOG_ON
|
||||
using (await m_lock.LockAsync())
|
||||
{
|
||||
#if LOCK_LOG_ON
|
||||
Log.getLogger().debug( $"LOCKED in TaskSerializer.enqueueLogicFunc() !!! : {logicFunc.toBasicString()}"
|
||||
+ $" - QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}" );
|
||||
#endif//LOCK_LOG_ON
|
||||
if (null == m_processing_task || true == m_processing_task.IsCompleted)
|
||||
{
|
||||
m_processing_task = await LogicThread.Factory.StartNew( runTask
|
||||
, CancellationToken.None
|
||||
, TaskCreationOptions.DenyChildAttach
|
||||
, TaskScheduler.Default
|
||||
).ConfigureAwait(false);
|
||||
|
||||
#if TASK_SERIALIZER_LOG_ON
|
||||
Log.getLogger().debug( $"Create Task in TaskSerializer.enqueueLogicFunc() !!! : {logicFunc.toBasicString()}"
|
||||
+ $" - QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}");
|
||||
#endif//TASK_SERIALIZER_LOG_ON
|
||||
}
|
||||
#if LOCK_LOG_ON
|
||||
Log.getLogger().debug( $"UNLOCK in TaskSerializer.enqueueLogicFunc() !!! : {logicFunc.toBasicString()}"
|
||||
+ $" - QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}");
|
||||
#endif//LOCK_LOG_ON
|
||||
}
|
||||
}
|
||||
|
||||
private async Task runTask()
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
await m_semaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (true == m_queue_logic_funcs.TryDequeue(out var next_logic_func))
|
||||
{
|
||||
var trans_id = string.Empty;
|
||||
Func<Task>? logic_func = null;
|
||||
var logic_func_name = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
NullReferenceCheckHelper.throwIfNull(next_logic_func, () => $"next_logic_func is null !!!");
|
||||
|
||||
trans_id = next_logic_func.getTransId();
|
||||
logic_func = next_logic_func.getLogicFunc();
|
||||
NullReferenceCheckHelper.throwIfNull(logic_func, () => $"logic_func is null !!! - transId:{trans_id}");
|
||||
|
||||
logic_func_name = next_logic_func.getLogicFuncName();
|
||||
|
||||
#if TASK_SERIALIZER_LOG_ON
|
||||
Log.getLogger().debug( $"Dequeue LogicFunc in TaskSerializer.runTask() : {next_logic_func.toBasicString()}"
|
||||
+ $" - QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}");
|
||||
#endif//TASK_SERIALIZER_LOG_ON
|
||||
if (false == trans_id.isNullOrWhiteSpace())
|
||||
{
|
||||
m_waiting_trans_ids.Remove(trans_id);
|
||||
}
|
||||
|
||||
await logic_func().ConfigureAwait(false);
|
||||
|
||||
#if TASK_SERIALIZER_LOG_ON
|
||||
Log.getLogger().debug( $"logic_func().ConfigureAwait(false) in TaskSerializer.runTask() !!!"
|
||||
+ $" - {next_logic_func.toBasicString()}, QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}");
|
||||
#endif//TASK_SERIALIZER_LOG_ON
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.getLogger().debug( $"Failed to logic_func().ConfigureAwait(false) in TaskSerializer.runTask() !!!, Exception:{e}, {next_logic_func.toBasicString()}"
|
||||
+ $" - QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}" );
|
||||
}
|
||||
}
|
||||
|
||||
#if LOCK_LOG_ON
|
||||
Log.getLogger().debug( $"LOCK TRY by TaskSerializer.runTask() !!! : {next_logic_func.toBasicString()}"
|
||||
+ $" - QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}");
|
||||
#endif//LOCK_LOG_ON
|
||||
using (await m_lock.LockAsync())
|
||||
{
|
||||
#if LOCK_LOG_ON
|
||||
Log.getLogger().debug( $"LOCKED by TaskSerializer.runTask() !!! : {next_logic_func.toBasicString()}"
|
||||
+ $" - QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}" );
|
||||
#endif//LOCK_LOG_ON
|
||||
if (true == m_queue_logic_funcs.IsEmpty)
|
||||
{
|
||||
m_processing_task = null;
|
||||
|
||||
NullReferenceCheckHelper.throwIfNull(next_logic_func, () => $"next_logic_func is null !!!");
|
||||
|
||||
Log.getLogger().debug( $"Release Task in TaskSerializer.runTask() !!! : {next_logic_func.toBasicString()}"
|
||||
+ $" - QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}");
|
||||
|
||||
#if LOCK_LOG_ON
|
||||
Log.getLogger().debug( $"UNLOCK by TaskSerializer.runTask() !!! : {next_logic_func.toBasicString()}"
|
||||
+ $" - QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}");
|
||||
#endif//LOCK_LOG_ON
|
||||
return;
|
||||
}
|
||||
#if LOCK_LOG_ON
|
||||
Log.getLogger().debug( $"UNLOCK by TaskSerializer.runTask() !!! : {next_logic_func.toBasicString()}"
|
||||
+ $" - QLFC:{getQueueingLogicFuncCount()}, WTIDC:{m_waiting_trans_ids.Count}");
|
||||
#endif//LOCK_LOG_ON
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Int32 getQueueingLogicFuncCount() => m_queue_logic_funcs.Count;
|
||||
|
||||
public Task? getProcessingTask() => m_processing_task;
|
||||
}
|
||||
131
ServerCore/Timer/PeriodicTaskTimer.cs
Normal file
131
ServerCore/Timer/PeriodicTaskTimer.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 주기적 Task 타이머 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public class PeriodicTaskTimer : IAsyncDisposable
|
||||
{
|
||||
private readonly string m_name;
|
||||
private readonly System.Threading.PeriodicTimer m_timer;
|
||||
private readonly Task m_timer_task;
|
||||
private readonly CancellationTokenSource m_cts;
|
||||
|
||||
public delegate Task FnFunction();
|
||||
private readonly FnFunction m_fn_alarm_function;
|
||||
|
||||
public PeriodicTaskTimer(string name, Int32 intervalSec, FnFunction fnAlarmFunction)
|
||||
{
|
||||
m_name = name;
|
||||
m_cts = new CancellationTokenSource();
|
||||
|
||||
m_timer = new System.Threading.PeriodicTimer(TimeSpan.FromSeconds(intervalSec));
|
||||
|
||||
m_fn_alarm_function = fnAlarmFunction;
|
||||
m_timer_task = handleTimerAsync(m_timer, m_cts.Token);
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// CancellationTokenSource 를 외부에서 제어할 때 사용한다.
|
||||
//=============================================================================================
|
||||
public PeriodicTaskTimer(string name, double intervalMSec, CancellationTokenSource cts, FnFunction fnAlarmFunction)
|
||||
{
|
||||
m_name = name;
|
||||
m_cts = cts;
|
||||
|
||||
m_timer = new System.Threading.PeriodicTimer(TimeSpan.FromMilliseconds(intervalMSec));
|
||||
|
||||
m_fn_alarm_function = fnAlarmFunction;
|
||||
m_timer_task = handleTimerAsync(m_timer, m_cts.Token);
|
||||
}
|
||||
|
||||
private async Task handleTimerAsync(System.Threading.PeriodicTimer timer, CancellationToken cancel = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (await timer.WaitForNextTickAsync(cancel))
|
||||
{
|
||||
await Task.Run(() => m_fn_alarm_function.Invoke());
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
var msg = $"PeriodicTaskTimer cancel Operation !!! : exception:{e} - {m_name}";
|
||||
Log.getLogger().info(msg);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"PeriodicTaskTimer exception !!! : exception:{e} - {m_name}";
|
||||
Log.getLogger().error(err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void cancelTimer() => m_cts.Cancel();
|
||||
|
||||
//for IAsyncDisposable.DisposeAsync()
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
m_timer.Dispose();
|
||||
await m_timer_task;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public System.Threading.PeriodicTimer getPeriodicTimer() => m_timer;
|
||||
|
||||
public Task getTask() => m_timer_task;
|
||||
}
|
||||
|
||||
|
||||
//=============================================================================================
|
||||
// 주기적 Task 타이머 지원 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
public static class PeriodicTaskHelper
|
||||
{
|
||||
public static async Task runTask(Func<Task> action, TimeSpan interval, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var timer = new PeriodicTimer(interval);
|
||||
while (cancellationToken.IsCancellationRequested == false)
|
||||
{
|
||||
try
|
||||
{
|
||||
await action();
|
||||
await timer.WaitForNextTickAsync(cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
var msg = $"PeriodicTaskHelper cancel Operation !!! : Msg:{e.Message}";
|
||||
Log.getLogger().info(msg);
|
||||
break;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var err_msg = $"PeriodicTaskHelper exception !!! : errMsg:{e.Message}";
|
||||
Log.getLogger().error(err_msg);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void bindToTasks(this PeriodicTaskTimer taskTimer, List<Task> tasks)
|
||||
{
|
||||
tasks.Add(taskTimer.getTask());
|
||||
}
|
||||
}
|
||||
|
||||
243
ServerCore/Timer/SimpleTimer.cs
Normal file
243
ServerCore/Timer/SimpleTimer.cs
Normal file
@@ -0,0 +1,243 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 간단한 로직을 위한 타이머 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public class SimpleTimer
|
||||
{
|
||||
private DateTime m_begin_timestamp = DateTime.MinValue;
|
||||
private DateTime m_end_timestamp = DateTime.MinValue;
|
||||
|
||||
private Int64 m_time_limit_msec = 0;
|
||||
|
||||
private bool m_is_paused = false;
|
||||
private DateTime m_pause_timestamp = new DateTime();
|
||||
private Int64 m_pause_total_msec = 0;
|
||||
|
||||
private bool m_active = false;
|
||||
|
||||
public SimpleTimer()
|
||||
{
|
||||
}
|
||||
|
||||
public SimpleTimer(Int64 timeLimitMSec)
|
||||
{
|
||||
setTimeLimitMSec(timeLimitMSec);
|
||||
}
|
||||
|
||||
public void setTimer(Int64 timeLimitMSec)
|
||||
{
|
||||
setTimer(0, timeLimitMSec);
|
||||
}
|
||||
|
||||
public void setTimer(Int64 beginUtcTick, Int64 timeLimitMSec)
|
||||
{
|
||||
m_begin_timestamp = beginUtcTick > 0 ? new DateTime(beginUtcTick) : DateTime.UtcNow;
|
||||
m_time_limit_msec = timeLimitMSec;
|
||||
|
||||
m_is_paused = false;
|
||||
m_pause_total_msec = 0;
|
||||
|
||||
m_end_timestamp = DateTime.MinValue;
|
||||
}
|
||||
|
||||
public void activate(Int64 timeLimitMSec = 0)
|
||||
{
|
||||
m_active = true;
|
||||
if (timeLimitMSec != 0)
|
||||
{
|
||||
setTimer(0, timeLimitMSec);
|
||||
}
|
||||
else
|
||||
{
|
||||
reset(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void activate(Int64 beginUtcTick, Int64 timeLimitMSec = 0)
|
||||
{
|
||||
m_active = true;
|
||||
if (timeLimitMSec != 0)
|
||||
{
|
||||
setTimer(beginUtcTick, timeLimitMSec);
|
||||
}
|
||||
else
|
||||
{
|
||||
reset(beginUtcTick);
|
||||
}
|
||||
}
|
||||
|
||||
public bool isActive()
|
||||
{
|
||||
return m_active;
|
||||
}
|
||||
|
||||
public void deactivate()
|
||||
{
|
||||
m_active = false;
|
||||
m_end_timestamp = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void setTimeLimitMSec(Int64 limitMSec)
|
||||
{
|
||||
m_time_limit_msec = limitMSec;
|
||||
|
||||
m_time_limit_msec = Math.Max(m_time_limit_msec, 0);
|
||||
}
|
||||
|
||||
public void incTimeLimitMSec(Int64 incMSec)
|
||||
{
|
||||
m_time_limit_msec += incMSec;
|
||||
|
||||
m_time_limit_msec = Math.Max(m_time_limit_msec, 0);
|
||||
}
|
||||
|
||||
public Int64 getTimeLimitMSec()
|
||||
{
|
||||
return m_time_limit_msec;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
setTimer(m_time_limit_msec);
|
||||
}
|
||||
|
||||
public void reset(Int64 beginUtcTick)
|
||||
{
|
||||
setTimer(beginUtcTick, m_time_limit_msec);
|
||||
}
|
||||
|
||||
public bool expired()
|
||||
{
|
||||
if (false == isActive())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (true == isPause())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (DateTime.UtcNow - m_begin_timestamp).TotalMilliseconds > (m_pause_total_msec + m_time_limit_msec);
|
||||
}
|
||||
|
||||
public void pause()
|
||||
{
|
||||
if (isPause())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_is_paused = true;
|
||||
m_pause_timestamp = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public bool isPause()
|
||||
{
|
||||
return m_is_paused;
|
||||
}
|
||||
|
||||
public void unpause()
|
||||
{
|
||||
if (false == isPause())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pause_time = (DateTime.UtcNow - m_pause_timestamp).TotalMilliseconds;
|
||||
m_pause_total_msec += (Int64)pause_time;
|
||||
|
||||
m_is_paused = false;
|
||||
}
|
||||
|
||||
public Int64 getTotalTimeMSec()
|
||||
{
|
||||
if (!isActive())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var end_time = m_begin_timestamp.AddMilliseconds(m_time_limit_msec);
|
||||
|
||||
var total_time = (end_time - m_begin_timestamp).TotalMilliseconds;
|
||||
|
||||
total_time = Math.Max(total_time, 0);
|
||||
|
||||
return (Int64)total_time;
|
||||
}
|
||||
|
||||
public Int64 getElapsedPauseTimeMSec()
|
||||
{
|
||||
if (false == isActive())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var pause_time = m_pause_total_msec;
|
||||
|
||||
if (true == isPause())
|
||||
{
|
||||
pause_time += (Int64)((DateTime.UtcNow - m_pause_timestamp).TotalMilliseconds);
|
||||
}
|
||||
|
||||
pause_time = Math.Max(pause_time, 0);
|
||||
|
||||
return (Int64)pause_time;
|
||||
}
|
||||
|
||||
public bool isOn()
|
||||
{
|
||||
if (false == expired())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public DateTime getStartedTime() => m_begin_timestamp;
|
||||
public DateTime getEndTime() => m_end_timestamp;
|
||||
|
||||
public Int64 getRemainTimeMSec()
|
||||
{
|
||||
if (false == isActive())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var pause_time = getElapsedPauseTimeMSec();
|
||||
|
||||
TimeSpan time_span = DateTime.UtcNow - m_begin_timestamp;
|
||||
var remain_time = m_time_limit_msec + pause_time - (Int64)time_span.TotalMilliseconds;
|
||||
remain_time = Math.Max(remain_time, 0);
|
||||
|
||||
return remain_time;
|
||||
}
|
||||
|
||||
public Int64 getElapsedTimeMSec()
|
||||
{
|
||||
if (false == isActive())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
TimeSpan time_span = DateTime.UtcNow - m_begin_timestamp;
|
||||
return (Int64)time_span.TotalMilliseconds;
|
||||
}
|
||||
|
||||
}
|
||||
160
ServerCore/Timer/StopwatchTimer.cs
Normal file
160
ServerCore/Timer/StopwatchTimer.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// StopWatch 모듈을 활용한 타이머 기능을 제공하는 클래스 이다.
|
||||
//
|
||||
// author : kangms
|
||||
//=============================================================================================
|
||||
|
||||
public class StopwatchTimer
|
||||
{
|
||||
private readonly System.Diagnostics.Stopwatch m_stop_watch = new System.Diagnostics.Stopwatch();
|
||||
private readonly TimeSpan m_interval = default(TimeSpan);// 체크 주기
|
||||
private readonly bool m_is_manual_restart = false; // 수동 재시작 여부
|
||||
private bool m_force_on = false;
|
||||
|
||||
public StopwatchTimer(TimeSpan intreval, bool is_pause = false, bool is_manual_restart = false)
|
||||
{
|
||||
m_interval = intreval;
|
||||
m_is_manual_restart = is_manual_restart;
|
||||
|
||||
if (is_pause)
|
||||
{
|
||||
pause();
|
||||
}
|
||||
else
|
||||
{
|
||||
resume();
|
||||
}
|
||||
}
|
||||
|
||||
public StopwatchTimer(Int64 interval_milliseconds, bool is_pause = false, bool is_manual_restart = false)
|
||||
: this(new TimeSpan(interval_milliseconds * TimeSpan.TicksPerMillisecond), is_pause, is_manual_restart)
|
||||
{
|
||||
}
|
||||
|
||||
public StopwatchTimer(float intreval_seconds, bool is_pause = false, bool is_manual_restart = false)
|
||||
: this((Int64)(intreval_seconds * 1000.0f), is_pause, is_manual_restart)
|
||||
{
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 지정된 시간 만큼 경과 되었는지 검사한다.
|
||||
//=========================================================================================
|
||||
public bool isOn()
|
||||
{
|
||||
if (m_force_on)
|
||||
{
|
||||
m_force_on = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_stop_watch.IsRunning == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Elapsed < m_interval)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_is_manual_restart)
|
||||
{
|
||||
// 한번만 실행되고 멈춘다, 다시 시작하려면, restart를 호출해야 한다.
|
||||
pause();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 자동리셋이면 재시작
|
||||
restart();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 강제로 타이머를 On상태로 만든다.
|
||||
public void forceOn()
|
||||
{
|
||||
m_force_on = true;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// isOn의 기능과, 경과시간을 얻어온다.
|
||||
//=========================================================================================
|
||||
public bool isOn(out TimeSpan elapsed)
|
||||
{
|
||||
elapsed = Elapsed;
|
||||
return isOn();
|
||||
}
|
||||
|
||||
|
||||
//=========================================================================================
|
||||
// 타이머가 멈춰있는가?
|
||||
//=========================================================================================
|
||||
public bool isPaused()
|
||||
{
|
||||
return m_stop_watch.IsRunning == false;
|
||||
}
|
||||
|
||||
|
||||
//=========================================================================================
|
||||
// 타이머가 실행중인가?
|
||||
//=========================================================================================
|
||||
public bool isRunning()
|
||||
{
|
||||
return m_stop_watch.IsRunning;
|
||||
}
|
||||
|
||||
|
||||
//=========================================================================================
|
||||
// 일시정지
|
||||
//=========================================================================================
|
||||
public void pause()
|
||||
{
|
||||
m_stop_watch.Stop();
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 시작 또는 일시정지를 종료하고 다시 시작
|
||||
//=========================================================================================
|
||||
public void resume()
|
||||
{
|
||||
m_stop_watch.Start();
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 타이머를 초기화하고, 시작
|
||||
//=========================================================================================
|
||||
public TimeSpan restart()
|
||||
{
|
||||
TimeSpan elapsed = m_stop_watch.Elapsed;
|
||||
m_stop_watch.Reset();
|
||||
m_stop_watch.Start();
|
||||
return elapsed;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 경과 시간 조회
|
||||
//=========================================================================================
|
||||
public TimeSpan Elapsed
|
||||
{
|
||||
get { return m_stop_watch.Elapsed; }
|
||||
}
|
||||
//=========================================================================================
|
||||
// 남은 시간 조회
|
||||
//=========================================================================================
|
||||
public TimeSpan RemainTime
|
||||
{
|
||||
get { return Elapsed.TotalSeconds > 0 ? (m_interval > Elapsed ? m_interval - Elapsed : new TimeSpan(0)) : new TimeSpan(0); }
|
||||
}
|
||||
}
|
||||
694
ServerCore/TypeHelper/DateTimeHelper.cs
Normal file
694
ServerCore/TypeHelper/DateTimeHelper.cs
Normal file
@@ -0,0 +1,694 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
/*
|
||||
//=============================================================================================
|
||||
시간 관련 편의기능 제공
|
||||
|
||||
참고
|
||||
시스템에 따라 초기값이 다르다
|
||||
MsSql : 1900-01-01
|
||||
DateTime : 0001-01-01
|
||||
Unix time : 1970-01-01
|
||||
|
||||
시간의 통합 관리를 위해서 유닉스 타임을 최소값으로 지정한다.
|
||||
//=============================================================================================
|
||||
*/
|
||||
|
||||
// 주간 요일
|
||||
public enum DayOfWeekType
|
||||
{
|
||||
Sunday = 0, // 일요일
|
||||
Monday = 1, // 월요일
|
||||
Tuesday = 2, // 화요일
|
||||
Wednesday = 3, // 수요일
|
||||
Thursday = 4, // 목요일
|
||||
Friday = 5, // 금요일
|
||||
Saturday = 6, // 토요일
|
||||
|
||||
None = 99,
|
||||
}
|
||||
|
||||
// 데이트 타임 종류
|
||||
public enum DateTimeType
|
||||
{
|
||||
// 표시된 시간이 현지 시간 또는 UTC(지역 표준시)로 지정되지 않습니다.
|
||||
Unspecified = 0,
|
||||
|
||||
// 표시된 시간이 UTC입니다.
|
||||
Utc = 1,
|
||||
|
||||
// 표시된 시간이 현지 시간입니다.
|
||||
Local = 2
|
||||
}
|
||||
|
||||
public class DateTimeInfo
|
||||
{
|
||||
// 시간대 종류
|
||||
public DateTimeType Kind = DateTimeType.Utc;
|
||||
|
||||
// 그레고리오력에서 0001년 1월 1일 00:00:00.000부터 경과한 100나노초 간격의 수로 표현한 날짜와 시간입니다.
|
||||
public Int64 Utc100NanoSecond = 0;
|
||||
|
||||
// 문자열 형식
|
||||
public string DateTime = string.Empty;
|
||||
}
|
||||
|
||||
public static class ConstDateTimeValue
|
||||
{
|
||||
public static readonly DateTime MinSqlDateTime = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
public static readonly DateTime MinUnixTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
public static readonly DateTime MinDateTime = DateTime.MinValue;
|
||||
public static readonly DateTime MinTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
public static readonly DateTime MaxTime = new DateTime(3000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
public static readonly TimeSpan SpanOneHour = new TimeSpan(1, 0, 0);
|
||||
public static readonly TimeSpan SpanOneDay = new TimeSpan(1, 0, 0, 0);
|
||||
public static readonly TimeSpan SpanOneWeek = new TimeSpan(7, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static class DateTimeHelper
|
||||
{
|
||||
public static readonly double Second2Milisecond = 1000;
|
||||
public static readonly double Minute2Second = 60;
|
||||
public static readonly double Hour2Minute = 60;
|
||||
public static readonly double Day2Hour = 24;
|
||||
public static readonly double Week2Day = 7;
|
||||
public static readonly double Hour2Second = Hour2Minute * Minute2Second;
|
||||
public static readonly double Day2Second = Day2Hour * Hour2Second;
|
||||
public static readonly double Week2Second = Week2Day * Day2Second;
|
||||
|
||||
// 리셋시각 미리 계산
|
||||
private static TimeSpan DailyResetTimespan = new TimeSpan();
|
||||
private static TimeSpan WeeklyResetTimespan = new TimeSpan();
|
||||
|
||||
// 타임서버와 동기화
|
||||
private static Dictionary<string/*server_key*/, TimeSyncHelper> m_time_syncs = new Dictionary<string, TimeSyncHelper>();
|
||||
|
||||
public static void setup(TimeSpan daily_reset_hour, TimeSpan weekly_reset_dayofweek)
|
||||
{
|
||||
DailyResetTimespan = daily_reset_hour;
|
||||
WeeklyResetTimespan = weekly_reset_dayofweek;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 현재 시각(UTC)
|
||||
//=========================================================================================
|
||||
public static DateTime Current
|
||||
{
|
||||
get
|
||||
{
|
||||
return DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 오늘 자정 시각
|
||||
//=========================================================================================
|
||||
private static DateTime TodayOclock
|
||||
{
|
||||
get { return Current.Date; }
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 내일 자정 시각
|
||||
//=========================================================================================
|
||||
private static DateTime TomorrowOclock
|
||||
{
|
||||
get { return Current.AddDays(1).Date; }
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 지정된 시각이상 경과 되었는가?
|
||||
//=========================================================================================
|
||||
public static bool hasTimeReached(DateTime targetTime)
|
||||
{
|
||||
return targetTime <= DateTimeHelper.Current;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 지정시각(at)부터 현재시각까지 경과된 시간(TotalMilisecond)
|
||||
//=========================================================================================
|
||||
public static Int64 differToCurrentMilliSeconds(DateTime at)
|
||||
{
|
||||
return (Int64)(Current - at).TotalMilliseconds;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 지정시각(from)부터 대상시각(to)까지 경과된 시간(TotalMilisecond)
|
||||
//=========================================================================================
|
||||
public static Int64 differFromToMilliSeconds(DateTime from, DateTime to)
|
||||
{
|
||||
return (Int64)(to - from).TotalMilliseconds;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 특정시각이(at) 시간 범위에 포함되는가?
|
||||
// is_include_end = true => start 이상(포함), end 이하(포함)
|
||||
// is_include_end = false -> start 이상(포함), end 미만(미포함)
|
||||
//=========================================================================================
|
||||
public static bool isBetween(DateTime at, DateTime start, DateTime end, bool is_include_end = false)
|
||||
{
|
||||
return is_include_end ? (start <= at && at <= end) : (start <= at && at < end);
|
||||
}
|
||||
|
||||
public static bool isBetween(DateTime start, DateTime end, bool is_include_end = false)
|
||||
{
|
||||
return isBetween(Current, start, end, is_include_end);
|
||||
}
|
||||
|
||||
public static bool isBetween(string start, string end, bool is_include_end = false)
|
||||
{
|
||||
return isBetween(Current, start.toUtcTime(), end.toUtcTime(), is_include_end);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 지정시각(at)에 해당하는 날자의 자정
|
||||
//=========================================================================================
|
||||
public static DateTime startOfDay(DateTime at)
|
||||
{
|
||||
return new DateTime(at.Year, at.Month, at.Day, 0, 0, 0, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 지정시각(at)에 해당하는 주의 시작일의 자정(일요일 자정)
|
||||
//=========================================================================================
|
||||
private static DateTime startOfWeek(DateTime at)
|
||||
{
|
||||
var target = at;
|
||||
return target.AddDays(System.DayOfWeek.Sunday - at.DayOfWeek).Date;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 지정시각(at)에 해당하는 월의 1일 자정
|
||||
//=========================================================================================
|
||||
public static DateTime startOfMonth(DateTime at)
|
||||
{
|
||||
return new DateTime(at.Year, at.Month, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 지정시각(at)에 해당하는 년도의 1월 1일 자정
|
||||
//=========================================================================================
|
||||
private static DateTime startOfYear(DateTime at)
|
||||
{
|
||||
return new DateTime(at.Year, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 현재 시각의 요일을 조회한다.
|
||||
//=========================================================================================
|
||||
public static DayOfWeekType getDayOfWeekType()
|
||||
{
|
||||
// 일일 리셋시간 만큼 보정하여, 요일을 계산하다.
|
||||
return (DayOfWeekType)((Current - DailyResetTimespan).DayOfWeek + 1);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 해당 요일인지 검사한다
|
||||
//=========================================================================================
|
||||
public static bool isDayOfWeek(DayOfWeekType dayOfWeekType/*, TimeSpan daily_reset*/)
|
||||
{
|
||||
// 일일 리셋시간 만큼 보정하여, 요일을 계산하다.
|
||||
return getDayOfWeekType() == dayOfWeekType;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 지정시각(at)을 기준으로 일일 리셋 시각을 계산한다.
|
||||
//=========================================================================================
|
||||
public static DateTime getDailyResetDateTime(DateTime at)
|
||||
{
|
||||
// at의 자정 시각을 얻어온다(0시 0분)
|
||||
var at_zero_oclock = startOfDay(at);
|
||||
var at_reset_oclock = at_zero_oclock + DailyResetTimespan;
|
||||
|
||||
// at이 리셋 보다 이전이면 하루전이다
|
||||
return at < at_reset_oclock ? at_reset_oclock.AddDays(-1) : at_reset_oclock;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 현재 시각(오늘) 기준으로 일일 리셋 시각을 계산한다.
|
||||
//=========================================================================================
|
||||
public static DateTime getTodayDailyResetDateTime()
|
||||
{
|
||||
return getDailyResetDateTime(Current);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 지정시각(at)을 기준으로 해당주의 리셋 시각을 계산한다.
|
||||
//=========================================================================================
|
||||
public static DateTime getWeeklyResetDateTime(DateTime at)
|
||||
{
|
||||
// 이번주의 시작 시각을 가져온다(일요일, 0시 0분)
|
||||
var at_week_zero_oclock = startOfWeek(at);
|
||||
var at_week_reset_oclock = at_week_zero_oclock + WeeklyResetTimespan;
|
||||
|
||||
// at이 리셋 보다 이전이면 일주일전이다.
|
||||
return at < at_week_reset_oclock ? at_week_reset_oclock.AddDays(-Week2Day) : at_week_reset_oclock;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 현재 시각(오늘)을 기준으로 해당주의 리셋 시각을 계산한다.
|
||||
//=========================================================================================
|
||||
public static DateTime getThisWeeklyResetDateTime()
|
||||
{
|
||||
return getWeeklyResetDateTime(Current);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 지정시각(at)을 기준으로 해당월의 리셋 시작과 종료를 계산한다.
|
||||
//=========================================================================================
|
||||
public static void getMonthlyResetDateTime(DateTime at, out DateTime begin, out DateTime end)
|
||||
{
|
||||
var this_month_zero_oclock = startOfMonth(at);
|
||||
var prev_month_zero_oclock = startOfMonth(this_month_zero_oclock.AddMonths(-1));
|
||||
var next_month_zero_oclock = startOfMonth(this_month_zero_oclock.AddMonths(1));
|
||||
|
||||
var this_month_reset_oclock = this_month_zero_oclock + DailyResetTimespan;
|
||||
var prev_month_reset_oclock = prev_month_zero_oclock + DailyResetTimespan;
|
||||
var next_month_reset_oclock = next_month_zero_oclock + DailyResetTimespan;
|
||||
|
||||
if (at < this_month_reset_oclock)
|
||||
{
|
||||
// at이 리셋 보다 이전이면 한달전이다
|
||||
begin = prev_month_reset_oclock;
|
||||
end = this_month_reset_oclock;
|
||||
}
|
||||
else
|
||||
{
|
||||
begin = this_month_reset_oclock;
|
||||
end = next_month_reset_oclock;
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// wrote_date 시각이 일일 리셋시각을 경과하여 만료 되었는지 검사한다
|
||||
// wrote_date : 발생시각, 주로 디비에 저장된 시각
|
||||
//=========================================================================================
|
||||
public static bool isDailyExpired(DateTime wroteDate)
|
||||
{
|
||||
return wroteDate < getTodayDailyResetDateTime();
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 기준일(at)부터 현재까지 경과 일수 (일일 리셋 시각 기준)
|
||||
//=========================================================================================
|
||||
public static Int32 getDayCountAt2Current(DateTime at)
|
||||
{
|
||||
return getDayCountFromTo(at, Current);
|
||||
}
|
||||
|
||||
public static Int32 getDayCountFromTo(DateTime from_at, DateTime to_at)
|
||||
{
|
||||
if (to_at <= from_at)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
var from_at_reset_time = getDailyResetDateTime(from_at);
|
||||
var to_at_reset_time = getDailyResetDateTime(to_at);
|
||||
return (Int32)(to_at_reset_time - from_at_reset_time).TotalDays;
|
||||
}
|
||||
|
||||
|
||||
//=========================================================================================
|
||||
// 타임서버와 동기화를 하거나 해제한다.
|
||||
// server_time 시각을 ping_msecond값으로 보정한후 셋팅한다.
|
||||
// server_time : 타임 서버에서 보내준 시각
|
||||
// ping_msecond : 타임 서버에 시각 요청후 응답까지 걸리 시간(밀리초)
|
||||
//=========================================================================================
|
||||
public static void setupFromTimeServer(string server_type, DateTime server_time, Int32 ping_msecond = 0)
|
||||
{
|
||||
if (server_time == ConstDateTimeValue.MinTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 타임 서버와 동기화를 한다.
|
||||
|
||||
if (false == m_time_syncs.TryGetValue(server_type, out var found))
|
||||
{
|
||||
found = new TimeSyncHelper(server_type);
|
||||
m_time_syncs.Add(server_type, found);
|
||||
}
|
||||
|
||||
found.setupFromTimeServer(server_time, new TimeSpan(0, 0, 0, 0, ping_msecond));
|
||||
}
|
||||
|
||||
|
||||
//=========================================================================================
|
||||
// 타임서버와 동기화된 시간을 계산한다.
|
||||
//=========================================================================================
|
||||
public static DateTime getCurrentByTimeServer(string server_type)
|
||||
{
|
||||
m_time_syncs.TryGetValue(server_type, out var found);
|
||||
return found != null ? found.getCurrentByTimeServer() : DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public static DateTime MinTime
|
||||
{
|
||||
get { return ConstDateTimeValue.MinTime; }
|
||||
}
|
||||
|
||||
public static DateTime MaxTime
|
||||
{
|
||||
get { return ConstDateTimeValue.MaxTime; }
|
||||
}
|
||||
|
||||
public static DateTime utcTick2DateTime(Int64 utcTick)
|
||||
{
|
||||
return new DateTime(utcTick, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
|
||||
//=========================================================================================
|
||||
// 초기값인지 검사
|
||||
//=========================================================================================
|
||||
public static bool isNullDate(this DateTime _this)
|
||||
{
|
||||
return _this == ConstDateTimeValue.MinDateTime || _this == ConstDateTimeValue.MinUnixTime || _this == ConstDateTimeValue.MinSqlDateTime;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// DataTime을 현재 시스템 TimeZone설정의 LocalTime으로 변환하여 반환한다.
|
||||
//=========================================================================================
|
||||
public static DateTime toLocalTimeBySystem(this DateTime _this)
|
||||
{
|
||||
return _this.ToLocalTime();
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// UTC DateTime을 TimeZoneType에 해당하는 LocalTime으로 변환하여 반환한다.
|
||||
//=========================================================================================
|
||||
public static DateTime toLocalTimeByTimeZoneType(this DateTime utcTime, TimeZoneType timeZoneType)
|
||||
{
|
||||
string time_zone_id;
|
||||
|
||||
if(TimeZoneType.Korea == timeZoneType)
|
||||
{
|
||||
// 운영체제(OS) 환경에 따라 시간대 식별자를 설정
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// Windows 환경
|
||||
time_zone_id = "Korea Standard Time";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
// 리눅스 또는 macOS 환경
|
||||
time_zone_id = "Asia/Seoul";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Not supported OS Type !!!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Not supported TimeZone !!! : timeZoneType:{timeZoneType}");
|
||||
}
|
||||
|
||||
// 설정한 시간대 정보를 가져온다.
|
||||
var time_zone = TimeZoneInfo.FindSystemTimeZoneById(time_zone_id);
|
||||
|
||||
// UTC 시간을 지정된 시간대로 변환 한다.
|
||||
return TimeZoneInfo.ConvertTimeFromUtc(utcTime, time_zone);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// DataTime을 UTC 로 변환하여 반환한다.
|
||||
//=========================================================================================
|
||||
public static DateTime toUtcTime(this DateTime _this)
|
||||
{
|
||||
return _this.ToUniversalTime();
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// DataTime을 DateTimeKind만 변환하여 반환한다. (시간값 유지)
|
||||
//=========================================================================================
|
||||
public static DateTime toSpecifyKindOnly(this DateTime _this, DateTimeKind toKind)
|
||||
{
|
||||
return DateTime.SpecifyKind(_this, toKind);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// DateTime을 타임존이 포함된 문자열로 변경한다
|
||||
//=========================================================================================
|
||||
public static string toString(this DateTime _this, string fmt = "")
|
||||
{
|
||||
if (fmt.isNullOrWhiteSpace())
|
||||
{
|
||||
fmt = "yyyy-MM-dd HH:mm";
|
||||
}
|
||||
return _this.ToString(fmt);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// DateTime을 타임존이 포함된 문자열로 변경한다
|
||||
//=========================================================================================
|
||||
public static DateTimeInfo toDateTimeInfo(this DateTime _this)
|
||||
{
|
||||
var created = new DateTimeInfo();
|
||||
created.Kind = (DateTimeType)_this.Kind;
|
||||
created.Utc100NanoSecond = _this.Ticks;
|
||||
created.DateTime = _this.toString("o");
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// DateTime을 Database(MSSQL)에서 사용하는 포맷으로 변환하여 반환한다.
|
||||
//=========================================================================================
|
||||
public static string toMsSqlDateTime(this DateTime _this)
|
||||
{
|
||||
return _this.toUtcTime().ToString("yyyy-MM-ddTHH:mm:ss.fff");
|
||||
}
|
||||
|
||||
public static bool isMinTime(this DateTime _this)
|
||||
{
|
||||
return _this <= ConstDateTimeValue.MinTime;
|
||||
}
|
||||
public static bool isMaxTime(this DateTime _this)
|
||||
{
|
||||
return _this == ConstDateTimeValue.MaxTime;
|
||||
}
|
||||
public static bool isValidTime(this DateTime _this)
|
||||
{
|
||||
return ConstDateTimeValue.MinTime < _this && _this < ConstDateTimeValue.MaxTime;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// System.DayOfWeek은 되도록 쓰지 않도록 한다 => DayOfWeekType 사용
|
||||
//=========================================================================================
|
||||
private static System.DayOfWeek toSystemDayOfWeek(this DayOfWeekType _this)
|
||||
{
|
||||
return (System.DayOfWeek)(_this - 1);
|
||||
}
|
||||
|
||||
private static DayOfWeekType toDayOfWeekType(this System.DayOfWeek _this)
|
||||
{
|
||||
return (DayOfWeekType)(_this + 1);
|
||||
}
|
||||
|
||||
public static string toStringWithUtcIso8601(this DateTime _this)
|
||||
{
|
||||
return _this.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); // UTC ISO 8601 형식으로 변환
|
||||
}
|
||||
|
||||
public static DateTime toUtcTime(this DateTimeInfo _this)
|
||||
{
|
||||
return new DateTime(_this.Utc100NanoSecond, (DateTimeKind)_this.Kind).toUtcTime();
|
||||
}
|
||||
|
||||
public static DateTime toLocalTime(this DateTimeInfo _this)
|
||||
{
|
||||
return new DateTime(_this.Utc100NanoSecond, (DateTimeKind)_this.Kind).toLocalTimeBySystem();
|
||||
}
|
||||
|
||||
public static DateTime toLocalTimeByTimeZoneType(this DateTimeInfo _this, TimeZoneType timeZoneType)
|
||||
{
|
||||
return new DateTime(_this.Utc100NanoSecond, (DateTimeKind)_this.Kind).toLocalTimeByTimeZoneType(timeZoneType);
|
||||
}
|
||||
|
||||
public static string toMsSqlDateTime(this DateTimeInfo _this)
|
||||
{
|
||||
return toUtcTime(_this).toMsSqlDateTime();
|
||||
}
|
||||
|
||||
public static string toString(this DateTimeInfo _this)
|
||||
{
|
||||
return toUtcTime(_this).toString("o");
|
||||
}
|
||||
|
||||
public static Int64 getUtc100NanoSecond(this DateTimeInfo _this)
|
||||
{
|
||||
return _this.Utc100NanoSecond;
|
||||
}
|
||||
|
||||
public static Int64 getUtcMiliSecond(this DateTimeInfo _this)
|
||||
{
|
||||
return _this.Utc100NanoSecond / 10000;
|
||||
}
|
||||
|
||||
public static DateTimeKind getKind(this DateTimeInfo _this)
|
||||
{
|
||||
return (DateTimeKind)_this.Kind;
|
||||
}
|
||||
|
||||
public static bool isMinTime(this DateTimeInfo _this)
|
||||
{
|
||||
return _this.toUtcTime() == ConstDateTimeValue.MinDateTime;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 현재시각부터 만료까지 얼마나 남았는지(초)
|
||||
// _timeToExpire : 만료시각
|
||||
//=========================================================================================
|
||||
public static double remainTimeSec(this DateTimeInfo _timeToExpire)
|
||||
{
|
||||
return _timeToExpire.remainTimeSpan().TotalSeconds;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 현재시각부터 만료까지 얼마나 남았는지(분)
|
||||
// _timeToExpire : 만료시각
|
||||
//=========================================================================================
|
||||
public static double remainTimeMinutes(this DateTimeInfo _timeToExpire)
|
||||
{
|
||||
return _timeToExpire.remainTimeSpan().TotalMinutes;
|
||||
}
|
||||
|
||||
public static TimeSpan remainTimeSpan(this DateTimeInfo _timeToExpire)
|
||||
{
|
||||
return _timeToExpire.toUtcTime() - DateTimeHelper.Current;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 현재 시간을 기준으로 targetTime 시간까지 남은 시간이 존재하는지 체크 한다. 남은 시간이 있는 경우 true 반환
|
||||
//=========================================================================================
|
||||
public static bool hasRemainingTime(DateTime targetTime)
|
||||
{
|
||||
return DateTimeHelper.Current < targetTime;
|
||||
}
|
||||
|
||||
public static void deepCopyFrom(this DateTimeInfo _target, DateTimeInfo _source)
|
||||
{
|
||||
_target.Kind = _source.Kind;
|
||||
_target.Utc100NanoSecond = _source.Utc100NanoSecond;
|
||||
_target.DateTime = _source.DateTime;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// Google Timestamp (UTC) 를 DateTime (UTC) 로 변환 한다.
|
||||
//=========================================================================================
|
||||
public static DateTime googleTimestampToDateTime(Timestamp googleTimestamp)
|
||||
{
|
||||
// Google Timestamp는 seconds와 nanos를 기준으로 UTC DateTime으로 변환됨
|
||||
var date_time = googleTimestamp.ToDateTime();
|
||||
|
||||
// UTC Kind 설정 확인 (이미 설정되어 있지만 안전을 위해 확인)
|
||||
return DateTime.SpecifyKind(date_time, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 문자열을 UTC DateTime 으로 변경
|
||||
// 문자열에 타임존이 포함되지 않으면, UTC(+0)으로 간주한다.
|
||||
//=========================================================================================
|
||||
|
||||
public static DateTime toUtcTime(this string _this)
|
||||
{
|
||||
return toLocalTime(_this).toUtcTime();
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 문자열을 local dateTime 으로 변경
|
||||
// 문자열에 타임존이 포함되지 않으면, UTC(+0)으로 간주한다.
|
||||
//=========================================================================================
|
||||
|
||||
public static DateTime toLocalTime(this string _this)
|
||||
{
|
||||
if (_this.Length == 0)
|
||||
{
|
||||
return ConstDateTimeValue.MinTime;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return DateTime.Parse(_this, null, System.Globalization.DateTimeStyles.AssumeUniversal);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return ConstDateTimeValue.MinTime;
|
||||
}
|
||||
}
|
||||
|
||||
public static DateTimeInfo toDateTimeInfo(this string _this)
|
||||
{
|
||||
return _this.toUtcTime().toDateTimeInfo();
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// UTC 기준 DateTime 을 Unix 시간 초기값과 시간차를 계산하여 밀리초(msec)로 변환하여 반환한다.
|
||||
//=========================================================================================
|
||||
public static double convertUnixDateTimeToMSec(System.DateTime utcDateTime)
|
||||
{
|
||||
System.TimeSpan ts = utcDateTime.Subtract(ConstDateTimeValue.MinUnixTime);
|
||||
return ts.TotalMilliseconds;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// UTC 기준 밀리초(msec)를 Unix 시간 초기값과 시간차를 계산하여 DateTime으로 변환하여 반환한다.
|
||||
//=========================================================================================
|
||||
public static DateTime convertMSecToUnixDateTime(double milliseconds)
|
||||
{
|
||||
System.DateTime dt = ConstDateTimeValue.MinUnixTime.AddMilliseconds(milliseconds);
|
||||
return dt;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// UTC 기준 DateTime 을 Unix 시간 초기값과 시간차 + 추가 경과 시간을 계산하여 밀리초(msec)로 변환하여 반환한다.
|
||||
//=========================================================================================
|
||||
public static double toUnixDateTimeSpanMSec(this System.DateTime utcDateTime, double milliseconds)
|
||||
{
|
||||
return convertUnixDateTimeToMSec(utcDateTime) + milliseconds;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 현재 시간을 기준으로 대상 시간(targetDateTime)까지 남은 시간을 분(minutes) 으로 반환 한다.
|
||||
//=========================================================================================
|
||||
public static double toRemainingTimeMin(System.DateTime targetDateTime)
|
||||
{
|
||||
double remain_minutes = 0;
|
||||
if (true == DateTimeHelper.hasRemainingTime(targetDateTime))
|
||||
{
|
||||
var remain_time = targetDateTime - DateTimeHelper.Current;
|
||||
remain_minutes = remain_time.TotalMinutes;
|
||||
}
|
||||
|
||||
return remain_minutes;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 현재 시간을 기준으로 대상 시간(targetDateTime)까지 남은 시간을 초(sec) 으로 반환 한다.
|
||||
//=========================================================================================
|
||||
public static double toRemainingTimeSec(System.DateTime targetDateTime)
|
||||
{
|
||||
double remain_sec = 0;
|
||||
if (true == DateTimeHelper.hasRemainingTime(targetDateTime))
|
||||
{
|
||||
var remain_time = targetDateTime - DateTimeHelper.Current;
|
||||
remain_sec = remain_time.TotalSeconds;
|
||||
}
|
||||
|
||||
return remain_sec;
|
||||
}
|
||||
}
|
||||
450
ServerCore/TypeHelper/EnumHelper.cs
Normal file
450
ServerCore/TypeHelper/EnumHelper.cs
Normal file
@@ -0,0 +1,450 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 열거체 관련 각종 지원 함수
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public static class EnumHelper
|
||||
{
|
||||
private static ConcurrentDictionary<string, Type> m_type_infos = new();
|
||||
|
||||
//=========================================================================================
|
||||
// 해당 열거체의 값이 Default 값인지 검사한다.
|
||||
//=========================================================================================
|
||||
public static bool isDefaultEnum<TEnum>(TEnum value)
|
||||
where TEnum : notnull
|
||||
{
|
||||
return EqualityComparer<TEnum>.Default.Equals(value, default);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 해당 열거체에 정의 되어 있는 열거체 정수인지 검사한다.
|
||||
//=========================================================================================
|
||||
public static bool isDefined<TEnum>(Int32 value)
|
||||
{
|
||||
foreach (Int32 each in Enum.GetValues(typeof(TEnum)))
|
||||
{
|
||||
if (each == value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 문자열을 열거체로 변환한다.
|
||||
// default(TEnum) 는 TEnum에 정의한 0값의 TEnum 타입을 반환 한다 !!!
|
||||
//=========================================================================================
|
||||
public static bool tryParse<TEnum>(string enumName, out TEnum? enumNumber)
|
||||
{
|
||||
enumNumber = default(TEnum);
|
||||
|
||||
try
|
||||
{
|
||||
if (0 <= enumName.IndexOf(' '))
|
||||
{
|
||||
Log.getLogger().error($"has white space !!! : {typeof(TEnum).Name}:{enumName}");
|
||||
}
|
||||
|
||||
enumNumber = (TEnum)Enum.Parse(typeof(TEnum), enumName, true);
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
Log.getLogger().error($"enum parse failed !!! : {typeof(TEnum).Name}:{enumName}, {e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 열거체에 속한 값들을 리턴한다.
|
||||
//=========================================================================================
|
||||
public static List<TEnum> getValues<TEnum>()
|
||||
{
|
||||
var enums = new List<TEnum>();
|
||||
foreach (var each in Enum.GetValues(typeof(TEnum)))
|
||||
{
|
||||
enums.Add((TEnum)each);
|
||||
}
|
||||
|
||||
return enums;
|
||||
}
|
||||
|
||||
public static List<TEnum> getValuesWithoutScope<TEnum>()
|
||||
{
|
||||
var enums = new List<TEnum>();
|
||||
foreach (var each in Enum.GetValues(typeof(TEnum)))
|
||||
{
|
||||
var upper_case = each.ToString()?.ToUpper();
|
||||
if (upper_case == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (upper_case.EndsWith("BEGIN") || upper_case.EndsWith("END") || upper_case.EndsWith("NONE"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
enums.Add((TEnum)each);
|
||||
}
|
||||
|
||||
return enums;
|
||||
}
|
||||
|
||||
public static List<TEnum> getValuesWithoutScopeAll<TEnum>()
|
||||
{
|
||||
var enums = new List<TEnum>();
|
||||
foreach (var each in Enum.GetValues(typeof(TEnum)))
|
||||
{
|
||||
var upper_case = each.ToString()?.ToUpper();
|
||||
if (upper_case == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (upper_case.EndsWith("BEGIN") || upper_case.EndsWith("END") || upper_case.EndsWith("NONE") || upper_case.EndsWith("MAX") || upper_case.EndsWith("ALL") )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
enums.Add((TEnum)each);
|
||||
}
|
||||
|
||||
return enums;
|
||||
}
|
||||
|
||||
public static List<TEnum> getValuesStartWith<TEnum>(List<string> toSkipWords)
|
||||
{
|
||||
var enums = new List<TEnum>();
|
||||
foreach (var each in Enum.GetValues(typeof(TEnum)))
|
||||
{
|
||||
var upper_case = each.ToString()?.ToUpper();
|
||||
if (upper_case == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var skip_word in toSkipWords)
|
||||
{
|
||||
if (upper_case.StartsWith(skip_word))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
enums.Add((TEnum)each);
|
||||
}
|
||||
|
||||
return enums;
|
||||
}
|
||||
|
||||
public static List<TEnum> getValuesEndWith<TEnum>(List<string> toSkipWords)
|
||||
{
|
||||
var enums = new List<TEnum>();
|
||||
foreach (var each in Enum.GetValues(typeof(TEnum)))
|
||||
{
|
||||
var upper_case = each.ToString()?.ToUpper();
|
||||
if (upper_case == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var skip_word in toSkipWords)
|
||||
{
|
||||
if (upper_case.EndsWith(skip_word))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
enums.Add((TEnum)each);
|
||||
}
|
||||
|
||||
return enums;
|
||||
}
|
||||
|
||||
// 열거체 내에 특정 범위
|
||||
public static List<TEnum> getValuesBeginEndBetweenWord<TEnum>(string word)
|
||||
{
|
||||
var enums = new List<TEnum>();
|
||||
|
||||
var begin_world = $"{word.ToUpper()}BEGIN";
|
||||
var end_world = $"{word.ToUpper()}END";
|
||||
|
||||
bool append_activate = false;
|
||||
foreach (var each in Enum.GetValues(typeof(TEnum)))
|
||||
{
|
||||
var upper_case = each.ToString()?.ToUpper();
|
||||
if (upper_case == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (upper_case == begin_world)
|
||||
{
|
||||
append_activate = true;
|
||||
continue;
|
||||
}
|
||||
if (upper_case == end_world)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (append_activate)
|
||||
{
|
||||
enums.Add((TEnum)each);
|
||||
}
|
||||
}
|
||||
return enums;
|
||||
}
|
||||
|
||||
|
||||
//=========================================================================================
|
||||
// 문자열 타입이 열거체이면 열거체에 속한 값들을 문자열로 반환한다.
|
||||
// return - true(열거체), false(열거체 아님)
|
||||
//=========================================================================================
|
||||
public static bool getValues(string typeName, out List<string> values)
|
||||
{
|
||||
values = new List<string>();
|
||||
|
||||
Type? type = getType(typeName);
|
||||
if (type == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type.IsEnum == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var each in Enum.GetValues(type))
|
||||
{
|
||||
var str = each.ToString();
|
||||
if (str == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
values.Add(str);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========================================================================================
|
||||
// 해당 타입이 열거체 인가?
|
||||
//=========================================================================================
|
||||
public static bool isEnumType(string typeName)
|
||||
{
|
||||
Type? type = getType(typeName);
|
||||
if (type == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return type.IsEnum;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 해당 타입이 숫자인가?
|
||||
//=========================================================================================
|
||||
public static bool isNumericType(string typeName)
|
||||
{
|
||||
Type? type = getType(typeName);
|
||||
if (type == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return TypeHelper.isNumericType(type);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 해당 타입이 문자열 인가?
|
||||
//=========================================================================================
|
||||
public static bool isStringType(string typeName)
|
||||
{
|
||||
Type? type = getType(typeName);
|
||||
if (type == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return TypeHelper.isString(type);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 문자열로 타입을 조회한다.
|
||||
//=========================================================================================
|
||||
public static Type? getType(string typeName)
|
||||
{
|
||||
Type? type = null;
|
||||
if (true == m_type_infos.TryGetValue(typeName, out type))
|
||||
{
|
||||
return type;
|
||||
|
||||
}
|
||||
|
||||
type = Assembly.GetExecutingAssembly().GetType(typeName);
|
||||
if (type == null)
|
||||
{
|
||||
foreach (var each in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
type = each.GetType(typeName);
|
||||
if (type != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (false == m_type_infos.ContainsKey(typeName))
|
||||
{
|
||||
m_type_infos.TryAdd(typeName, type);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
//=========================================================================================
|
||||
// 문자열을 enum으로 변환하여 반환한다.
|
||||
//=========================================================================================
|
||||
public static TEnum convertEnumValueStringToEnum<TEnum>(string valueOfEnum, TEnum enumDefaultType)
|
||||
{
|
||||
if (true == valueOfEnum.isNullOrWhiteSpace())
|
||||
{
|
||||
return enumDefaultType;
|
||||
}
|
||||
|
||||
if (Enum.IsDefined(typeof(TEnum), valueOfEnum))
|
||||
{
|
||||
return (TEnum)Enum.Parse(typeof(TEnum), valueOfEnum, true);
|
||||
}
|
||||
|
||||
return enumDefaultType;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// enum을 EnumType + . + EnumValue 병합하여 문자열로 반환한다.
|
||||
//=========================================================================================
|
||||
public static string convertEnumToEnumTypeAndValueString<TEnum>(this TEnum enumVale)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(enumVale, () => $"enumVale is null !!!");
|
||||
return $"{enumVale.getTypeName()}.{enumVale.ToString()}";
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// enum.Type.enumValue Or enumValue 문자열을 열거체로 변환후 반환한다. 실패시 설정한 기본값을 반환 한다.
|
||||
//=========================================================================================
|
||||
public static TEnum convertEnumTypeAndValueStringToEnum<TEnum>(this string enumTypeAndValue, TEnum enumDefaultType)
|
||||
{
|
||||
if (true == enumTypeAndValue.isNullOrWhiteSpace())
|
||||
{
|
||||
return enumDefaultType;
|
||||
}
|
||||
|
||||
var enum_type_name = string.Empty;
|
||||
var enum_value_name = string.Empty;
|
||||
|
||||
// "~Type.Value" -> ["Value1", "Value2" ... ]
|
||||
string[] parts = enumTypeAndValue.Split('.');
|
||||
if ( 2 > parts.Length )
|
||||
{
|
||||
enum_type_name = typeof(TEnum).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
enum_type_name = string.Join(".", parts, 0, parts.Length - 1); // 마지막 부분 제외한 타입 이름
|
||||
}
|
||||
enum_value_name = parts[parts.Length - 1]; // 마지막 부분이 enum 값
|
||||
|
||||
var enum_type = typeof(TEnum).Name;
|
||||
if(enum_type.ToLower() != enum_type_name.ToLower())
|
||||
{
|
||||
return enumDefaultType;
|
||||
}
|
||||
|
||||
if (false == Enum.IsDefined(typeof(TEnum), enum_value_name))
|
||||
{
|
||||
return enumDefaultType;
|
||||
}
|
||||
|
||||
return (TEnum)Enum.Parse(typeof(TEnum), enum_value_name, true);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// enum.Type.enumValue Or enumValue 문자열을 열거체로 변환후 반환한다. 실패시 설정한 기본값을 반환 한다.
|
||||
//=========================================================================================
|
||||
public static bool fillupEnumTypeAndValueStringToEnum<TEnum>( this string enumTypeAndValue
|
||||
, string enumDefaultType, out TEnum? fillupEnumDefaultType )
|
||||
{
|
||||
if (true == enumTypeAndValue.isNullOrWhiteSpace())
|
||||
{
|
||||
EnumHelper.tryParse<TEnum>(enumDefaultType, out fillupEnumDefaultType);
|
||||
return false;
|
||||
}
|
||||
|
||||
var enum_type_name = string.Empty;
|
||||
string enum_value_name = "";
|
||||
|
||||
// "~Type.Value" -> ["Value1", "Value2" ... ]
|
||||
string[] parts = enumTypeAndValue.Split('.');
|
||||
if (2 > parts.Length)
|
||||
{
|
||||
enum_type_name = typeof(TEnum).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
enum_type_name = string.Join(".", parts, 0, parts.Length - 1); // 마지막 부분 제외한 타입 이름
|
||||
}
|
||||
enum_value_name = parts[parts.Length - 1]; // 마지막 부분이 enum 값
|
||||
|
||||
var enum_type = typeof(TEnum).Name;
|
||||
if (enum_type.ToLower() != enum_type_name.ToLower())
|
||||
{
|
||||
EnumHelper.tryParse<TEnum>(enumDefaultType, out fillupEnumDefaultType);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false == Enum.IsDefined(typeof(TEnum), enum_value_name))
|
||||
{
|
||||
EnumHelper.tryParse<TEnum>(enumDefaultType, out fillupEnumDefaultType);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
fillupEnumDefaultType = (TEnum)Enum.Parse(typeof(TEnum), enum_value_name, true);
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
Log.getLogger().error($"enum parse failed !!! : {typeof(TEnum).Name}:{enum_value_name}, {e}");
|
||||
EnumHelper.tryParse<TEnum>(enumDefaultType, out fillupEnumDefaultType);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
73
ServerCore/TypeHelper/FileHelper.cs
Normal file
73
ServerCore/TypeHelper/FileHelper.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 파일 관련 각종 지원 함수
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
public static class FileHelper
|
||||
{
|
||||
public static bool getFilesInDirectory( string pathDir, string pattern
|
||||
, ref List<FileInfo> outFileInfos
|
||||
, out string errorMsg )
|
||||
{
|
||||
errorMsg = string.Empty;
|
||||
|
||||
FileInfo[] files;
|
||||
var dir_info = new DirectoryInfo(pathDir);
|
||||
|
||||
try
|
||||
{
|
||||
var file_pattern = string.Format("*{0}", pattern);
|
||||
files = dir_info.GetFiles(file_pattern, SearchOption.AllDirectories);
|
||||
}
|
||||
catch (UnauthorizedAccessException e)
|
||||
{
|
||||
errorMsg = e.ToString();
|
||||
return false;
|
||||
}
|
||||
catch (System.IO.DirectoryNotFoundException e)
|
||||
{
|
||||
errorMsg = e.ToString();
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var file_info in files)
|
||||
{
|
||||
outFileInfos.Add(file_info);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static List<string> getSubDirectoryAll(string pathDir, string pattern)
|
||||
{
|
||||
var root_dir = new DirectoryInfo(pathDir);
|
||||
|
||||
return Directory.GetDirectories(root_dir.FullName, pattern, SearchOption.AllDirectories).ToList();
|
||||
}
|
||||
|
||||
public static void deleteDirectory(string pathDir)
|
||||
{
|
||||
var target_dir = new DirectoryInfo(pathDir);
|
||||
|
||||
foreach (FileInfo file in target_dir.GetFiles())
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo sub_dir in target_dir.GetDirectories())
|
||||
{
|
||||
sub_dir.Delete(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
241
ServerCore/TypeHelper/JsonHelper.cs
Normal file
241
ServerCore/TypeHelper/JsonHelper.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices.JavaScript;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
public class JsonHelper
|
||||
{
|
||||
public static string getJsonPropertyName<T>(string propertyName)
|
||||
{
|
||||
Type type = typeof(T);
|
||||
|
||||
var property = type.GetProperty(propertyName);
|
||||
NullReferenceCheckHelper.throwIfNull(property, () => $"property is null !!!");
|
||||
|
||||
var json_property_attribute = property.GetCustomAttributes(typeof(Newtonsoft.Json.JsonPropertyAttribute), false)
|
||||
.FirstOrDefault() as Newtonsoft.Json.JsonPropertyAttribute;
|
||||
|
||||
return json_property_attribute?.PropertyName ?? propertyName;
|
||||
}
|
||||
|
||||
public static bool fillupKeyPaths(string jsonString, string toFindKey, out string fillupKeyPaths)
|
||||
{
|
||||
fillupKeyPaths = string.Empty;
|
||||
|
||||
var json_object = JObject.Parse(jsonString);
|
||||
|
||||
if (false == fillupJsonKeyPaths(json_object, toFindKey, out var fillup_key_paths))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fillupKeyPaths = string.Join('.', fillup_key_paths);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool fillupJsonKeyPaths(JObject jsonObject, string toFindKey, out List<string> fillUpKeyPaths)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(jsonObject, () => $"jsonObject is null !!!");
|
||||
|
||||
fillUpKeyPaths = new List<string>();
|
||||
|
||||
foreach (var property in jsonObject.Properties())
|
||||
{
|
||||
var json_key = property.Name;
|
||||
fillUpKeyPaths.Add(json_key);
|
||||
|
||||
var jtoken_value = property.Value;
|
||||
if (JTokenType.Array == jtoken_value.Type)
|
||||
{
|
||||
if(fillupJsonKeyPathsInJsonArray((JArray)jtoken_value, toFindKey, fillUpKeyPaths))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (JTokenType.Object == jtoken_value.Type)
|
||||
{
|
||||
if (fillupJsonKeyPathsInJsonObject((JObject)jtoken_value, toFindKey, fillUpKeyPaths))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fillupJsonKeyPathsInJsonProperty(property, toFindKey, fillUpKeyPaths))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(json_key == toFindKey)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var found_index = fillUpKeyPaths.LastIndexOf(json_key);
|
||||
if(0 <= found_index)
|
||||
{
|
||||
fillUpKeyPaths.RemoveRange(found_index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool fillupJsonKeyPathsInJsonArray(JArray jsonArray, string toFindKey, List<string> fillUpKeyPaths)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(jsonArray, () => $"jsonArray is null !!!");
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(fillUpKeyPaths, () => $"fillUpKeyPaths is null !!!");
|
||||
|
||||
foreach (JToken element in jsonArray)
|
||||
{
|
||||
if (JTokenType.Array == element.Type)
|
||||
{
|
||||
fillupJsonKeyPathsInJsonArray((JArray)element, toFindKey, fillUpKeyPaths);
|
||||
}
|
||||
else if (JTokenType.Object == element.Type)
|
||||
{
|
||||
fillupJsonKeyPathsInJsonObject((JObject)element, toFindKey, fillUpKeyPaths);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool fillupJsonKeyPathsInJsonObject(JObject jsonObject, string toFindKey, List<string> fillupKeyPaths)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(jsonObject, () => $"jsonObject is null !!!");
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(fillupKeyPaths, () => $"fillupKeyPaths is null !!!");
|
||||
|
||||
foreach (var property in jsonObject.Properties())
|
||||
{
|
||||
var json_key = property.Name;
|
||||
fillupKeyPaths.Add(json_key);
|
||||
|
||||
var jtoken_value = property.Value;
|
||||
|
||||
if (JTokenType.Array == jtoken_value.Type)
|
||||
{
|
||||
if(true == fillupJsonKeyPathsInJsonArray((JArray)jtoken_value, toFindKey, fillupKeyPaths))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (JTokenType.Object == jtoken_value.Type)
|
||||
{
|
||||
|
||||
if (true == fillupJsonKeyPathsInJsonObject((JObject)jtoken_value, toFindKey, fillupKeyPaths))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(true == fillupJsonKeyPathsInJsonProperty(property, toFindKey, fillupKeyPaths))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(json_key == toFindKey)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var found_index = fillupKeyPaths.LastIndexOf(json_key);
|
||||
if (0 <= found_index)
|
||||
{
|
||||
fillupKeyPaths.RemoveRange(found_index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static bool fillupJsonKeyPathsInJsonProperty(JProperty jsonProperty, string toFindKey, List<string> fillupKeyPaths)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(jsonProperty, () => $"jsonProperty is null !!!");
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(fillupKeyPaths, () => $"fillupKeyPaths is null !!!");
|
||||
|
||||
if (jsonProperty.Name == toFindKey)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// Json <=> String <=> Double 타입 변환기
|
||||
//
|
||||
// public class CustomClass
|
||||
// {
|
||||
// [JsonConverter(typeof(StringToDoubleEpsilonRoundConverter))] // double 값에 커스텀 컨버터 적용
|
||||
// [JsonProperty("cutom_value")]
|
||||
// public double CustomValue { get; set; }
|
||||
// }
|
||||
//
|
||||
// var custom_obj = new CustomObject();
|
||||
// custom_obj.CustomValue = 0.05;
|
||||
// var serialized_json = JsonConvert.SerializeObject( custom_obj );
|
||||
// NullReferenceCheckHelper.throwIfNull( serialized_json );
|
||||
//
|
||||
// // JSON 직렬화 출력
|
||||
// Console.WriteLine( serialized_json );
|
||||
// // => {"CustomValue":"0.05"}
|
||||
//
|
||||
// // JSON 역직렬화
|
||||
// var deserialized_obj = JsonConvert.DeserializeObject<CustomObject>( serialized_json );
|
||||
// NullReferenceCheckHelper.throwIfNull( deserialized_obj );
|
||||
//
|
||||
// var calculated_value = deserialized_obj.CustomValue * 3;
|
||||
// // 연산된 결과 출력
|
||||
// Console.WriteLine( calculated_value );
|
||||
// // => 0.15 출력
|
||||
//
|
||||
//=============================================================================================
|
||||
public class StringToDoubleEpsilonRoundConverter : JsonConverter<double>
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, double value, JsonSerializer serializer)
|
||||
{
|
||||
// double 값을 문자열로 변환해서 직렬화
|
||||
writer.WriteValue(value.ToString("F2")); // 고정 소숫점 2자리 변환하고, 소숫점 3자리를 반올림 !!!
|
||||
}
|
||||
|
||||
public override double ReadJson(JsonReader reader, System.Type objectType, double existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
// 문자열을 double로 변환하여 역직렬화
|
||||
if (JsonToken.String == reader.TokenType)
|
||||
{
|
||||
NullReferenceCheckHelper.throwIfNull(reader.Value, () => $"reader.Value is null !!! - {existingValue}");
|
||||
var double_as_string = (string)reader.Value;
|
||||
if (double.TryParse(double_as_string, out double result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else if(JsonToken.Float == reader.TokenType)
|
||||
{
|
||||
NullReferenceCheckHelper.throwIfNull(reader.Value, () => $"reader.Value is null !!! - {existingValue}");
|
||||
return ((double)reader.Value).withEpsilon(2);
|
||||
}
|
||||
|
||||
throw new JsonSerializationException($"Invalid double Type !!! : {reader.TokenType}");
|
||||
}
|
||||
}
|
||||
41
ServerCore/TypeHelper/ListHelper.cs
Normal file
41
ServerCore/TypeHelper/ListHelper.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// List 관련 각종 지원 함수
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public static class ListHelper
|
||||
{
|
||||
public static List<ELEM_TYPE> shuffleList<ELEM_TYPE>(List<ELEM_TYPE> src_list)
|
||||
{
|
||||
List<ELEM_TYPE> random_list = new List<ELEM_TYPE>();
|
||||
|
||||
Random random_device = new Random();
|
||||
Int32 random_index = 0;
|
||||
List<ELEM_TYPE> input_list = new List<ELEM_TYPE>(src_list);
|
||||
while (input_list.Count > 0)
|
||||
{
|
||||
random_index = random_device.Next(0, input_list.Count); //Choose a random object in the list
|
||||
random_list.Add(input_list[random_index]); //add it to the new, random list
|
||||
input_list.RemoveAt(random_index); //remove to avoid duplicates
|
||||
}
|
||||
return random_list; //return the new random list
|
||||
}
|
||||
|
||||
public static ELEM_TYPE popList<ELEM_TYPE>(this List<ELEM_TYPE> list, Int32 index = 0)
|
||||
{
|
||||
ELEM_TYPE return_value = list[index];
|
||||
list.RemoveAt(index);
|
||||
return return_value;
|
||||
}
|
||||
}
|
||||
311
ServerCore/TypeHelper/StringHelper.cs
Normal file
311
ServerCore/TypeHelper/StringHelper.cs
Normal file
@@ -0,0 +1,311 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
|
||||
using META_ID = System.UInt32;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// String 관련 각종 지원 함수
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public static class StringHelper
|
||||
{
|
||||
public static Stream toStream(this string stringValue, Encoding? encoding = null)
|
||||
{
|
||||
encoding ??= Encoding.UTF8;
|
||||
|
||||
var byte_array = encoding.GetBytes(stringValue);
|
||||
var memory_stream = new MemoryStream(byte_array);
|
||||
|
||||
return memory_stream;
|
||||
}
|
||||
|
||||
public static string toString(this Stream stream, Encoding? encoding = null)
|
||||
{
|
||||
encoding ??= Encoding.UTF8;
|
||||
|
||||
var reader = new StreamReader(stream, encoding);
|
||||
string text = reader.ReadToEnd();
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
public static bool isNullOrWhiteSpace(this string stringValue)
|
||||
{
|
||||
if(null == stringValue) return true;
|
||||
return string.IsNullOrEmpty(stringValue.Trim());
|
||||
}
|
||||
|
||||
public static bool hasDot(this string stringValue)
|
||||
{
|
||||
var splitted_value = stringValue.Split(".");
|
||||
if (2 <= splitted_value.Length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Int32 hierachyDepth(this string path)
|
||||
{
|
||||
return path.Split('.').Length;
|
||||
}
|
||||
|
||||
public static bool isChildPath(this string path, string childPath)
|
||||
{
|
||||
string path_key = path + ".";
|
||||
return childPath.StartsWith(path_key);
|
||||
}
|
||||
|
||||
public static string parentHierachyPath(this string path)
|
||||
{
|
||||
Int32 hier_index = path.LastIndexOf('.');
|
||||
if (hier_index < 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string parent_key = path.Substring(0, path.Length - hier_index);
|
||||
|
||||
return parent_key;
|
||||
}
|
||||
|
||||
public static bool isContainKoreanChars( this string stringValues
|
||||
, out MatchCollection matchCollection )
|
||||
{
|
||||
matchCollection = Regex.Matches(stringValues, $"[ㄱ-ㅎ가-힣]");
|
||||
|
||||
if( 1 <= matchCollection.Count )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool isContainInitialismKoreanChars( this string stringValues
|
||||
, out MatchCollection matchCollection)
|
||||
{
|
||||
matchCollection = Regex.Matches(stringValues, $"[ㄱ-ㅎ]");
|
||||
|
||||
if (1 <= matchCollection.Count)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool isContainEnglishChars( this string stringValues
|
||||
, out MatchCollection matchCollection )
|
||||
{
|
||||
matchCollection = Regex.Matches(stringValues, $"[a-zA-Z]");
|
||||
|
||||
if( 1 <= matchCollection.Count )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool isContainJapaneChars( this string stringValues
|
||||
, out MatchCollection matchCollection )
|
||||
{
|
||||
matchCollection = Regex.Matches(stringValues, @"[\p{IsHiragana}\p{IsKatakana}\p{IsCJKUnifiedIdeographs}]");
|
||||
|
||||
if (1 <= matchCollection.Count)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool isContainThaiChars( this string stringValues
|
||||
, out MatchCollection matchCollection )
|
||||
{
|
||||
matchCollection = Regex.Matches(stringValues, $"[\u0E01-\u0E3A\u0E3F-\u0E5B]");
|
||||
|
||||
if( 1 <= matchCollection.Count )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool isContainSpecialChars( this string stringValues
|
||||
, out MatchCollection matchCollection )
|
||||
{
|
||||
matchCollection = Regex.Matches(stringValues, $"[^a-zA-Z0-9ㄱ-ㅎ가-힣\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f]");
|
||||
|
||||
if( 1 <= matchCollection.Count )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool isContainCharsWithCount( this MatchCollection matchCollection
|
||||
, Int16 toCheckCharMinCount = 1, Int16 toCheckCharMaxCount = Int16.MaxValue )
|
||||
{
|
||||
if ( toCheckCharMinCount <= matchCollection.Count
|
||||
&& matchCollection.Count <= toCheckCharMaxCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<META_ID> toMetaIds(this string stringValue)
|
||||
{
|
||||
if (0 >= stringValue.Length)
|
||||
{
|
||||
return new List<META_ID> { };
|
||||
}
|
||||
stringValue = stringValue.Replace("(", "");
|
||||
stringValue = stringValue.Replace(")", "");
|
||||
|
||||
return stringValue.Split(',').Select(META_ID.Parse).ToList();
|
||||
}
|
||||
|
||||
public static bool toCamelCaseIfEnglish( this string stringValue, out string convertedString )
|
||||
{
|
||||
convertedString = string.Empty;
|
||||
|
||||
// 영문 글자가 있는지 체크 한다.
|
||||
if (false == stringValue.isContainEnglishChars(out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 모두 소문자로 변환 한다.
|
||||
var to_low_string = stringValue.ToLower();
|
||||
// 첫번째 글자를 대문자로 변환하고 나머지 글자를 연결 한다.
|
||||
convertedString = to_low_string[0].ToString().ToUpper() + to_low_string.Substring(1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool toBool( this string stringValue, out bool convertedBool )
|
||||
{
|
||||
convertedBool = false;
|
||||
|
||||
if (true == stringValue.isNullOrWhiteSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var lower_string = stringValue.ToLower();
|
||||
if(lower_string == "false")
|
||||
{
|
||||
convertedBool = false;
|
||||
return true;
|
||||
}
|
||||
else if (lower_string == "true")
|
||||
{
|
||||
convertedBool = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool toInt16(this string stringValue, out Int16 convertedInt16 )
|
||||
{
|
||||
convertedInt16 = 0;
|
||||
|
||||
if (true == stringValue.isNullOrWhiteSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedInt16 = TypeConvertHelper.castTo<Int16>(stringValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool toInt32(this string stringValue, out Int32 convertedInt32)
|
||||
{
|
||||
convertedInt32 = 0;
|
||||
|
||||
if (true == stringValue.isNullOrWhiteSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedInt32 = TypeConvertHelper.castTo<Int32>(stringValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool toInt64(this string stringValue, out Int64 convertedInt64)
|
||||
{
|
||||
convertedInt64 = 0;
|
||||
|
||||
if (true == stringValue.isNullOrWhiteSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedInt64 = TypeConvertHelper.castTo<Int64>(stringValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool toFloat(this string stringValue, out float convertedFloat)
|
||||
{
|
||||
convertedFloat = 0;
|
||||
|
||||
if (true == stringValue.isNullOrWhiteSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedFloat = TypeConvertHelper.castTo<float>(stringValue, CultureInfo.InvariantCulture);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool toDouble(this string stringValue, out double convertedDouble)
|
||||
{
|
||||
convertedDouble = 0;
|
||||
|
||||
if (true == stringValue.isNullOrWhiteSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedDouble = TypeConvertHelper.castTo<double>(stringValue, CultureInfo.InvariantCulture);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool toDecimal(this string stringValue, out decimal convertedDecimal)
|
||||
{
|
||||
convertedDecimal = 0;
|
||||
|
||||
if (true == stringValue.isNullOrWhiteSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedDecimal = TypeConvertHelper.castTo<decimal>(stringValue, CultureInfo.InvariantCulture);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
28
ServerCore/TypeHelper/TemplateReplacer.cs
Normal file
28
ServerCore/TypeHelper/TemplateReplacer.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
|
||||
public static class TemplateReplacer
|
||||
{
|
||||
public static string replace(string template, Dictionary<string, string> values)
|
||||
{
|
||||
if (template.isNullOrWhiteSpace())
|
||||
{
|
||||
return template;
|
||||
}
|
||||
|
||||
foreach (var pair in values)
|
||||
{
|
||||
template = template.Replace($"{{{pair.Key}}}", pair.Value);
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
}
|
||||
180
ServerCore/TypeHelper/TypeConvertHelper.cs
Normal file
180
ServerCore/TypeHelper/TypeConvertHelper.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using StackExchange.Redis;
|
||||
|
||||
|
||||
using Enum = System.Enum;
|
||||
using Type = System.Type;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
//=============================================================================================
|
||||
// 타입 변환 관련 각종 지원 함수
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
public static class TypeConvertHelper
|
||||
{
|
||||
//=========================================================================================
|
||||
// float 타입 데이터를 Int32 타입 데이터로 변환한다.
|
||||
//=========================================================================================
|
||||
public static Int32 toInt32(this float from)
|
||||
{
|
||||
var to_bytes = System.BitConverter.GetBytes(from);
|
||||
return System.BitConverter.ToInt32(to_bytes, 0);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// Int32 타입 데이터를 float 타입 데이터로 변환한다.
|
||||
//=========================================================================================
|
||||
public static float toFloat(this Int32 from)
|
||||
{
|
||||
var to_bytes = System.BitConverter.GetBytes(from);
|
||||
return System.BitConverter.ToSingle(to_bytes, 0);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// float 타입 데이터를 double 타입으로 변환한다.
|
||||
// Epsilon 보정 목적으로 소수점 몇 자리까지 반올림할 것인지 설정 한다.
|
||||
// toDoubleWithDecimal() 함수 보다 Release 빌드 기준 5배이상 빠르다 !!! - kangms
|
||||
//=========================================================================================
|
||||
public static double toDoubleWithEpsilon(this float from, int decimalPlaces = 9)
|
||||
{
|
||||
var to_double_value = (double)from;
|
||||
return to_double_value.withEpsilon(decimalPlaces);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// float 타입 데이터를 double 타입으로 변환한다. decimal 자료형을 중간 버퍼로 활용하여 double 로 변환 한다.
|
||||
//=========================================================================================
|
||||
public static double toDoubleWithDecimal(this float from)
|
||||
{
|
||||
return (double)(decimal)from;
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// double을 Epsilon 보정 목적으로 소수점 몇 자리까지 반올림할 것인지 설정 한다.
|
||||
//=========================================================================================
|
||||
public static double withEpsilon(this double from, int decimalPlaces = 9)
|
||||
{
|
||||
return MathHelper.round(from, decimalPlaces);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// object 타입을 T 제네릭 타입으로 변환 한다.
|
||||
//=========================================================================================
|
||||
public static T castTo<T>(this Object objValue, IFormatProvider? culture_info = null)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(objValue, () => $"objValue is null !!!");
|
||||
|
||||
return (T)Convert.ChangeType(objValue, typeof(T), culture_info);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// object 타입을 HashEntry 배열로 전환
|
||||
//=========================================================================================
|
||||
public static HashEntry[] toHashEntries(object obj)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(obj, () => $"obj is null !!!");
|
||||
|
||||
var hash_entries = new List<HashEntry>();
|
||||
|
||||
var objType = obj.GetType();
|
||||
var properties = objType.GetProperties();
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var property_value = property.GetValue(obj);
|
||||
if (property_value == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string hash_value;
|
||||
if (property_value is IEnumerable<object>)
|
||||
{
|
||||
hash_value = JsonConvert.SerializeObject(property_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var string_value = property_value.ToString();
|
||||
NullReferenceCheckHelper.throwIfNull(string_value, () => $"string_value is null !!!");
|
||||
|
||||
hash_value = string_value;
|
||||
}
|
||||
|
||||
hash_entries.Add(new HashEntry(property.Name, hash_value));
|
||||
}
|
||||
|
||||
return hash_entries.ToArray();
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// HashEntry 배열을 Class 로 전환
|
||||
//=========================================================================================
|
||||
public static T toClassFromHashEntries<T>(HashEntry[] hashEntries)
|
||||
{
|
||||
ArgumentNullReferenceCheckHelper.throwIfNull(hashEntries, () => $"hashEntries is null !!!");
|
||||
|
||||
PropertyInfo[] properties = typeof(T).GetProperties();
|
||||
|
||||
var obj = Activator.CreateInstance(typeof(T));
|
||||
NullReferenceCheckHelper.throwIfNull(obj, () => $"obj is null !!!");
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
HashEntry entry = hashEntries.FirstOrDefault(g => g.Name.ToString().Equals(property.Name));
|
||||
if (entry.Equals(new HashEntry())) continue;
|
||||
|
||||
object? propValue;
|
||||
if (property.PropertyType.IsEnum)
|
||||
{
|
||||
propValue = Enum.Parse(property.PropertyType, entry.Value.ToString(), true);
|
||||
}
|
||||
else if (property.PropertyType == typeof(Timestamp))
|
||||
{
|
||||
var value = entry.Value.ToString().Replace("\"", "");
|
||||
propValue = Timestamp.FromDateTime(value.toUtcTime());
|
||||
}
|
||||
else
|
||||
{
|
||||
propValue = Convert.ChangeType(entry.Value.ToString(), property.PropertyType);
|
||||
}
|
||||
|
||||
property.SetValue(obj, propValue);
|
||||
}
|
||||
|
||||
return (T)obj;
|
||||
}
|
||||
|
||||
public static string toByteStringFromString(this string input)
|
||||
{
|
||||
byte[] bytes = Encoding.Default.GetBytes(input);
|
||||
var byte_string = BitConverter.ToString(bytes).Replace("-", "");
|
||||
|
||||
return byte_string;
|
||||
}
|
||||
|
||||
public static string toStringFromByteString(this string input)
|
||||
{
|
||||
byte[] bytes = new byte[input.Length / 2];
|
||||
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes[i] = Convert.ToByte(input.Substring(i * 2, 2), 16);
|
||||
}
|
||||
|
||||
return Encoding.Default.GetString(bytes);
|
||||
}
|
||||
}
|
||||
308
ServerCore/TypeHelper/TypeHelper.cs
Normal file
308
ServerCore/TypeHelper/TypeHelper.cs
Normal file
@@ -0,0 +1,308 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections;
|
||||
|
||||
|
||||
|
||||
using Amazon.DynamoDBv2.DocumentModel;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
|
||||
using Type = System.Type;
|
||||
|
||||
|
||||
namespace ServerCore;
|
||||
|
||||
|
||||
//=============================================================================================
|
||||
// Type 관련 각종 지원 함수
|
||||
//
|
||||
// author : kangms
|
||||
//
|
||||
//=============================================================================================
|
||||
|
||||
public static class TypeHelper
|
||||
{
|
||||
//=========================================================================================
|
||||
// 클래스명 반환
|
||||
//=========================================================================================
|
||||
public static string getTypeName(this object obj)
|
||||
{
|
||||
Type type = obj.GetType();
|
||||
|
||||
// 제네릭 타입 여부 확인 하기
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
// 제네릭 타입명에서 (`) 제거 한다.
|
||||
string genericTypeName = type.GetGenericTypeDefinition().Name;
|
||||
genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`'));
|
||||
|
||||
// 제네릭 인자들 가져온다.
|
||||
Type[] genericArguments = type.GetGenericArguments();
|
||||
string genericArgs = string.Join(", ", genericArguments.Select(t => t.Name));
|
||||
|
||||
// 제네릭 인수 포함된 타입명을 반환 한다.
|
||||
return $"{genericTypeName}<{genericArgs}>";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 제네릭 아닌 경우, 단순히 타입명을 반환 한다.
|
||||
return type.Name;
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// Numeric 계열 관련 체크
|
||||
//=============================================================================================
|
||||
public static bool isNumericType(this Type _this)
|
||||
{
|
||||
switch (Type.GetTypeCode(_this))
|
||||
{
|
||||
case TypeCode.Boolean:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.Int32:
|
||||
case TypeCode.UInt32:
|
||||
case TypeCode.Int64:
|
||||
case TypeCode.UInt64:
|
||||
case TypeCode.Single:
|
||||
case TypeCode.Double:
|
||||
case TypeCode.Decimal:
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 정수형 계열 관련 체크
|
||||
//=============================================================================================
|
||||
public static bool isIntegerType(this Type type)
|
||||
{
|
||||
return type.isShort() || type.isInt16() || type.isInt() || type.isInt32() || type.isInt64();
|
||||
}
|
||||
public static bool isShort(this Type type)
|
||||
{
|
||||
return type == typeof(short);
|
||||
}
|
||||
public static bool isInt16(this Type type)
|
||||
{
|
||||
return type == typeof(Int16);
|
||||
}
|
||||
public static bool isInt(this Type type)
|
||||
{
|
||||
return type == typeof(int);
|
||||
}
|
||||
public static bool isInt32(this Type type)
|
||||
{
|
||||
return type == typeof(Int32);
|
||||
}
|
||||
public static bool isInt64(this Type type)
|
||||
{
|
||||
return type == typeof(Int64);
|
||||
}
|
||||
public static bool isLong(this Type type)
|
||||
{
|
||||
return type == typeof(long);
|
||||
}
|
||||
public static bool isUnsignedIntegerType(this Type type)
|
||||
{
|
||||
return type.isUShort() || type.isUInt16() || type.isUInt() || type.isUInt32() || type.isUInt64();
|
||||
}
|
||||
public static bool isUShort(this Type type)
|
||||
{
|
||||
return type == typeof(ushort);
|
||||
}
|
||||
public static bool isUInt16(this Type type)
|
||||
{
|
||||
return type == typeof(UInt16);
|
||||
}
|
||||
public static bool isUInt(this Type type)
|
||||
{
|
||||
return type == typeof(uint);
|
||||
}
|
||||
public static bool isUInt32(this Type type)
|
||||
{
|
||||
return type == typeof(UInt32);
|
||||
}
|
||||
public static bool isUInt64(this Type type)
|
||||
{
|
||||
return type == typeof(UInt64);
|
||||
}
|
||||
public static bool isULong(this Type type)
|
||||
{
|
||||
return type == typeof(ulong);
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// Float 계열 관련 체크
|
||||
//=============================================================================================
|
||||
public static bool isFloatType(this Type type)
|
||||
{
|
||||
return type.isFloat() || type.isDouble() || type.isDecimal();
|
||||
}
|
||||
public static bool isDouble(this Type type)
|
||||
{
|
||||
return type == typeof(double);
|
||||
}
|
||||
public static bool isFloat(this Type type)
|
||||
{
|
||||
return type == typeof(float);
|
||||
}
|
||||
public static bool isDecimal(this Type type)
|
||||
{
|
||||
return type == typeof(decimal);
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 특수형 관련 체크
|
||||
//=============================================================================================
|
||||
public static bool isString(this Type type)
|
||||
{
|
||||
return type == typeof(string);
|
||||
}
|
||||
public static bool isBool(this Type type)
|
||||
{
|
||||
return type == typeof(bool);
|
||||
}
|
||||
public static bool isEnum(this Type type)
|
||||
{
|
||||
return true == type.IsEnum;
|
||||
}
|
||||
|
||||
public static bool isTimestamp(this Type type)
|
||||
{
|
||||
return type == typeof(Timestamp);
|
||||
}
|
||||
|
||||
public static bool isDateTime(this Type type)
|
||||
{
|
||||
return type == typeof(DateTime);
|
||||
}
|
||||
|
||||
public static bool isTimeSpan(this Type type)
|
||||
{
|
||||
return type == typeof(TimeSpan);
|
||||
}
|
||||
|
||||
public static bool isStruct(this Type type)
|
||||
{
|
||||
// 값 타입(struct:구조체) 등을 체크 !!!
|
||||
return true == type.IsValueType;
|
||||
}
|
||||
public static bool isByteArray(this Type type)
|
||||
{
|
||||
return type == typeof(byte[]);
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 원시 타입 관련 체크
|
||||
// Type.IsPrimitive == true 에 해당하는 타입들...
|
||||
// : Boolean(bool), Byte(byte), SByte(sbyte),
|
||||
// Int16(short), UInt16(ushort), Int32(int), UInt32(uint), Int64(long), UInt64(ulong),
|
||||
// Char(char), Double(double), Single(float)
|
||||
//=============================================================================================
|
||||
public static bool isPrimitiveType(this Type type)
|
||||
{
|
||||
return type.IsPrimitive;
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// NumericSign 관련 체크
|
||||
//=============================================================================================
|
||||
|
||||
public enum NumericSignType
|
||||
{
|
||||
Zero = 0,
|
||||
Positive = 1,
|
||||
Negative = 2
|
||||
}
|
||||
|
||||
public static NumericSignType checkNumericSignType<TNumeric>(TNumeric value)
|
||||
where TNumeric : IComparable<TNumeric>
|
||||
{
|
||||
if (value.CompareTo(default(TNumeric)) > 0)
|
||||
{
|
||||
return NumericSignType.Positive;
|
||||
}
|
||||
else if (value.CompareTo(default(TNumeric)) < 0)
|
||||
{
|
||||
return NumericSignType.Negative;
|
||||
}
|
||||
|
||||
return NumericSignType.Zero;
|
||||
}
|
||||
|
||||
//=============================================================================================
|
||||
// 객체형 관련 체크
|
||||
//=============================================================================================
|
||||
public static bool isNonClassType(this Type type)
|
||||
{
|
||||
// 기본형 타입, 열거형 타입, 값 타입(struct:구조체) 등을 체크 !!!
|
||||
return type.IsPrimitive || type.IsEnum || type.IsValueType;
|
||||
}
|
||||
|
||||
public static bool isNormalClass(this Type type)
|
||||
{
|
||||
return type.IsClass
|
||||
&& (false == type.IsGenericType) && (false == type.isString())
|
||||
&& (false == type.isNonClassType()) && (false == type.isDecimal());
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 해당 객체는 동일한 클래스 타입인가 ?
|
||||
//=========================================================================================
|
||||
public static bool isClassType<TCheckToType>(this object _this)
|
||||
where TCheckToType : class
|
||||
{
|
||||
if (null == (_this as TCheckToType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool isList(this Type type)
|
||||
{
|
||||
return typeof(IList).IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
public static bool isDictionary(this Type type)
|
||||
{
|
||||
return typeof(IDictionary).IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
//=========================================================================================
|
||||
// 기타
|
||||
//=========================================================================================
|
||||
|
||||
public static bool isTrue(this bool value)
|
||||
{
|
||||
if(true == value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool isFalse(this bool value)
|
||||
{
|
||||
if (false == value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
252
ServerCore/z.Backup/ServerLog.cs
Normal file
252
ServerCore/z.Backup/ServerLog.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NLog.AWS.Logger;
|
||||
using NLog.Targets.Wrappers;
|
||||
using NLog.Config;
|
||||
using NLog.Layouts;
|
||||
using Amazon.Runtime;
|
||||
|
||||
|
||||
|
||||
namespace ServerCore
|
||||
{
|
||||
|
||||
//public class ServerLog
|
||||
//{
|
||||
// //static NLog.Logger log = LogManager.GetCurrentClassLogger();
|
||||
// static NLog.Logger log = LogManager.GetLogger("Developer");
|
||||
|
||||
// public ServerLog()
|
||||
// {
|
||||
// }
|
||||
// private static string _serverType = string.Empty;
|
||||
// private static string _ipPort = string.Empty;
|
||||
|
||||
// public static void Init( string serverType, string ip, int port
|
||||
// , string? logGroup = null
|
||||
// , string? logNamePattern = null
|
||||
// , string? logLevel = null
|
||||
// , string? awsRegion = null
|
||||
// , string? awsAccessKey = null
|
||||
// , string? awsSecretKey = null
|
||||
// , Layout? layout = null)
|
||||
// {
|
||||
// _serverType = serverType;
|
||||
// //StringBuilder stringBuilder= new StringBuilder();
|
||||
// //stringBuilder.AppendFormat();
|
||||
// _ipPort = $"{ip}:{port}";
|
||||
// //LogManager.Configuration.Variables["LogPath"] = "./";
|
||||
// //LogManager.Configuration.Variables["LogFileName"] = "";
|
||||
|
||||
|
||||
// if (logGroup == string.Empty || logGroup is null ||
|
||||
// logNamePattern == string.Empty || logNamePattern is null ||
|
||||
// logLevel == string.Empty || logLevel is null ||
|
||||
// awsRegion == string.Empty || awsRegion is null ||
|
||||
// awsAccessKey == string.Empty || awsAccessKey is null ||
|
||||
// awsSecretKey == string.Empty || awsSecretKey is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// try
|
||||
// {
|
||||
// CloudWatchLogInit(logGroup, logNamePattern, logLevel, awsRegion, awsAccessKey, awsSecretKey, layout);
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// Log.getLogger().error($"CloudWatchLogInit Error!!! {logGroup}, {logNamePattern}, {logLevel}, {awsRegion}, {awsAccessKey}, {awsSecretKey}");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// }
|
||||
// public static void End()
|
||||
// {
|
||||
// LogManager.Shutdown();
|
||||
// }
|
||||
|
||||
// private static void CloudWatchLogInit( string? logGroup, string? namePattern, string? logLevel
|
||||
// , string? awsRegion, string? awsAccessKey, string? awsSecretKey
|
||||
// , Layout? layout)
|
||||
// {
|
||||
// var current_config = LogManager.Configuration;
|
||||
// var aws_target = new AWSTarget()
|
||||
// {
|
||||
// LogGroup = logGroup,
|
||||
// Region = awsRegion,
|
||||
// Credentials = new BasicAWSCredentials(awsAccessKey, awsSecretKey),
|
||||
// Layout = layout
|
||||
// };
|
||||
|
||||
// var newRule = new LoggingRule(namePattern, LogLevel.FromString(logLevel), aws_target);
|
||||
// current_config.LoggingRules.Add(newRule);
|
||||
|
||||
// LogManager.ReconfigExistingLoggers();
|
||||
// }
|
||||
|
||||
// public static string GenerateRandomUniqueKey()
|
||||
// {
|
||||
// int length = 16;
|
||||
// const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
// byte[] randomBytes = new byte[length];
|
||||
// string nowTime = DateTimeOffset.Now.ToUnixTimeMilliseconds().ToString();
|
||||
|
||||
// using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
|
||||
// {
|
||||
// rng.GetBytes(randomBytes);
|
||||
// }
|
||||
|
||||
// StringBuilder result = new StringBuilder(length + nowTime.Length);
|
||||
|
||||
// foreach (byte b in randomBytes)
|
||||
// {
|
||||
// result.Append(chars[b % chars.Length]);
|
||||
// }
|
||||
// result.Append(nowTime);
|
||||
|
||||
// return result.ToString();
|
||||
// }
|
||||
|
||||
// public static void Trace(string message,
|
||||
// string player = "",
|
||||
// [CallerMemberName] string memberName = "",
|
||||
// [CallerFilePath] string sourceFilePath = "",
|
||||
// [CallerLineNumber] int sourceLineNumber = 0)
|
||||
// {
|
||||
// LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Trace, null, message);
|
||||
// logEventInfo.Properties["player"] = player;
|
||||
// logEventInfo.Properties["memberName"] = memberName;
|
||||
// logEventInfo.Properties["filePath"] = sourceFilePath;
|
||||
// logEventInfo.Properties["lineNumber"] = sourceLineNumber;
|
||||
// logEventInfo.Properties["server"] = _serverType;
|
||||
// logEventInfo.Properties["ipport"] = _ipPort;
|
||||
// log.Trace(logEventInfo);
|
||||
// }
|
||||
|
||||
// public static void Debug(string message,
|
||||
// string player = "",
|
||||
// [CallerMemberName] string memberName = "",
|
||||
// [CallerFilePath] string sourceFilePath = "",
|
||||
// [CallerLineNumber] int sourceLineNumber = 0)
|
||||
// {
|
||||
// LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Debug, null, message);
|
||||
// logEventInfo.Properties["player"] = player;
|
||||
// logEventInfo.Properties["memberName"] = memberName;
|
||||
// logEventInfo.Properties["filePath"] = sourceFilePath;
|
||||
// logEventInfo.Properties["lineNumber"] = sourceLineNumber;
|
||||
// logEventInfo.Properties["server"] = _serverType;
|
||||
// logEventInfo.Properties["ipport"] = _ipPort;
|
||||
// log.Debug(logEventInfo);
|
||||
// }
|
||||
|
||||
// public static void Info(string message,
|
||||
// string player = "",
|
||||
// [CallerMemberName] string memberName = "",
|
||||
// [CallerFilePath] string sourceFilePath = "",
|
||||
// [CallerLineNumber] int sourceLineNumber = 0)
|
||||
// {
|
||||
// LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Info, null, message);
|
||||
// logEventInfo.Properties["player"] = player;
|
||||
// logEventInfo.Properties["memberName"] = memberName;
|
||||
// logEventInfo.Properties["filePath"] = sourceFilePath;
|
||||
// logEventInfo.Properties["lineNumber"] = sourceLineNumber;
|
||||
// logEventInfo.Properties["server"] = _serverType;
|
||||
// logEventInfo.Properties["ipport"] = _ipPort;
|
||||
// log.Info(logEventInfo);
|
||||
// }
|
||||
|
||||
// /*
|
||||
// static public void Info(string message, IReadOnlyList<KeyValuePair<object, object>> eventProperties,
|
||||
// string player = "",
|
||||
// [CallerMemberName] string memberName = "",
|
||||
// [CallerFilePath] string sourceFilePath = "",
|
||||
// [CallerLineNumber] int sourceLineNumber = 0)
|
||||
// {
|
||||
// var jsonProperties = new Dictionary<string, object>();
|
||||
// foreach (var property in eventProperties)
|
||||
// {
|
||||
// var key = property.Key.ToString();
|
||||
// if (key == null) continue;
|
||||
// jsonProperties[key] = property.Value;
|
||||
// }
|
||||
// string jsonEventProperties = JsonConvert.SerializeObject(jsonProperties);
|
||||
|
||||
// LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Info, null, message);
|
||||
// logEventInfo.Properties["memberName"] = memberName;
|
||||
// logEventInfo.Properties["player"] = player;
|
||||
// logEventInfo.Properties["properties"] = jsonEventProperties;
|
||||
// logEventInfo.Properties["filePath"] = sourceFilePath;
|
||||
// logEventInfo.Properties["lineNumber"] = sourceLineNumber;
|
||||
// logEventInfo.Properties["server"] = _serverType;
|
||||
// logEventInfo.Properties["ipport"] = _ipPort;
|
||||
|
||||
// log.Info(logEventInfo);
|
||||
// }*/
|
||||
|
||||
|
||||
// public static void Warn(string message,
|
||||
// string player = "",
|
||||
// [CallerMemberName] string memberName = "",
|
||||
// [CallerFilePath] string sourceFilePath = "",
|
||||
// [CallerLineNumber] int sourceLineNumber = 0)
|
||||
// {
|
||||
// LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Warn, null, message);
|
||||
// logEventInfo.Properties["player"] = player;
|
||||
// logEventInfo.Properties["filePath"] = sourceFilePath;
|
||||
// logEventInfo.Properties["lineNumber"] = sourceLineNumber;
|
||||
// logEventInfo.Properties["memberName"] = memberName;
|
||||
// logEventInfo.Properties["server"] = _serverType;
|
||||
// logEventInfo.Properties["ipport"] = _ipPort;
|
||||
// log.Warn(logEventInfo);
|
||||
// }
|
||||
|
||||
// public static void Error(string message,
|
||||
// string player = "",
|
||||
// [CallerMemberName] string memberName = "",
|
||||
// [CallerFilePath] string sourceFilePath = "",
|
||||
// [CallerLineNumber] int sourceLineNumber = 0)
|
||||
// {
|
||||
// LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Error, null, message);
|
||||
// logEventInfo.Properties["player"] = player;
|
||||
// logEventInfo.Properties["filePath"] = sourceFilePath;
|
||||
// logEventInfo.Properties["lineNumber"] = sourceLineNumber;
|
||||
// logEventInfo.Properties["memberName"] = memberName;
|
||||
// logEventInfo.Properties["server"] = _serverType;
|
||||
// logEventInfo.Properties["ipport"] = _ipPort;
|
||||
// log.Error(logEventInfo);
|
||||
// }
|
||||
|
||||
// public static void Fatal(string message,
|
||||
// string player = "",
|
||||
// [CallerMemberName] string memberName = "",
|
||||
// [CallerFilePath] string sourceFilePath = "",
|
||||
// [CallerLineNumber] int sourceLineNumber = 0)
|
||||
// {
|
||||
// LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Fatal, null, message);
|
||||
// logEventInfo.Properties["player"] = player;
|
||||
// logEventInfo.Properties["filePath"] = sourceFilePath;
|
||||
// logEventInfo.Properties["lineNumber"] = sourceLineNumber;
|
||||
// logEventInfo.Properties["memberName"] = memberName;
|
||||
// logEventInfo.Properties["server"] = _serverType;
|
||||
// logEventInfo.Properties["ipport"] = _ipPort;
|
||||
// log.Fatal(logEventInfo);
|
||||
// }
|
||||
|
||||
// public static void Sequence(string sender, string receiver, string message, string errDesc = "")
|
||||
// {
|
||||
// NLog.Logger sequence_logger = LogManager.GetLogger("SequenceLogger");
|
||||
|
||||
// LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Debug, "SequenceLogger", message);
|
||||
// logEventInfo.Properties["sender"] = sender;
|
||||
// logEventInfo.Properties["receiver"] = receiver;
|
||||
// logEventInfo.Properties["errordesc"] = (errDesc == string.Empty || errDesc.Contains("Success") == true ? $"{errDesc}" : $"ERR-{errDesc}");
|
||||
// sequence_logger.Debug(logEventInfo);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
Reference in New Issue
Block a user