Aetheria/Economy.Server/Program.cs
hyperion51 f8a6582154 Server Initialization and Serverside Galaxy/Zone Delivery
Made the master server connect to the database and now it's listening for and responding correctly to GalaxyRequestMessages and ZoneRequestMessages, generating the zone contents if they do not yet exist.
2020-03-27 01:53:03 +00:00

241 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using MessagePack;
using MessagePack.Resolvers;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using RethinkDb.Driver;
using RethinkDb.Driver.Net;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace ChatApp.Server
{
class Program
{
private static readonly RethinkDB R = RethinkDB.R;
private static ILogger _logger;
static async Task Main(string[] args)
{
var serilogger = new LoggerConfiguration()
.WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
var loggerFactory = LoggerFactory.Create(builder =>
{
builder
// .AddFilter("Microsoft", LogLevel.Warning)
// .AddFilter("System", LogLevel.Warning)
// .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
.AddSerilog(serilogger)
.AddConsole();
});
_logger = loggerFactory.CreateLogger<Program>();
// Register extensions to Json.NET Serialization
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter>
{
new MathJsonConverter(),
Converter.DateTimeConverter,
Converter.BinaryConverter,
Converter.GroupingConverter,
Converter.PocoExprConverter
}
};
Converter.Serializer.Converters.Add(new MathJsonConverter());
// Register extensions to MessagePack Serialization
var resolver = CompositeResolver.Create(
MathResolver.Instance,
NativeGuidResolver.Instance,
StandardResolver.Instance
);
var options = MessagePackSerializerOptions.Standard.WithResolver(resolver);
MessagePackSerializer.DefaultOptions = options;
var connection = R.Connection().Hostname("asgard.gamecult.games")
.Port(RethinkDBConstants.DefaultPort).Timeout(60).Connect();
var cache = new DatabaseCache();
// When entries are changed locally, push the changes to RethinkDB
cache.OnDataUpdateLocal += async entry =>
{
var table = entry.GetType().GetCustomAttribute<RethinkTableAttribute>()?.TableName ?? "Other";
var result = await R.Db("Aetheria").Table(table).Update(entry).RunAsync(connection);
_logger.Log(LogLevel.Information, $"Uploaded entry to RethinkDB: {entry.ID} result: {result}");
};
cache.OnDataInsertLocal += async entry =>
{
var table = entry.GetType().GetCustomAttribute<RethinkTableAttribute>()?.TableName ?? "Other";
var result = await R.Db("Aetheria").Table(table).Insert(entry).RunAsync(connection);
_logger.Log(LogLevel.Information, $"Inserted entry to RethinkDB: {entry.ID} result: {result}");
};
cache.OnDataDeleteLocal += async entry =>
{
var table = entry.GetType().GetCustomAttribute<RethinkTableAttribute>()?.TableName ?? "Other";
var result = await R.Db("Aetheria").Table(table).Get(entry.ID).Delete().RunAsync(connection);
_logger.Log(LogLevel.Information, $"Deleted entry from RethinkDB: {entry.ID} result: {result}");
};
// Get data from RethinkDB
GetTable("Items", connection, cache);
GetTable("Galaxy", connection, cache);
GetTable("Users", connection, cache);
// Subscribe to changes from RethinkDB
SubscribeTable("Items", connection, cache);
SubscribeTable("Galaxy", connection, cache);
SubscribeTable("Users", connection, cache);
var server = new MasterServer(cache)
{
Logger = _logger
};
server.Start();
var context = new GameContext(cache);
server.AddMessageListener<GalaxyRequestMessage>(galaxyRequest => galaxyRequest.Peer.Send(
new GalaxyResponseMessage
{
Zones = cache.GetAll<ZoneData>().Select(zd=>
new GalaxyResponseZone
{
Name = zd.Name,
Position = zd.Position,
ZoneID = zd.ID,
Links = zd.Wormholes.ToArray()
}).ToArray()
}));
server.AddMessageListener<ZoneRequestMessage>(zoneRequest =>
{
var zone = cache.Get<ZoneData>(zoneRequest.ZoneID);
// Zone has not been populated, generate the contents now!
if (!zone.Planets.Any())
{
var planets = ZoneGenerator.GenerateEntities(context, zone, 10000, 2000);
// Create collections to map between zone generator output and database entries
var orbitMap = new Dictionary<Planet, OrbitData>();
var orbitInverseMap = new Dictionary<OrbitData, Planet>();
// Create orbit database entries
var orbitData = planets.Select(planet =>
{
var data = new OrbitData()
{
ID = Guid.NewGuid(),
Distance = planet.Distance,
Period = planet.Period,
Phase = planet.Phase,
Zone = zone.ID
};
orbitMap[planet] = data;
orbitInverseMap[data] = planet;
return data;
}).ToArray();
// Link OrbitData parents to database GUIDs
foreach (var data in orbitData)
data.Parent = orbitInverseMap[data].Parent != null
? orbitMap[orbitInverseMap[data].Parent].ID
: (Guid?) null;
foreach (var data in orbitData) cache.Add(data);
var planetData = planets.Select(planet =>
{
var planetDatum = new PlanetData
{
Mass = planet.Mass,
ID = Guid.NewGuid(),
Orbit = orbitMap[planet].ID,
Zone = zone.ID
};
planetDatum.Name = planetDatum.ID.ToString().Substring(0, 8);
return planetDatum;
}).ToArray();
foreach (var data in planetData) cache.Add(data);
zone.Planets = planetData.Select(pd => pd.ID).ToList();
zone.Orbits = orbitData.Select(od => od.ID).ToList();
cache.Add(zone);
}
zoneRequest.Peer.Send(
new ZoneResponseMessage
{
Zone = zone,
Contents = zone.Orbits.Select(id=>cache.Get(id))
.Concat(zone.Planets.Select(id=>cache.Get(id)))
.Concat(zone.Stations.Select(id=>cache.Get(id))).ToArray()
});
});
while (true)
{
Thread.Sleep(100);
}
}
private static void SubscribeTable(string table, Connection connection, DatabaseCache cache)
{
Task.Run(async () =>
{
var result = await R.Db("Aetheria").Table(table).Changes()
.RunChangesAsync<DatabaseEntry>(connection);
while (await result.MoveNextAsync())
{
var change = result.Current;
if (change.OldValue == null)
{
_logger.Log(LogLevel.Information,
$"Received change from RethinkDB {table} table (Entry Created): {change.NewValue.GetType()} {(change.NewValue as INamedEntry)?.EntryName ?? ""}:{change.NewValue.ID}");
cache.Add(change.NewValue, true);
}
else if (change.NewValue == null)
{
_logger.Log(LogLevel.Information,
$"Received change from RethinkDB {table} table (Entry Deleted): {change.OldValue.GetType()} {(change.OldValue as INamedEntry)?.EntryName ?? ""}:{change.OldValue.ID}");
cache.Delete(change.NewValue, true);
}
else
{
_logger.Log(LogLevel.Information,
$"Received change from RethinkDB {table} table: {change.NewValue.GetType()} {(change.NewValue as INamedEntry)?.EntryName ?? ""}:{change.NewValue.ID}");
cache.Add(change.NewValue, true);
}
}
});
}
private static void GetTable(string table, Connection connection, DatabaseCache cache)
{
Task.Run(async () =>
{
var result = await R.Db("Aetheria").Table(table).RunCursorAsync<DatabaseEntry>(connection);
while (await result.MoveNextAsync())
{
var entry = result.Current;
_logger.Log(LogLevel.Information,
$"Received {table} entry from RethinkDB: {entry.GetType()} {(entry as INamedEntry)?.EntryName ?? ""}:{entry.ID}");
cache.Add(entry, true);
}
});
}
}
}