diff --git a/Samples/VpSharp.CSharp_Sample/Program.cs b/Samples/VpSharp.CSharp_Sample/Program.cs index c889d59..3eb1778 100644 --- a/Samples/VpSharp.CSharp_Sample/Program.cs +++ b/Samples/VpSharp.CSharp_Sample/Program.cs @@ -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(); - 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}!"); - } } diff --git a/Samples/VpSharp.VB_Sample/Program.vb b/Samples/VpSharp.VB_Sample/Program.vb index c0e05e0..1255ddf 100644 --- a/Samples/VpSharp.VB_Sample/Program.vb +++ b/Samples/VpSharp.VB_Sample/Program.vb @@ -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 diff --git a/VpSharp/VpSharp.csproj b/VpSharp/VpSharp.csproj index 56dcb7a..935b680 100644 --- a/VpSharp/VpSharp.csproj +++ b/VpSharp/VpSharp.csproj @@ -59,6 +59,7 @@ + diff --git a/VpSharp/src/ClientExtensions/VirtualParadiseClientExtension.cs b/VpSharp/src/ClientExtensions/VirtualParadiseClientExtension.cs index 8b0d6ab..9cb1619 100644 --- a/VpSharp/src/ClientExtensions/VirtualParadiseClientExtension.cs +++ b/VpSharp/src/ClientExtensions/VirtualParadiseClientExtension.cs @@ -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. /// /// An object containing event data. - protected internal virtual Task OnMessageReceived(MessageReceivedEventArgs args) + protected internal virtual Task OnMessageReceived(VirtualParadiseMessage message) { return Task.CompletedTask; } diff --git a/VpSharp/src/EventData/AvatarJoinedEventArgs.cs b/VpSharp/src/EventData/AvatarJoinedEventArgs.cs deleted file mode 100644 index 5dea39c..0000000 --- a/VpSharp/src/EventData/AvatarJoinedEventArgs.cs +++ /dev/null @@ -1,24 +0,0 @@ -using VpSharp.Entities; - -namespace VpSharp.EventData; - -/// -/// Provides event arguments for . -/// -public sealed class AvatarJoinedEventArgs : EventArgs -{ - /// - /// Initializes a new instance of the class. - /// - /// The newly-joined avatar. - public AvatarJoinedEventArgs(VirtualParadiseAvatar avatar) - { - Avatar = avatar; - } - - /// - /// Gets the newly-joined avatar. - /// - /// The newly-joined avatar. - public VirtualParadiseAvatar Avatar { get; } -} diff --git a/VpSharp/src/EventData/AvatarLeftEventArgs.cs b/VpSharp/src/EventData/AvatarLeftEventArgs.cs deleted file mode 100644 index 2c0b941..0000000 --- a/VpSharp/src/EventData/AvatarLeftEventArgs.cs +++ /dev/null @@ -1,24 +0,0 @@ -using VpSharp.Entities; - -namespace VpSharp.EventData; - -/// -/// Provides event arguments for . -/// -public sealed class AvatarLeftEventArgs : EventArgs -{ - /// - /// Initializes a new instance of the class. - /// - /// The newly-departed avatar. - public AvatarLeftEventArgs(VirtualParadiseAvatar avatar) - { - Avatar = avatar; - } - - /// - /// Gets the newly-departed avatar. - /// - /// The newly-departed avatar. - public VirtualParadiseAvatar Avatar { get; } -} diff --git a/VpSharp/src/EventData/InviteRequestReceivedEventArgs.cs b/VpSharp/src/EventData/InviteRequestReceivedEventArgs.cs deleted file mode 100644 index ea7295e..0000000 --- a/VpSharp/src/EventData/InviteRequestReceivedEventArgs.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace VpSharp.EventData; - -/// -/// Provides event arguments for . -/// -public sealed class InviteRequestReceivedEventArgs : EventArgs -{ - /// - /// Initializes a new instance of the class. - /// - /// The invite request. - public InviteRequestReceivedEventArgs(InviteRequest inviteRequest) - { - InviteRequest = inviteRequest; - } - - /// - /// Gets the invite request. - /// - /// The invite request. - public InviteRequest InviteRequest { get; } -} diff --git a/VpSharp/src/EventData/JoinRequestReceivedEventArgs.cs b/VpSharp/src/EventData/JoinRequestReceivedEventArgs.cs deleted file mode 100644 index f913028..0000000 --- a/VpSharp/src/EventData/JoinRequestReceivedEventArgs.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace VpSharp.EventData; - -/// -/// Provides event arguments for . -/// -public sealed class JoinRequestReceivedEventArgs : EventArgs -{ - /// - /// Initializes a new instance of the class. - /// - /// The join request. - public JoinRequestReceivedEventArgs(JoinRequest joinRequest) - { - JoinRequest = joinRequest; - } - - /// - /// Gets the join request. - /// - /// The join request. - public JoinRequest JoinRequest { get; } -} diff --git a/VpSharp/src/EventData/MessageReceivedEventArgs.cs b/VpSharp/src/EventData/MessageReceivedEventArgs.cs deleted file mode 100644 index f9d4035..0000000 --- a/VpSharp/src/EventData/MessageReceivedEventArgs.cs +++ /dev/null @@ -1,24 +0,0 @@ -using VpSharp.Entities; - -namespace VpSharp.EventData; - -/// -/// Provides event arguments for . -/// -public sealed class MessageReceivedEventArgs : EventArgs -{ - /// - /// Initializes a new instance of the class. - /// - /// The received message. - public MessageReceivedEventArgs(VirtualParadiseMessage message) - { - Message = message; - } - - /// - /// Gets the message. - /// - /// The message. - public VirtualParadiseMessage Message { get; } -} diff --git a/VpSharp/src/Extensions/ObservableExtensions.cs b/VpSharp/src/Extensions/ObservableExtensions.cs new file mode 100644 index 0000000..4a54d24 --- /dev/null +++ b/VpSharp/src/Extensions/ObservableExtensions.cs @@ -0,0 +1,21 @@ +using System.Reactive.Linq; + +namespace VpSharp.Extensions; + +/// +/// Reactive extensions for . +/// +public static class ObservableExtensions +{ + /// + /// Provides a way to subscribe to an observable using an asynchronous on-next handler. + /// + /// The source observable. + /// The asynchronous on-next handler. + /// The type of the observable. + /// A disposable object used to unsubscribe from the observable. + public static IDisposable SubscribeAsync(this IObservable source, Func onNextAsync) + { + return source.Select(arg => Observable.FromAsync(_ => onNextAsync(arg))).Concat().Subscribe(); + } +} diff --git a/VpSharp/src/VirtualParadiseClient.Events.cs b/VpSharp/src/VirtualParadiseClient.Events.cs index 65c2b91..6e3f2ea 100644 --- a/VpSharp/src/VirtualParadiseClient.Events.cs +++ b/VpSharp/src/VirtualParadiseClient.Events.cs @@ -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 _avatarClicked = new(); + private readonly Subject _avatarJoined = new(); + private readonly Subject _avatarLeft = new(); + private readonly Subject _avatarMoved = new(); + private readonly Subject _avatarTypeChanged = new(); + private readonly Subject _inviteRequestReceived = new(); + private readonly Subject _joinRequestReceived = new(); + private readonly Subject _messageReceived = new(); + private readonly Subject _objectBump = new(); + private readonly Subject _objectChanged = new(); + private readonly Subject _objectClicked = new(); + private readonly Subject _objectCreated = new(); + private readonly Subject _objectDeleted = new(); + private readonly Subject _teleported = new(); + private readonly Subject _universeServerDisconnected = new(); + private readonly Subject _uriReceived = new(); + private readonly Subject _worldServerDisconnected = new(); + /// /// Occurs when an avatar has clicked another avatar. /// - public event EventHandler? AvatarClicked; + public IObservable AvatarClicked + { + get => _avatarClicked; + } /// /// Occurs when an avatar has entered the vicinity of the client. /// - public event EventHandler? AvatarJoined; + public IObservable AvatarJoined + { + get => _avatarJoined; + } /// /// Occurs when an avatar has left the vicinity of the client. /// - public event EventHandler? AvatarLeft; + public IObservable AvatarLeft + { + get => _avatarLeft; + } /// /// Occurs when an avatar has changed their position or rotation. /// - public event EventHandler? AvatarMoved; + public IObservable AvatarMoved + { + get => _avatarMoved; + } /// /// Occurs when an avatar has changed their type. /// - public event EventHandler? AvatarTypeChanged; + public IObservable AvatarTypeChanged + { + get => _avatarTypeChanged; + } /// /// Occurs when an invite request has been received. /// - public event EventHandler? InviteRequestReceived; + public IObservable InviteRequestReceived + { + get => _inviteRequestReceived; + } /// /// Occurs when a join request has been received. /// - public event EventHandler? JoinRequestReceived; + public IObservable JoinRequestReceived + { + get => _joinRequestReceived; + } /// /// Occurs when a chat message or console message has been received. /// - public event EventHandler? MessageReceived; + public IObservable MessageReceived + { + get => _messageReceived; + } /// /// Occurs when a bump phase has changed for an object. /// - public event EventHandler? ObjectBump; + public IObservable ObjectBump + { + get => _objectBump; + } /// /// Occurs when an object has been changed. /// - public event EventHandler? ObjectChanged; + public IObservable ObjectChanged + { + get => _objectChanged; + } /// /// Occurs when an avatar has clicked an object. /// - public event EventHandler? ObjectClicked; + public IObservable ObjectClicked + { + get => _objectClicked; + } /// /// Occurs when an object has been created. /// - public event EventHandler? ObjectCreated; + public IObservable ObjectCreated + { + get => _objectCreated; + } /// /// Occurs when an object has been deleted. /// - public event EventHandler? ObjectDeleted; + public IObservable ObjectDeleted + { + get => _objectDeleted; + } /// /// Occurs when an avatar has requested this client to teleport. /// - public event EventHandler? Teleported; + public IObservable Teleported + { + get => _teleported; + } /// /// Occurs when the client has been disconnected from the universe server. /// - public event EventHandler? UniverseServerDisconnected; + public IObservable UniverseServerDisconnected + { + get => _universeServerDisconnected; + } /// /// Occurs when an avatar has sent a URI to this client. /// - public event EventHandler? UriReceived; + public IObservable UriReceived + { + get => _uriReceived; + } + /// /// Occurs when the client has been disconnected from the world server. /// - public event EventHandler? WorldServerDisconnected; - - private void RaiseEvent(EventHandler? eventHandler, T args) - where T : EventArgs + public IObservable WorldServerDisconnected { - eventHandler?.Invoke(this, args); + get => _worldServerDisconnected; } } diff --git a/VpSharp/src/VirtualParadiseClient.NativeEvents.cs b/VpSharp/src/VirtualParadiseClient.NativeEvents.cs index efc77c2..aa60496 100644 --- a/VpSharp/src/VirtualParadiseClient.NativeEvents.cs +++ b/VpSharp/src/VirtualParadiseClient.NativeEvents.cs @@ -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); } }