refactor: replace events with observables

This commit is contained in:
Oliver Booth 2023-05-08 15:40:18 +01:00
parent 65a9a0e1e3
commit ee005f30a8
No known key found for this signature in database
GPG Key ID: 20BEB9DC87961025
12 changed files with 153 additions and 195 deletions

View File

@ -1,5 +1,5 @@
using VpSharp.Commands;
using VpSharp.EventData;
using VpSharp.Extensions;
namespace VpSharp.CSharp_Sample;
@ -21,7 +21,7 @@ internal static class Program
var commands = s_client.UseCommands(new CommandsExtensionConfiguration {Prefixes = new[] {"/"}});
commands.RegisterCommands<SayCommand>();
s_client.AvatarJoined += ClientOnAvatarJoined;
s_client.AvatarJoined.SubscribeAsync(async avatar => await s_client.SendMessageAsync($"Hello, {avatar.Name}!"));
await s_client.ConnectAsync();
await s_client.LoginAsync();
@ -30,9 +30,4 @@ internal static class Program
await Task.Delay(-1);
}
private static async void ClientOnAvatarJoined(object? sender, AvatarJoinedEventArgs args)
{
await s_client.SendMessageAsync($"Hello, {args.Avatar.Name}!");
}
}

View File

@ -1,5 +1,6 @@
Imports VpSharp.Commands
Imports VpSharp.EventData
Imports VpSharp.Extensions
Module Program
Private WithEvents _client As VirtualParadiseClient
@ -19,6 +20,8 @@ Module Program
_client = New VirtualParadiseClient(configuration)
_client.AvatarJoined.SubscribeAsync(Async Function(avatar) Await _client.SendMessageAsync($"Hello, {avatar.Name}"))
Dim commands = _client.UseCommands(New CommandsExtensionConfiguration With { .Prefixes = {"/"} })
commands.RegisterCommands(GetType(SayCommand))
@ -29,8 +32,4 @@ Module Program
Await Task.Delay(- 1)
End Function
Private Async Sub ClientOnAvatarJoined(sender As Object, args As AvatarJoinedEventArgs) Handles _client.AvatarJoined
Await _client.SendMessageAsync("Hello, " & args.Avatar.Name)
End Sub
End Module

View File

@ -59,6 +59,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1"/>
<PackageReference Include="System.Drawing.Common" Version="7.0.0"/>
<PackageReference Include="System.Reactive" Version="5.0.0"/>
<PackageReference Include="X10D" Version="3.2.0"/>
<PackageReference Include="ZString" Version="2.5.0"/>
</ItemGroup>

View File

@ -1,4 +1,5 @@
using VpSharp.EventData;
using VpSharp.Entities;
using VpSharp.EventData;
namespace VpSharp.ClientExtensions;
@ -27,7 +28,7 @@ public abstract class VirtualParadiseClientExtension
/// Called when a chat message is received.
/// </summary>
/// <param name="args">An object containing event data.</param>
protected internal virtual Task OnMessageReceived(MessageReceivedEventArgs args)
protected internal virtual Task OnMessageReceived(VirtualParadiseMessage message)
{
return Task.CompletedTask;
}

View File

@ -1,24 +0,0 @@
using VpSharp.Entities;
namespace VpSharp.EventData;
/// <summary>
/// Provides event arguments for <see cref="VirtualParadiseClient.AvatarJoined" />.
/// </summary>
public sealed class AvatarJoinedEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="AvatarJoinedEventArgs" /> class.
/// </summary>
/// <param name="avatar">The newly-joined avatar.</param>
public AvatarJoinedEventArgs(VirtualParadiseAvatar avatar)
{
Avatar = avatar;
}
/// <summary>
/// Gets the newly-joined avatar.
/// </summary>
/// <value>The newly-joined avatar.</value>
public VirtualParadiseAvatar Avatar { get; }
}

View File

