Use async DB queries.

This commit is contained in:
Pieter-Jan Briers
2020-06-26 03:46:08 +02:00
parent 357c110535
commit 579ff6bb26
20 changed files with 656 additions and 148 deletions

View File

@@ -1,3 +1,4 @@
using System;
using System.Linq;
using Content.Client.Interfaces;
using Content.Shared.Preferences;
@@ -17,6 +18,7 @@ namespace Content.Client
[Dependency] private readonly IClientNetManager _netManager;
#pragma warning restore 649
public event Action OnServerDataLoaded;
public GameSettings Settings { get; private set; }
public PlayerPreferences Preferences { get; private set; }
@@ -69,6 +71,8 @@ namespace Content.Client
{
Preferences = message.Preferences;
Settings = message.Settings;
OnServerDataLoaded?.Invoke();
}
}
}

View File

@@ -1,9 +1,14 @@
using System;
using Content.Shared.Preferences;
namespace Content.Client.Interfaces
{
public interface IClientPreferencesManager
{
event Action OnServerDataLoaded;
bool ServerDataLoaded => Settings != null;
GameSettings Settings { get; }
PlayerPreferences Preferences { get; }
void Initialize();

View File

@@ -136,7 +136,6 @@ namespace Content.Client.UserInterface
_createNewCharacterButton = new Button
{
Text = "Create new slot...",
ToolTip = $"A maximum of {preferencesManager.Settings.MaxCharacterSlots} characters are allowed."
};
_createNewCharacterButton.OnPressed += args =>
{
@@ -155,6 +154,8 @@ namespace Content.Client.UserInterface
hBox.AddChild(_humanoidProfileEditor);
UpdateUI();
preferencesManager.OnServerDataLoaded += UpdateUI;
}
public void Save() => _humanoidProfileEditor.Save();
@@ -164,6 +165,15 @@ namespace Content.Client.UserInterface
var numberOfFullSlots = 0;
var characterButtonsGroup = new ButtonGroup();
_charactersVBox.RemoveAllChildren();
if (!_preferencesManager.ServerDataLoaded)
{
return;
}
_createNewCharacterButton.ToolTip =
$"A maximum of {_preferencesManager.Settings.MaxCharacterSlots} characters are allowed.";
var characterIndex = 0;
foreach (var character in _preferencesManager.Preferences.Characters)
{

View File

@@ -51,8 +51,7 @@ namespace Content.Client.UserInterface
public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager)
{
_random = IoCManager.Resolve<IRobustRandom>();
Profile = (HumanoidCharacterProfile) preferencesManager.Preferences.SelectedCharacter;
CharacterSlot = preferencesManager.Preferences.SelectedCharacterIndex;
_preferencesManager = preferencesManager;
var margin = new MarginContainer
@@ -365,11 +364,23 @@ namespace Content.Client.UserInterface
#endregion Save
UpdateControls();
if (preferencesManager.ServerDataLoaded)
{
LoadServerData();
}
preferencesManager.OnServerDataLoaded += LoadServerData;
IsDirty = false;
}
private void LoadServerData()
{
Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences.SelectedCharacter;
CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
UpdateControls();
}
private void SetAge(int newAge)
{
Profile = Profile?.WithAge(newAge);

View File

@@ -22,6 +22,8 @@ namespace Content.Client.UserInterface
private readonly IClientPreferencesManager _preferencesManager;
private IEntity _previewDummy;
private readonly Label _summaryLabel;
private VBoxContainer _loaded;
private Label _unloaded;
public LobbyCharacterPreviewPanel(IEntityManager entityManager,
IClientPreferencesManager preferencesManager)
@@ -50,9 +52,13 @@ namespace Content.Client.UserInterface
var vBox = new VBoxContainer();
vBox.AddChild(header);
vBox.AddChild(CharacterSetupButton);
vBox.AddChild(_summaryLabel);
_unloaded = new Label {Text = "Your character preferences have not yet loaded, please stand by."};
_loaded = new VBoxContainer {Visible = false};
_loaded.AddChild(CharacterSetupButton);
_loaded.AddChild(_summaryLabel);
var hBox = new HBoxContainer();
hBox.AddChild(viewSouth);
@@ -60,11 +66,15 @@ namespace Content.Client.UserInterface
hBox.AddChild(viewWest);
hBox.AddChild(viewEast);
vBox.AddChild(hBox);
_loaded.AddChild(hBox);
vBox.AddChild(_loaded);
vBox.AddChild(_unloaded);
AddChild(vBox);
UpdateUI();
_preferencesManager.OnServerDataLoaded += UpdateUI;
}
public Button CharacterSetupButton { get; }
@@ -89,6 +99,15 @@ namespace Content.Client.UserInterface
public void UpdateUI()
{
if (!_preferencesManager.ServerDataLoaded)
{
_loaded.Visible = false;
_unloaded.Visible = true;
}
else
{
_loaded.Visible = true;
_unloaded.Visible = false;
if (!(_preferencesManager.Preferences.SelectedCharacter is HumanoidCharacterProfile selectedCharacter))
{
_summaryLabel.Text = string.Empty;
@@ -102,6 +121,7 @@ namespace Content.Client.UserInterface
GiveDummyJobClothes(_previewDummy, selectedCharacter);
}
}
}
public static void GiveDummyJobClothes(IEntity dummy, HumanoidCharacterProfile profile)
{

View File

@@ -0,0 +1,154 @@
// <auto-generated />
using Content.Server.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Content.Server.Database.Migrations.Postgres
{
[DbContext(typeof(PostgresPreferencesDbContext))]
[Migration("20200625230829_AddSlotPrefsIdIndex")]
partial class AddSlotPrefsIdIndex
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.4")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b =>
{
b.Property<int>("HumanoidProfileId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("Age")
.HasColumnType("integer");
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("EyeColor")
.IsRequired()
.HasColumnType("text");
b.Property<string>("FacialHairColor")
.IsRequired()
.HasColumnType("text");
b.Property<string>("FacialHairName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("HairColor")
.IsRequired()
.HasColumnType("text");
b.Property<string>("HairName")
.IsRequired()
.HasColumnType("text");
b.Property<int>("PreferenceUnavailable")
.HasColumnType("integer");
b.Property<int>("PrefsId")
.HasColumnType("integer");
b.Property<string>("Sex")
.IsRequired()
.HasColumnType("text");
b.Property<string>("SkinColor")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Slot")
.HasColumnType("integer");
b.Property<string>("SlotName")
.IsRequired()
.HasColumnType("text");
b.HasKey("HumanoidProfileId");
b.HasIndex("PrefsId");
b.HasIndex("Slot", "PrefsId")
.IsUnique();
b.ToTable("HumanoidProfile");
});
modelBuilder.Entity("Content.Server.Database.Job", b =>
{
b.Property<int>("JobId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("JobName")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Priority")
.HasColumnType("integer");
b.Property<int>("ProfileHumanoidProfileId")
.HasColumnType("integer");
b.HasKey("JobId");
b.HasIndex("ProfileHumanoidProfileId");
b.ToTable("Job");
});
modelBuilder.Entity("Content.Server.Database.Prefs", b =>
{
b.Property<int>("PrefsId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("SelectedCharacterSlot")
.HasColumnType("integer");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text");
b.HasKey("PrefsId");
b.HasIndex("Username")
.IsUnique();
b.ToTable("Preferences");
});
modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b =>
{
b.HasOne("Content.Server.Database.Prefs", "Prefs")
.WithMany("HumanoidProfiles")
.HasForeignKey("PrefsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Content.Server.Database.Job", b =>
{
b.HasOne("Content.Server.Database.HumanoidProfile", "Profile")
.WithMany("Jobs")
.HasForeignKey("ProfileHumanoidProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Content.Server.Database.Migrations.Postgres
{
public partial class AddSlotPrefsIdIndex : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_HumanoidProfile_Slot_PrefsId",
table: "HumanoidProfile",
columns: new[] { "Slot", "PrefsId" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_HumanoidProfile_Slot_PrefsId",
table: "HumanoidProfile");
}
}
}

View File

@@ -1,7 +1,8 @@
// <auto-generated />
using Content.Server.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Content.Server.Database.Migrations.Postgres
@@ -14,7 +15,7 @@ namespace Content.Server.Database.Migrations.Postgres
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.0")
.HasAnnotation("ProductVersion", "3.1.4")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b =>
@@ -76,6 +77,9 @@ namespace Content.Server.Database.Migrations.Postgres
b.HasIndex("PrefsId");
b.HasIndex("Slot", "PrefsId")
.IsUnique();
b.ToTable("HumanoidProfile");
});

View File

@@ -0,0 +1,148 @@
// <auto-generated />
using Content.Server.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Content.Server.Database.Migrations.Sqlite
{
[DbContext(typeof(SqlitePreferencesDbContext))]
[Migration("20200625230839_AddSlotPrefsIdIndex")]
partial class AddSlotPrefsIdIndex
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.4");
modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b =>
{
b.Property<int>("HumanoidProfileId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Age")
.HasColumnType("INTEGER");
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("EyeColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("FacialHairColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("FacialHairName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("HairColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("HairName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("PreferenceUnavailable")
.HasColumnType("INTEGER");
b.Property<int>("PrefsId")
.HasColumnType("INTEGER");
b.Property<string>("Sex")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("SkinColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Slot")
.HasColumnType("INTEGER");
b.Property<string>("SlotName")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("HumanoidProfileId");
b.HasIndex("PrefsId");
b.HasIndex("Slot", "PrefsId")
.IsUnique();
b.ToTable("HumanoidProfile");
});
modelBuilder.Entity("Content.Server.Database.Job", b =>
{
b.Property<int>("JobId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("JobName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Priority")
.HasColumnType("INTEGER");
b.Property<int>("ProfileHumanoidProfileId")
.HasColumnType("INTEGER");
b.HasKey("JobId");
b.HasIndex("ProfileHumanoidProfileId");
b.ToTable("Job");
});
modelBuilder.Entity("Content.Server.Database.Prefs", b =>
{
b.Property<int>("PrefsId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("SelectedCharacterSlot")
.HasColumnType("INTEGER");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("PrefsId");
b.HasIndex("Username")
.IsUnique();
b.ToTable("Preferences");
});
modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b =>
{
b.HasOne("Content.Server.Database.Prefs", "Prefs")
.WithMany("HumanoidProfiles")
.HasForeignKey("PrefsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Content.Server.Database.Job", b =>
{
b.HasOne("Content.Server.Database.HumanoidProfile", "Profile")
.WithMany("Jobs")
.HasForeignKey("ProfileHumanoidProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Content.Server.Database.Migrations.Sqlite
{
public partial class AddSlotPrefsIdIndex : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_HumanoidProfile_Slot_PrefsId",
table: "HumanoidProfile",
columns: new[] { "Slot", "PrefsId" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_HumanoidProfile_Slot_PrefsId",
table: "HumanoidProfile");
}
}
}

View File

@@ -1,7 +1,8 @@
// <auto-generated />
using Content.Server.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Content.Server.Database.Migrations
{
@@ -12,7 +13,7 @@ namespace Content.Server.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.0");
.HasAnnotation("ProductVersion", "3.1.4");
modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b =>
{
@@ -72,6 +73,9 @@ namespace Content.Server.Database.Migrations
b.HasIndex("PrefsId");
b.HasIndex("Slot", "PrefsId")
.IsUnique();
b.ToTable("HumanoidProfile");
});

View File

@@ -53,12 +53,17 @@ namespace Content.Server.Database
}
public DbSet<Prefs> Preferences { get; set; } = null!;
public DbSet<HumanoidProfile> HumanoidProfile { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Prefs>()
.HasIndex(p => p.Username)
.IsUnique();
modelBuilder.Entity<HumanoidProfile>()
.HasIndex(p => new {p.Slot, p.PrefsId})
.IsUnique();
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace Content.Server.Database
@@ -20,16 +22,16 @@ namespace Content.Server.Database
_prefsCtx.Database.Migrate();
}
public Prefs GetPlayerPreferences(string username)
public async Task<Prefs?> GetPlayerPreferences(string username)
{
return _prefsCtx
return await _prefsCtx
.Preferences
.Include(p => p.HumanoidProfiles)
.ThenInclude(h => h.Jobs)
.SingleOrDefault(p => p.Username == username);
.SingleOrDefaultAsync(p => p.Username == username);
}
public void SaveSelectedCharacterIndex(string username, int slot)
public async Task SaveSelectedCharacterIndex(string username, int slot)
{
var prefs = _prefsCtx.Preferences.SingleOrDefault(p => p.Username == username);
if (prefs is null)
@@ -40,10 +42,10 @@ namespace Content.Server.Database
});
else
prefs.SelectedCharacterSlot = slot;
_prefsCtx.SaveChanges();
await _prefsCtx.SaveChangesAsync();
}
public void SaveCharacterSlot(string username, HumanoidProfile newProfile)
public async Task SaveCharacterSlotAsync(string username, HumanoidProfile newProfile)
{
var prefs = _prefsCtx
.Preferences
@@ -53,17 +55,29 @@ namespace Content.Server.Database
.SingleOrDefault(h => h.Slot == newProfile.Slot);
if (!(oldProfile is null)) prefs.HumanoidProfiles.Remove(oldProfile);
prefs.HumanoidProfiles.Add(newProfile);
_prefsCtx.SaveChanges();
await _prefsCtx.SaveChangesAsync();
}
public void DeleteCharacterSlot(string username, int slot)
public async Task DeleteCharacterSlotAsync(string username, int slot)
{
var profile = _prefsCtx
.Preferences
.Single(p => p.Username == username)
.HumanoidProfiles
.RemoveAll(h => h.Slot == slot);
_prefsCtx.SaveChanges();
await _prefsCtx.SaveChangesAsync();
}
public async Task<Dictionary<string, HumanoidProfile>> GetProfilesForPlayersAsync(List<string> usernames)
{
return await _prefsCtx.HumanoidProfile
.Include(p => p.Jobs)
.Join(_prefsCtx.Preferences,
profile => new {profile.Slot, profile.PrefsId},
prefs => new {Slot = prefs.SelectedCharacterSlot, prefs.PrefsId},
(profile, prefs) => new {prefs.Username, profile})
.Where(p => usernames.Contains(p.Username))
.ToDictionaryAsync(arg => arg.Username, arg => arg.profile);
}
}
}

View File

@@ -82,9 +82,9 @@ namespace Content.Server
{
base.PostInit();
IoCManager.Resolve<IServerPreferencesManager>().FinishInit();
_gameTicker.Initialize();
IoCManager.Resolve<ISandboxManager>().Initialize();
IoCManager.Resolve<IServerPreferencesManager>().FinishInit();
IoCManager.Resolve<RecipeManager>().Initialize();
IoCManager.Resolve<BlackboardManager>().Initialize();
IoCManager.Resolve<IPDAUplinkManager>().Initialize();

View File

@@ -19,7 +19,7 @@ namespace Content.Server.GameTicking
private readonly Dictionary<string, int> _spawnedPositions = new Dictionary<string, int>();
private Dictionary<IPlayerSession, string> AssignJobs(List<IPlayerSession> available,
Dictionary<IPlayerSession, HumanoidCharacterProfile> profiles)
Dictionary<string, HumanoidCharacterProfile> profiles)
{
// Calculate positions available round-start for each job.
var availablePositions = GetBasePositions(true);
@@ -38,7 +38,7 @@ namespace Content.Server.GameTicking
var candidates = available
.Select(player =>
{
var profile = profiles[player];
var profile = profiles[player.Name];
var availableJobs = profile.JobPriorities
.Where(j =>

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.GameObjects;
using Content.Server.GameObjects.Components.Access;
using Content.Server.GameObjects.Components.Markers;
@@ -205,7 +206,7 @@ namespace Content.Server.GameTicking
}
}
public void StartRound(bool force = false)
public async void StartRound(bool force = false)
{
DebugTools.Assert(RunLevel == GameRunLevel.PreRoundLobby);
Logger.InfoS("ticker", "Starting round!");
@@ -227,7 +228,18 @@ namespace Content.Server.GameTicking
RoundLengthMetric.Set(0);
// Get the profiles for each player for easier lookup.
var profiles = readyPlayers.ToDictionary(p => p, GetPlayerProfile);
var profiles = (await _prefsManager.GetSelectedProfilesForPlayersAsync(
readyPlayers
.Select(p => p.Name).ToList()))
.ToDictionary(p => p.Key, p => (HumanoidCharacterProfile) p.Value);
foreach (var readyPlayer in readyPlayers)
{
if (!profiles.ContainsKey(readyPlayer.Name))
{
profiles.Add(readyPlayer.Name, HumanoidCharacterProfile.Default());
}
}
var assignedJobs = AssignJobs(readyPlayers, profiles);
@@ -239,7 +251,7 @@ namespace Content.Server.GameTicking
continue;
}
var profile = profiles[player];
var profile = profiles[player.Name];
if (profile.PreferenceUnavailable == PreferenceUnavailableMode.SpawnAsOverflow)
{
assignedJobs.Add(player, OverflowJob);
@@ -259,7 +271,8 @@ namespace Content.Server.GameTicking
{
SetStartPreset(_configurationManager.GetCVar<string>("game.fallbackpreset"));
var newPreset = MakeGamePreset();
_chatManager.DispatchServerAnnouncement($"Failed to start {preset.ModeTitle} mode! Defaulting to {newPreset.ModeTitle}...");
_chatManager.DispatchServerAnnouncement(
$"Failed to start {preset.ModeTitle} mode! Defaulting to {newPreset.ModeTitle}...");
if (!newPreset.Start(readyPlayers, force))
{
throw new ApplicationException("Fallback preset failed to start!");
@@ -278,8 +291,9 @@ namespace Content.Server.GameTicking
IoCManager.Resolve<IServerNetManager>().ServerSendToAll(msg);
}
private HumanoidCharacterProfile GetPlayerProfile(IPlayerSession p) =>
(HumanoidCharacterProfile) _prefsManager.GetPreferences(p.SessionId.Username).SelectedCharacter;
private async Task<HumanoidCharacterProfile> GetPlayerProfileAsync(IPlayerSession p) =>
(HumanoidCharacterProfile) (await _prefsManager.GetPreferencesAsync(p.SessionId.Username))
.SelectedCharacter;
public void EndRound()
{
@@ -307,7 +321,9 @@ namespace Content.Server.GameTicking
{
PlayerOOCName = ply.Name,
PlayerICName = mind.CurrentEntity.Name,
Role = antag ? mind.AllRoles.First(role => role.Antag).Name : mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("Unknown"),
Role = antag
? mind.AllRoles.First(role => role.Antag).Name
: mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("Unknown"),
Antag = antag
};
listOfPlayerInfo.Add(playerEndRoundInfo);
@@ -725,14 +741,14 @@ namespace Content.Server.GameTicking
}, _updateShutdownCts.Token);
}
private void SpawnPlayer(IPlayerSession session, string jobId = null, bool lateJoin = true)
private async void SpawnPlayer(IPlayerSession session, string jobId = null, bool lateJoin = true)
{
var character = (HumanoidCharacterProfile) _prefsManager
.GetPreferences(session.SessionId.Username)
.SelectedCharacter;
_playerJoinGame(session);
var character = (HumanoidCharacterProfile) (await _prefsManager
.GetPreferencesAsync(session.SessionId.Username))
.SelectedCharacter;
var data = session.ContentData();
data.WipeMind();
data.Mind = new Mind(session.SessionId)
@@ -789,11 +805,12 @@ namespace Content.Server.GameTicking
{
if (mindComponent.Mind.AllRoles.Any(role => role.Antag)) //Give antags a new uplinkaccount.
{
var uplinkAccount = new UplinkAccount(mob.Uid, 20); //TODO: make me into a variable based on server pop or something.
var uplinkAccount =
new UplinkAccount(mob.Uid,
20); //TODO: make me into a variable based on server pop or something.
pdaComponent.InitUplinkAccount(uplinkAccount);
}
}
}
private void AddManifestEntry(string characterName, string jobId)
@@ -801,13 +818,14 @@ namespace Content.Server.GameTicking
_manifest.Add(new ManifestEntry(characterName, jobId));
}
private void _spawnObserver(IPlayerSession session)
private async void _spawnObserver(IPlayerSession session)
{
var name = _prefsManager
.GetPreferences(session.SessionId.Username)
_playerJoinGame(session);
var name = (await _prefsManager
.GetPreferencesAsync(session.SessionId.Username))
.SelectedCharacter.Name;
_playerJoinGame(session);
var data = session.ContentData();
data.WipeMind();
data.Mind = new Mind(session.SessionId);

View File

@@ -1,3 +1,5 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Content.Shared.Preferences;
using Robust.Server.Interfaces.Player;
@@ -7,7 +9,8 @@ namespace Content.Server.Interfaces
{
void FinishInit();
void OnClientConnected(IPlayerSession session);
PlayerPreferences GetPreferences(string username);
Task<PlayerPreferences> GetPreferencesAsync(string username);
Task<IEnumerable<KeyValuePair<string, ICharacterProfile>>> GetSelectedProfilesForPlayersAsync(List<string> usernames);
void StartInit();
}
}

View File

@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.Database;
using Content.Shared.Preferences;
using Robust.Shared.Maths;
@@ -16,38 +19,28 @@ namespace Content.Server.Preferences
private readonly int _maxCharacterSlots;
private readonly PrefsDb _prefsDb;
// We use a single DbContext for the entire DB connection, and EFCore doesn't allow concurrent access.
// So we need this semaphore to prevent bugs.
private readonly SemaphoreSlim _prefsSemaphore = new SemaphoreSlim(1, 1);
public PreferencesDatabase(IDatabaseConfiguration dbConfig, int maxCharacterSlots)
{
_maxCharacterSlots = maxCharacterSlots;
_prefsDb = new PrefsDb(dbConfig);
}
public PlayerPreferences GetPlayerPreferences(string username)
public async Task<PlayerPreferences> GetPlayerPreferencesAsync(string username)
{
var prefs = _prefsDb.GetPlayerPreferences(username);
await _prefsSemaphore.WaitAsync();
try
{
var prefs = await _prefsDb.GetPlayerPreferences(username);
if (prefs is null) return null;
var profiles = new ICharacterProfile[_maxCharacterSlots];
foreach (var profile in prefs.HumanoidProfiles)
{
var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority);
profiles[profile.Slot] = new HumanoidCharacterProfile(
profile.CharacterName,
profile.Age,
profile.Sex == "Male" ? Male : Female,
new HumanoidCharacterAppearance
(
profile.HairName,
Color.FromHex(profile.HairColor),
profile.FacialHairName,
Color.FromHex(profile.FacialHairColor),
Color.FromHex(profile.EyeColor),
Color.FromHex(profile.SkinColor)
),
jobs,
(PreferenceUnavailableMode) profile.PreferenceUnavailable
);
profiles[profile.Slot] = ConvertProfiles(profile);
}
return new PlayerPreferences
@@ -56,20 +49,37 @@ namespace Content.Server.Preferences
prefs.SelectedCharacterSlot
);
}
public void SaveSelectedCharacterIndex(string username, int index)
finally
{
index = index.Clamp(0, _maxCharacterSlots - 1);
_prefsDb.SaveSelectedCharacterIndex(username, index);
_prefsSemaphore.Release();
}
}
public void SaveCharacterSlot(string username, ICharacterProfile profile, int slot)
public async Task SaveSelectedCharacterIndexAsync(string username, int index)
{
await _prefsSemaphore.WaitAsync();
try
{
index = index.Clamp(0, _maxCharacterSlots - 1);
await _prefsDb.SaveSelectedCharacterIndex(username, index);
}
finally
{
_prefsSemaphore.Release();
}
}
public async Task SaveCharacterSlotAsync(string username, ICharacterProfile profile, int slot)
{
if (slot < 0 || slot >= _maxCharacterSlots)
return;
await _prefsSemaphore.WaitAsync();
try
{
if (profile is null)
{
DeleteCharacterSlot(username, slot);
await DeleteCharacterSlotAsync(username, slot);
return;
}
@@ -97,12 +107,55 @@ namespace Content.Server.Preferences
.Where(j => j.Value != JobPriority.Never)
.Select(j => new Job {JobName = j.Key, Priority = (DbJobPriority) j.Value})
);
_prefsDb.SaveCharacterSlot(username, entity);
await _prefsDb.SaveCharacterSlotAsync(username, entity);
}
finally
{
_prefsSemaphore.Release();
}
}
private void DeleteCharacterSlot(string username, int slot)
private async Task DeleteCharacterSlotAsync(string username, int slot)
{
_prefsDb.DeleteCharacterSlot(username, slot);
await _prefsDb.DeleteCharacterSlotAsync(username, slot);
}
public async Task<IEnumerable<KeyValuePair<string, ICharacterProfile>>> GetSelectedProfilesForPlayersAsync(
List<string> usernames)
{
await _prefsSemaphore.WaitAsync();
try
{
var profiles = await _prefsDb.GetProfilesForPlayersAsync(usernames);
return profiles.Select(
p => new KeyValuePair<string, ICharacterProfile>(p.Key, ConvertProfiles(p.Value)));
}
finally
{
_prefsSemaphore.Release();
}
}
private static HumanoidCharacterProfile ConvertProfiles(HumanoidProfile profile)
{
var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority);
return new HumanoidCharacterProfile(
profile.CharacterName,
profile.Age,
profile.Sex == "Male" ? Male : Female,
new HumanoidCharacterAppearance
(
profile.HairName,
Color.FromHex(profile.HairColor),
profile.FacialHairName,
Color.FromHex(profile.FacialHairColor),
Color.FromHex(profile.EyeColor),
Color.FromHex(profile.SkinColor)
),
jobs,
(PreferenceUnavailableMode) profile.PreferenceUnavailable
);
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Content.Server.Database;
@@ -82,20 +83,22 @@ namespace Content.Server.Preferences
_preferencesDb = _prefsDbLoadTask.Result;
}
private void HandleSelectCharacterMessage(MsgSelectCharacter message)
private async void HandleSelectCharacterMessage(MsgSelectCharacter message)
{
_preferencesDb.SaveSelectedCharacterIndex(message.MsgChannel.SessionId.Username, message.SelectedCharacterIndex);
await _preferencesDb.SaveSelectedCharacterIndexAsync(message.MsgChannel.SessionId.Username,
message.SelectedCharacterIndex);
}
private void HandleUpdateCharacterMessage(MsgUpdateCharacter message)
private async void HandleUpdateCharacterMessage(MsgUpdateCharacter message)
{
_preferencesDb.SaveCharacterSlot(message.MsgChannel.SessionId.Username, message.Profile, message.Slot);
await _preferencesDb.SaveCharacterSlotAsync(message.MsgChannel.SessionId.Username, message.Profile,
message.Slot);
}
public void OnClientConnected(IPlayerSession session)
public async void OnClientConnected(IPlayerSession session)
{
var msg = _netManager.CreateNetMessage<MsgPreferencesAndSettings>();
msg.Preferences = GetPreferences(session.SessionId.Username);
msg.Preferences = await GetPreferencesAsync(session.SessionId.Username);
msg.Settings = new GameSettings
{
MaxCharacterSlots = _configuration.GetCVar<int>("game.maxcharacterslots")
@@ -106,26 +109,31 @@ namespace Content.Server.Preferences
/// <summary>
/// Returns the requested <see cref="PlayerPreferences"/> or null if not found.
/// </summary>
private PlayerPreferences GetFromSql(string username)
private async Task<PlayerPreferences> GetFromSql(string username)
{
return _preferencesDb.GetPlayerPreferences(username);
return await _preferencesDb.GetPlayerPreferencesAsync(username);
}
/// <summary>
/// Retrieves preferences for the given username from storage.
/// Creates and saves default preferences if they are not found, then returns them.
/// </summary>
public PlayerPreferences GetPreferences(string username)
public async Task<PlayerPreferences> GetPreferencesAsync(string username)
{
var prefs = GetFromSql(username);
var prefs = await GetFromSql(username);
if (prefs is null)
{
_preferencesDb.SaveSelectedCharacterIndex(username, 0);
_preferencesDb.SaveCharacterSlot(username, HumanoidCharacterProfile.Default(), 0);
prefs = GetFromSql(username);
await _preferencesDb.SaveSelectedCharacterIndexAsync(username, 0);
await _preferencesDb.SaveCharacterSlotAsync(username, HumanoidCharacterProfile.Default(), 0);
prefs = await GetFromSql(username);
}
return prefs;
}
public async Task<IEnumerable<KeyValuePair<string, ICharacterProfile>>> GetSelectedProfilesForPlayersAsync(List<string> usernames)
{
return await _preferencesDb.GetSelectedProfilesForPlayersAsync(usernames);
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Database;
using Content.Server.Preferences;
using Content.Shared;
@@ -44,64 +45,64 @@ namespace Content.Tests.Server.Preferences
}
[Test]
public void TestUserDoesNotExist()
public async Task TestUserDoesNotExist()
{
var db = GetDb();
Assert.Null(db.GetPlayerPreferences("[The database should be empty so any string should do]"));
Assert.Null(await db.GetPlayerPreferencesAsync("[The database should be empty so any string should do]"));
}
[Test]
public void TestUserDoesExist()
public async Task TestUserDoesExist()
{
var db = GetDb();
const string username = "bobby";
db.SaveSelectedCharacterIndex(username, 0);
var prefs = db.GetPlayerPreferences(username);
await db.SaveSelectedCharacterIndexAsync(username, 0);
var prefs = await db.GetPlayerPreferencesAsync(username);
Assert.NotNull(prefs);
Assert.Zero(prefs.SelectedCharacterIndex);
Assert.That(prefs.Characters.ToList().TrueForAll(character => character is null));
}
[Test]
public void TestUpdateCharacter()
public async Task TestUpdateCharacter()
{
var db = GetDb();
const string username = "charlie";
const int slot = 0;
var originalProfile = CharlieCharlieson();
db.SaveSelectedCharacterIndex(username, slot);
db.SaveCharacterSlot(username, originalProfile, slot);
var prefs = db.GetPlayerPreferences(username);
await db.SaveSelectedCharacterIndexAsync(username, slot);
await db.SaveCharacterSlotAsync(username, originalProfile, slot);
var prefs = await db.GetPlayerPreferencesAsync(username);
Assert.That(prefs.Characters.ElementAt(slot).MemberwiseEquals(originalProfile));
}
[Test]
public void TestDeleteCharacter()
public async Task TestDeleteCharacter()
{
var db = GetDb();
const string username = "charlie";
const int slot = 0;
db.SaveSelectedCharacterIndex(username, slot);
db.SaveCharacterSlot(username, CharlieCharlieson(), slot);
db.SaveCharacterSlot(username, null, slot);
var prefs = db.GetPlayerPreferences(username);
await db.SaveSelectedCharacterIndexAsync(username, slot);
await db.SaveCharacterSlotAsync(username, CharlieCharlieson(), slot);
await db.SaveCharacterSlotAsync(username, null, slot);
var prefs = await db.GetPlayerPreferencesAsync(username);
Assert.That(prefs.Characters.ToList().TrueForAll(character => character is null));
}
[Test]
public void TestInvalidSlot()
public async Task TestInvalidSlot()
{
var db = GetDb();
const string username = "charlie";
const int slot = -1;
db.SaveSelectedCharacterIndex(username, slot);
db.SaveCharacterSlot(username, CharlieCharlieson(), slot);
var prefs = db.GetPlayerPreferences(username);
await db.SaveSelectedCharacterIndexAsync(username, slot);
await db.SaveCharacterSlotAsync(username, CharlieCharlieson(), slot);
var prefs = await db.GetPlayerPreferencesAsync(username);
Assert.AreEqual(prefs.SelectedCharacterIndex, 0);
db.SaveSelectedCharacterIndex(username, MaxCharacterSlots);
prefs = db.GetPlayerPreferences(username);
await db.SaveSelectedCharacterIndexAsync(username, MaxCharacterSlots);
prefs = await db.GetPlayerPreferencesAsync(username);
Assert.AreEqual(prefs.SelectedCharacterIndex, MaxCharacterSlots - 1);
}
}