Skip to content

Commit

Permalink
Add LocalLobby
Browse files Browse the repository at this point in the history
  • Loading branch information
james7132 committed Nov 17, 2019
1 parent d41ea7e commit 1c86920
Show file tree
Hide file tree
Showing 8 changed files with 425 additions and 1 deletion.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Added more events to Lobby and LobbyMember.
* Added a high performance CRC32 implemenation for ease of use.
* Added shortcut IntegrationManager to simplify setup.
* Increased default max serialization size buffer to 65535 to match max packet sizes.
* Increased default max serialization size buffer to 65535 to match max datagram sizes for UDP.
* Changed Steamworks.NET to Facepunch.Steamworks for simpler code and IL2CPP compatibility.
* Added LocalLobby for entirely local lobby based code, useful for unit tests and
potentially using netplay code as local play.

## [0.1.1] - 2019-10-23
* Added Serializer/Deserializer overloads for reading/writing from raw byte pointers
Expand Down
8 changes: 8 additions & 0 deletions Runtime/Integrations/Local.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

170 changes: 170 additions & 0 deletions Runtime/Integrations/Local/LocalLobby.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;

namespace HouraiTeahouse.Networking {

/// <summary>
/// An object for creating a local implementation of HouraiNetworking lobbies.
///
/// Note that this class doesn't actually derive from Lobby: that would be
/// LocalLobbyView.
///
/// Primary use of this is for testing, but can also be used for establishing
/// local play using netplay-only codebase.
/// </summary>
public sealed class LocalLobby : IDisposable {

readonly MetadataContainer _metadata;
readonly Dictionary<AccountHandle, LocalLobbyView> _connectedViews;
readonly double _packetLossPercent;
readonly Random _packetLossRng;

public ulong Id { get; }
public LobbyType Type { get; set; }
public ulong OwnerId { get; private set; }
public int Capacity { get; set; }

/// <summary>
/// Creates a local lobby and provides you the connected owner's lobby.
/// </summary>
/// <param name="id">the lobby's ID</param>
/// <param name="capacity">how many player can connect.</param>
/// <param name="ownerId">the owner of the lobby.</param>
/// <param name="packetLossPercent">for unreliable packets, how often </param>
/// <param name="packetLossRng">a System.Random instance for randomly producing packet loss</param>
public static LocalLobbyView Create(ulong id, int capacity, ulong ownerId = 0,
double packetLossPercent = 0, Random packetLossRng = null) {
packetLossRng = packetLossRng ?? new Random();
var lobby = new LocalLobby(id, capacity, ownerId, packetLossPercent, packetLossRng);
LocalLobbyView owner = lobby.CreateView(ownerId);
owner.Join().Wait();
return owner;
}

LocalLobby(ulong id, int capacity, ulong ownerId,
double packetLossPercent, Random packetLossRng) {
Id = id;
Capacity = capacity;
_packetLossRng = packetLossRng;
_connectedViews = new Dictionary<AccountHandle, LocalLobbyView>();
_metadata = new MetadataContainer();
}

/// <summary>
/// Creates a view of the lobby, as seen from a remote player.
///
/// This does not connect the view to the lobby. LobbyView.Join must be called
/// to "connect" the view to to the lobby.
/// </summary>
/// <param name="id">the user to create the view from</param>
/// <returns>the Lobby view from the view of that player</returns>
public LocalLobbyView CreateView(AccountHandle id) {
if (_connectedViews.TryGetValue(id, out LocalLobbyView view)) {
return view;
}
return new LocalLobbyView(id, this);
}

/// <summary>
/// Deletes the lobby. This will invalidate the state of all related
/// LocalLobbyViews and the associated LobbyMembers.
/// </summary>
public void Delete() => Dispose();

internal void Connect(LocalLobbyView view) {
var handle = new AccountHandle(view.Id);
if (_connectedViews.ContainsKey(handle)) return;
_metadata.AddMember(handle);
_connectedViews[handle] = view;
foreach (var connectedView in _connectedViews.Values) {
connectedView.Members.Add(handle);
}
}

internal void Disconnect(LocalLobbyView view) {
var handle = new AccountHandle(view.Id);
if (!_connectedViews.ContainsKey(handle)) return;
_metadata.RemoveMember(handle);
_connectedViews.Remove(handle);
foreach (var connectedView in _connectedViews.Values) {
connectedView.Members.Remove(handle);
}
}

internal void SendLobbyMessage(AccountHandle source, FixedBuffer msg) {
foreach (var view in _connectedViews.Values) {
if (view.Members.TryGetValue(source, out LobbyMember member)) {
view.DispatchLobbyMessage(member, msg);
}
}
}

internal void SendNetworkMessage(AccountHandle source, AccountHandle target, FixedBuffer msg, Reliability reliability) {
// Simulate unreliablity
if (reliability == Reliability.Unreliable && _packetLossRng.NextDouble() > _packetLossPercent) {
return;
}
LocalLobbyView view;
LobbyMember member;
if (!_connectedViews.TryGetValue(target, out view) ||
!view.Members.TryGetValue(source, out member)) {
return;
}
member.DispatchNetworkMessage(msg);
}

internal IEnumerable<AccountHandle> GetMemberIds() => _connectedViews.Keys;
internal string GetMetadata(string key) => _metadata.GetMetadata(key);

internal void SetMetadata(string key, string value) {
if (_metadata.SetMetadata(key, value)) {
DispatchUpdate();
}
}

internal void DeleteMetadata(string key) {
if (_metadata.DeleteMetadata(key)) {
DispatchUpdate();
}
}

internal string GetMemberMetadata(AccountHandle handle, string key) =>
_metadata.GetMemberMetadata(handle, key);

internal void SetMemberMetadata(AccountHandle handle, string key, string value) {
if (_metadata.SetMemberMetadata(handle, key, value)) {
DispatchMemberUpdate(handle);
}
}

internal void DeleteMemberMetadata(AccountHandle handle, string key) {
if (_metadata.DeleteMemberMetadata(handle, key)) {
DispatchMemberUpdate(handle);
}
}

internal IReadOnlyDictionary<string, string> GetAllMetadata() => _metadata.GetAllMetadata();

void DispatchUpdate() {
foreach (var view in _connectedViews.Values) {
view.DispatchUpdate();
}
}

void DispatchMemberUpdate(AccountHandle handle) {
foreach (var view in _connectedViews.Values) {
if (view.Members.TryGetValue(handle, out LobbyMember member)) {
member.DispatchUpdate();
}
}
}

public void Dispose() {
foreach (var view in _connectedViews.Values) {
view.Dispose();
}
}

}

}
11 changes: 11 additions & 0 deletions Runtime/Integrations/Local/LocalLobby.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 78 additions & 0 deletions Runtime/Integrations/Local/LocalLobbyView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace HouraiTeahouse.Networking {

/// <summary>
/// A Lobby implementation that does not make any networked calls.
///
/// Unlike other lobby implementations, this does not do any permissions checking
/// when calling metadata mutation calls.
///
/// Primary use of this is for testing, but can also be used for establishing
/// local play using netplay-only codebase.
///
/// See also: <seealso cref="HouraiTeahouse.Networking.LocalLobby"/>
/// </summary>
public sealed class LocalLobbyView : Lobby {

/// <summary>
/// The underlying LocalLobby.
/// </summary>
public LocalLobby BaseLobby { get; }

public override ulong Id { get; }
public override ulong UserId { get; }
public override ulong OwnerId => BaseLobby.OwnerId;
public override LobbyType Type => BaseLobby.Type;
public override int Capacity {
get => BaseLobby.Capacity;
set => BaseLobby.Capacity = value;
}

internal LocalLobbyView(ulong userId, LocalLobby lobby) : base() {
UserId = userId;
BaseLobby = lobby;
Members.Refresh();
}

public override int MemberCount => Members.Count;
internal override IEnumerable<AccountHandle> GetMemberIds() => BaseLobby.GetMemberIds();
public override string GetMetadata(string key) => BaseLobby.GetMetadata(key);
public override void SetMetadata(string key, string value) => BaseLobby.SetMetadata(key, value);
public override void DeleteMetadata(string key) => BaseLobby.DeleteMetadata(key);
public override IReadOnlyDictionary<string, string> GetAllMetadata() => BaseLobby.GetAllMetadata();

internal override string GetMemberMetadata(AccountHandle handle, string key) =>
throw new NotSupportedException();
internal override void SetMemberMetadata(AccountHandle handle, string key, string value) =>
throw new NotSupportedException();
internal override void DeleteMemberMetadata(AccountHandle handle, string key) =>
throw new NotSupportedException();

public override void SendLobbyMessage(FixedBuffer message) {
BaseLobby.SendLobbyMessage(new AccountHandle(UserId), message);
}

internal override void SendNetworkMessage(AccountHandle target, FixedBuffer message,
Reliability reliability = Reliability.Reliable) {
BaseLobby.SendNetworkMessage(new AccountHandle(UserId), target, message, reliability);
}

public override Task Join() {
BaseLobby.Connect(this);
return Task.CompletedTask;
}

public override void Leave() {
BaseLobby.Disconnect(this);
Dispose();
}

public override void Delete() => Dispose();

}

}
11 changes: 11 additions & 0 deletions Runtime/Integrations/Local/LocalLobbyView.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 1c86920

Please sign in to comment.