@ -1,24 +0,0 @@
using VpSharp.Entities;
namespace VpSharp.EventData;
/// <summary>
/// Provides event arguments for <see cref="VirtualParadiseClient.AvatarLeft" />.
/// </summary>
public sealed class AvatarLeftEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="AvatarLeftEventArgs" /> class.
/// </summary>
/// <param name="avatar">The newly-departed avatar.</param>
public AvatarLeftEventArgs(VirtualParadiseAvatar avatar)
{
Avatar = avatar;
}
/// <summary>
/// Gets the newly-departed avatar.
/// </summary>
/// <value>The newly-departed avatar.</value>
public VirtualParadiseAvatar Avatar { get; }
}

View File

@ -1,22 +0,0 @@
namespace VpSharp.EventData;
/// <summary>
/// Provides event arguments for <see cref="VirtualParadiseClient.InviteRequestReceived" />.
/// </summary>
public sealed class InviteRequestReceivedEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="InviteRequestReceivedEventArgs" /> class.
/// </summary>
/// <param name="inviteRequest">The invite request.</param>
public InviteRequestReceivedEventArgs(InviteRequest inviteRequest)
{
InviteRequest = inviteRequest;
}
/// <summary>
/// Gets the invite request.
/// </summary>
/// <value>The invite request.</value>
public InviteRequest InviteRequest { get; }
}

View File

@ -1,22 +0,0 @@
namespace VpSharp.EventData;
/// <summary>
/// Provides event arguments for <see cref="VirtualParadiseClient.JoinRequestReceived" />.
/// </summary>
public sealed class JoinRequestReceivedEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="JoinRequestReceivedEventArgs" /> class.
/// </summary>
/// <param name="joinRequest">The join request.</param>
public JoinRequestReceivedEventArgs(JoinRequest joinRequest)
{
JoinRequest = joinRequest;
}
/// <summary>
/// Gets the join request.
/// </summary>
/// <value>The join request.</value>
public JoinRequest JoinRequest { get; }
}

View File

@ -1,24 +0,0 @@
using VpSharp.Entities;
namespace VpSharp.EventData;
/// <summary>
/// Provides event arguments for <see cref="VirtualParadiseClient.MessageReceived" />.
/// </summary>
public sealed class MessageReceivedEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="MessageReceivedEventArgs" /> class.
/// </summary>
/// <param name="message">The received message.</param>
public MessageReceivedEventArgs(VirtualParadiseMessage message)
{
Message = message;
}
/// <summary>
/// Gets the message.
/// </summary>
/// <value>The message.</value>
public VirtualParadiseMessage Message { get; }
}

View File

@ -0,0 +1,21 @@
using System.Reactive.Linq;
namespace VpSharp.Extensions;
/// <summary>
/// Reactive extensions for <see cref="IObservable{T}" />.
/// </summary>
public static class ObservableExtensions
{
/// <summary>
/// Provides a way to subscribe to an observable using an asynchronous on-next handler.
/// </summary>
/// <param name="source">The source observable.</param>
/// <param name="onNextAsync">The asynchronous on-next handler.</param>
/// <typeparam name="T">The type of the observable.</typeparam>
/// <returns>A disposable object used to unsubscribe from the observable.</returns>
public static IDisposable SubscribeAsync<T>(this IObservable<T> source, Func<T, Task> onNextAsync)
{
return source.Select(arg => Observable.FromAsync(_ => onNextAsync(arg))).Concat().Subscribe();
}
}

View File

@ -1,97 +1,163 @@
using VpSharp.EventData;
using System.Reactive.Subjects;
using VpSharp.Entities;
using VpSharp.EventData;
namespace VpSharp;
public sealed partial class VirtualParadiseClient
{
private readonly Subject<AvatarClickedEventArgs> _avatarClicked = new();
private readonly Subject<VirtualParadiseAvatar> _avatarJoined = new();
private readonly Subject<VirtualParadiseAvatar> _avatarLeft = new();
private readonly Subject<AvatarMovedEventArgs> _avatarMoved = new();
private readonly Subject<AvatarTypeChangedEventArgs> _avatarTypeChanged = new();
private readonly Subject<InviteRequest> _inviteRequestReceived = new();
private readonly Subject<JoinRequest> _joinRequestReceived = new();
private readonly Subject<VirtualParadiseMessage> _messageReceived = new();
private readonly Subject<ObjectBumpEventArgs> _objectBump = new();
private readonly Subject<ObjectChangedEventArgs> _objectChanged = new();
private readonly Subject<ObjectClickedEventArgs> _objectClicked = new();
private readonly Subject<ObjectCreatedEventArgs> _objectCreated = new();
private readonly Subject<ObjectDeletedEventArgs> _objectDeleted = new();
private readonly Subject<TeleportedEventArgs> _teleported = new();
private readonly Subject<DisconnectReason> _universeServerDisconnected = new();
private readonly Subject<UriReceivedEventArgs> _uriReceived = new();
private readonly Subject<DisconnectReason> _worldServerDisconnected = new();
/// <summary>
/// Occurs when an avatar has clicked another avatar.
/// </summary>
public event EventHandler<AvatarClickedEventArgs>? AvatarClicked;
public IObservable<AvatarClickedEventArgs> AvatarClicked
{
get => _avatarClicked;
}
/// <summary>
/// Occurs when an avatar has entered the vicinity of the client.
/// </summary>
public event EventHandler<AvatarJoinedEventArgs>? AvatarJoined;
public IObservable<VirtualParadiseAvatar> AvatarJoined
{
get => _avatarJoined;
}
/// <summary>
/// Occurs when an avatar has left the vicinity of the client.
/// </summary>
public event EventHandler<AvatarLeftEventArgs>? AvatarLeft;
public IObservable<VirtualParadiseAvatar> AvatarLeft
{
get => _avatarLeft;
}
/// <summary>
/// Occurs when an avatar has changed their position or rotation.
/// </summary>
public event EventHandler<AvatarMovedEventArgs>? AvatarMoved;
public IObservable<AvatarMovedEventArgs> AvatarMoved
{
get => _avatarMoved;
}
/// <summary>
/// Occurs when an avatar has changed their type.
/// </summary>
public event EventHandler<AvatarTypeChangedEventArgs>? AvatarTypeChanged;
public IObservable<AvatarTypeChangedEventArgs> AvatarTypeChanged
{
get => _avatarTypeChanged;
}
/// <summary>
/// Occurs when an invite request has been received.
/// </summary>
public event EventHandler<InviteRequestReceivedEventArgs>? InviteRequestReceived;
public IObservable<InviteRequest> InviteRequestReceived
{
get => _inviteRequestReceived;
}
/// <summary>
/// Occurs when a join request has been received.
/// </summary>
public event EventHandler<JoinRequestReceivedEventArgs>? JoinRequestReceived;
public IObservable<JoinRequest> JoinRequestReceived
{
get => _joinRequestReceived;
}
/// <summary>
/// Occurs when a chat message or console message has been received.
/// </summary>
public event EventHandler<MessageReceivedEventArgs>? MessageReceived;
public IObservable<VirtualParadiseMessage> MessageReceived
{
get => _messageReceived;
}
/// <summary>
/// Occurs when a bump phase has changed for an object.
/// </summary>
public event EventHandler<ObjectBumpEventArgs>? ObjectBump;
public IObservable<ObjectBumpEventArgs> ObjectBump
{
get => _objectBump;
}
/// <summary>
/// Occurs when an object has been changed.
/// </summary>
public event EventHandler<ObjectChangedEventArgs>? ObjectChanged;
public IObservable<ObjectChangedEventArgs> ObjectChanged
{
get => _objectChanged;
}
/// <summary>
/// Occurs when an avatar has clicked an object.
/// </summary>
public event EventHandler<ObjectClickedEventArgs>? ObjectClicked;
public IObservable<ObjectClickedEventArgs> ObjectClicked
{
get => _objectClicked;
}
/// <summary>
/// Occurs when an object has been created.
/// </summary>
public event EventHandler<ObjectCreatedEventArgs>? ObjectCreated;
public IObservable<ObjectCreatedEventArgs> ObjectCreated
{
get => _objectCreated;
}
/// <summary>
/// Occurs when an object has been deleted.
/// </summary>
public event EventHandler<ObjectDeletedEventArgs>? ObjectDeleted;
public IObservable<ObjectDeletedEventArgs> ObjectDeleted
{
get => _objectDeleted;
}
/// <summary>
/// Occurs when an avatar has requested this client to teleport.
/// </summary>
public event EventHandler<TeleportedEventArgs>? Teleported;
public IObservable<TeleportedEventArgs> Teleported
{
get => _teleported;
}
/// <summary>
/// Occurs when the client has been disconnected from the universe server.
/// </summary>
public event EventHandler<DisconnectedEventArgs>? UniverseServerDisconnected;
public IObservable<DisconnectReason> UniverseServerDisconnected
{
get => _universeServerDisconnected;
}
/// <summary>
/// Occurs when an avatar has sent a URI to this client.
/// </summary>
public event EventHandler<UriReceivedEventArgs>? UriReceived;
public IObservable<UriReceivedEventArgs> UriReceived
{
get => _uriReceived;
}
/// <summary>
/// Occurs when the client has been disconnected from the world server.
/// </summary>
public event EventHandler<DisconnectedEventArgs>? WorldServerDisconnected;
private void RaiseEvent<T>(EventHandler<T>? eventHandler, T args)
where T : EventArgs
public IObservable<DisconnectReason> WorldServerDisconnected
{
eventHandler?.Invoke(this, args);
get => _worldServerDisconnected;
}
}

View File

@ -74,16 +74,15 @@ public sealed partial class VirtualParadiseClient
style = (FontStyle)vp_int(sender, IntegerAttribute.ChatEffects);
}
VirtualParadiseAvatar? avatar = GetAvatar(session);
VirtualParadiseAvatar avatar = GetAvatar(session)!;
message = new VirtualParadiseMessage((MessageType)type, name, content, avatar, style, color);
}
var args = new MessageReceivedEventArgs(message);
RaiseEvent(MessageReceived, args);
_messageReceived.OnNext(message);
foreach (VirtualParadiseClientExtension extension in _extensions)
{
extension.OnMessageReceived(args);
extension.OnMessageReceived(message);
}
}
@ -92,9 +91,7 @@ public sealed partial class VirtualParadiseClient
VirtualParadiseAvatar avatar = ExtractAvatar(sender);
avatar = AddOrUpdateAvatar(avatar);
avatar.User = await GetUserAsync(vp_int(sender, IntegerAttribute.UserId)).ConfigureAwait(false);
var args = new AvatarJoinedEventArgs(avatar);
RaiseEvent(AvatarJoined, args);
_avatarJoined.OnNext(avatar);
}
private void OnAvatarChangeNativeEvent(nint sender)
@ -119,14 +116,14 @@ public sealed partial class VirtualParadiseClient
rotation = Rotation.CreateFromTiltYawRoll(pitch, yaw, 0);
}
VirtualParadiseAvatar? avatar = GetAvatar(session);
VirtualParadiseAvatar avatar = GetAvatar(session)!;
if (type != avatar.Type)
{
int oldType = avatar.Type;
avatar.Type = type;
var args = new AvatarTypeChangedEventArgs(avatar, type, oldType);
RaiseEvent(AvatarTypeChanged, args);
_avatarTypeChanged.OnNext(args);
}
Location oldLocation = avatar.Location;
@ -136,7 +133,7 @@ public sealed partial class VirtualParadiseClient
if (position != oldLocation.Position || rotation != oldLocation.Rotation)
{
var args = new AvatarMovedEventArgs(avatar, newLocation, oldLocation);
RaiseEvent(AvatarMoved, args);
_avatarMoved.OnNext(args);
}
}
@ -149,11 +146,9 @@ public sealed partial class VirtualParadiseClient
session = vp_int(sender, IntegerAttribute.AvatarSession);
}
VirtualParadiseAvatar? avatar = GetAvatar(session);
VirtualParadiseAvatar avatar = GetAvatar(session)!;
_avatars.TryRemove(session, out VirtualParadiseAvatar _);
var args = new AvatarLeftEventArgs(avatar);
RaiseEvent(AvatarLeft, args);
_avatarLeft.OnNext(avatar);
}
private async void OnObjectNativeEvent(nint sender)
@ -179,9 +174,9 @@ public sealed partial class VirtualParadiseClient
}
else
{
VirtualParadiseAvatar? avatar = GetAvatar(session);
VirtualParadiseAvatar avatar = GetAvatar(session)!;
var args = new ObjectCreatedEventArgs(avatar, virtualParadiseObject);
RaiseEvent(ObjectCreated, args);
_objectCreated.OnNext(args);
}
}
@ -196,7 +191,7 @@ public sealed partial class VirtualParadiseClient
session = vp_int(sender, IntegerAttribute.AvatarSession);
}
VirtualParadiseAvatar? avatar = GetAvatar(session);
VirtualParadiseAvatar avatar = GetAvatar(session)!;
VirtualParadiseObject? cachedObject = null;
if (_objects.TryGetValue(objectId, out VirtualParadiseObject? virtualParadiseObject))
@ -217,8 +212,8 @@ public sealed partial class VirtualParadiseClient
AddOrUpdateObject(virtualParadiseObject);
}
var args = new ObjectChangedEventArgs(avatar, cachedObject, virtualParadiseObject);
RaiseEvent(ObjectChanged, args);
var args = new ObjectChangedEventArgs(avatar, cachedObject, virtualParadiseObject!);
_objectChanged.OnNext(args);
}
private async void OnObjectDeleteNativeEvent(nint sender)
@ -247,7 +242,7 @@ public sealed partial class VirtualParadiseClient
_objects.TryRemove(objectId, out VirtualParadiseObject _);
var args = new ObjectDeletedEventArgs(avatar!, objectId, virtualParadiseObject!);
RaiseEvent(ObjectDeleted, args);
_objectDeleted.OnNext(args);
}
private async void OnObjectClickNativeEvent(nint sender)
@ -267,10 +262,10 @@ public sealed partial class VirtualParadiseClient
clickPoint = new Vector3d(x, y, z);
}
VirtualParadiseAvatar? avatar = GetAvatar(session);
VirtualParadiseAvatar avatar = GetAvatar(session)!;
VirtualParadiseObject virtualParadiseObject = await GetObjectAsync(objectId).ConfigureAwait(false);
var args = new ObjectClickedEventArgs(avatar, virtualParadiseObject, clickPoint);
RaiseEvent(ObjectClicked, args);
_objectClicked.OnNext(args);
}
private async void OnWorldListNativeEvent(nint sender)
@ -343,8 +338,7 @@ public sealed partial class VirtualParadiseClient
reason = (DisconnectReason)vp_int(sender, IntegerAttribute.DisconnectErrorCode);
}
var args = new DisconnectedEventArgs(reason);
RaiseEvent(WorldServerDisconnected, args);
_worldServerDisconnected.OnNext(reason);
}
private void OnUniverseDisconnectNativeEvent(nint sender)
@ -355,8 +349,7 @@ public sealed partial class VirtualParadiseClient
reason = (DisconnectReason)vp_int(sender, IntegerAttribute.DisconnectErrorCode);
}
var args = new DisconnectedEventArgs(reason);
RaiseEvent(UniverseServerDisconnected, args);
_universeServerDisconnected.OnNext(reason);
}
private void OnUserAttributesNativeEvent(nint sender)
@ -424,10 +417,10 @@ public sealed partial class VirtualParadiseClient
clickPoint = new Vector3d(x, y, z);
}
VirtualParadiseAvatar? avatar = GetAvatar(session);
VirtualParadiseAvatar? clickedAvatar = GetAvatar(clickedSession);
VirtualParadiseAvatar avatar = GetAvatar(session)!;
VirtualParadiseAvatar clickedAvatar = GetAvatar(clickedSession)!;
var args = new AvatarClickedEventArgs(avatar, clickedAvatar, clickPoint);
RaiseEvent(AvatarClicked, args);
_avatarClicked.OnNext(args);
}
private async void OnTeleportNativeEvent(nint sender)
@ -453,14 +446,14 @@ public sealed partial class VirtualParadiseClient
worldName = vp_string(sender, StringAttribute.TeleportWorld);
}
VirtualParadiseWorld? world = string.IsNullOrWhiteSpace(worldName)
VirtualParadiseWorld world = (string.IsNullOrWhiteSpace(worldName)
? CurrentWorld
: await GetWorldAsync(worldName).ConfigureAwait(false);
: await GetWorldAsync(worldName).ConfigureAwait(false))!;
var location = new Location(world, position, rotation);
VirtualParadiseAvatar? avatar = GetAvatar(session);
VirtualParadiseAvatar avatar = GetAvatar(session)!;
var args = new TeleportedEventArgs(avatar, location);
RaiseEvent(Teleported, args);
_teleported.OnNext(args);
}
private async void OnObjectBumpEndNativeEvent(nint sender)
@ -474,11 +467,11 @@ public sealed partial class VirtualParadiseClient
objectId = vp_int(sender, IntegerAttribute.ObjectId);
}
VirtualParadiseAvatar? avatar = GetAvatar(session);
VirtualParadiseAvatar avatar = GetAvatar(session)!;
var vpObject = await GetObjectAsync(objectId).ConfigureAwait(false);
var args = new ObjectBumpEventArgs(avatar, vpObject, BumpPhase.End);
RaiseEvent(ObjectBump, args);
_objectBump.OnNext(args);
}
private void OnUrlNativeEvent(nint sender)
@ -499,10 +492,10 @@ public sealed partial class VirtualParadiseClient
return;
}
VirtualParadiseAvatar? avatar = GetAvatar(session);
VirtualParadiseAvatar avatar = GetAvatar(session)!;
var uri = new Uri(url);
var args = new UriReceivedEventArgs(uri, target, avatar);
RaiseEvent(UriReceived, args);
_uriReceived.OnNext(args);
}
private async void OnObjectBumpBeginNativeEvent(nint sender)
@ -516,11 +509,11 @@ public sealed partial class VirtualParadiseClient
objectId = vp_int(sender, IntegerAttribute.ObjectId);
}
VirtualParadiseAvatar? avatar = GetAvatar(session);
VirtualParadiseAvatar avatar = GetAvatar(session)!;
var vpObject = await GetObjectAsync(objectId).ConfigureAwait(false);
var args = new ObjectBumpEventArgs(avatar, vpObject, BumpPhase.Begin);
RaiseEvent(ObjectBump, args);
_objectBump.OnNext(args);
}
private async void OnJoinNativeEvent(nint sender)
@ -538,8 +531,7 @@ public sealed partial class VirtualParadiseClient
VirtualParadiseUser user = await GetUserAsync(userId).ConfigureAwait(false);
var joinRequest = new JoinRequest(this, requestId, name, user);
var args = new JoinRequestReceivedEventArgs(joinRequest);
RaiseEvent(JoinRequestReceived, args);
_joinRequestReceived.OnNext(joinRequest);
}
private async void OnInviteNativeEvent(nint sender)
@ -570,12 +562,11 @@ public sealed partial class VirtualParadiseClient
worldName = vp_string(sender, StringAttribute.InviteWorld);
}
VirtualParadiseWorld? world = await GetWorldAsync(worldName).ConfigureAwait(false);
VirtualParadiseWorld world = (await GetWorldAsync(worldName).ConfigureAwait(false))!;
VirtualParadiseUser user = await GetUserAsync(userId).ConfigureAwait(false);
var location = new Location(world, position, rotation);
var request = new InviteRequest(this, requestId, avatarName, user, location);
var args = new InviteRequestReceivedEventArgs(request);
RaiseEvent(InviteRequestReceived, args);
_inviteRequestReceived.OnNext(request);
}
}