Constructor field deserialization test on two domains added.

--- Tests/TwoDomainTests.cs
+++ Tests/TwoDomainTests.cs
@@ -24,7 +24,6 @@
 // *******************************************************************
 using System;
 using NUnit.Framework;
-using System.IO;

 namespace Antmicro.Migrant.Tests
 {
@@ -170,6 +169,22 @@
             Assert.AreEqual(100, testsOnDomain2.GetValueOnAppDomain("C", "f"));
             Assert.AreEqual(200, testsOnDomain2.GetValueOnAppDomain("B", "f"));
         }
+
+        [Test]
+        public void ShouldDeserializeConstructorFields()
+        {
+            var type1 = DynamicClass.Create("A").WithConstructorField<object>("f");
+            var type2 = DynamicClass.Create("A").WithConstructorField<object>("f");
+
+            testsOnDomain1.CreateInstanceOnAppDomain(type1);
+            testsOnDomain1.SetValueOnAppDomain("f", new Object());
+
+            var bytes = testsOnDomain1.SerializeOnAppDomain();
+            testsOnDomain2.CreateInstanceOnAppDomain(type2);
+            testsOnDomain2.DeserializeOnAppDomain(bytes, GetSettings());
+
+            Assert.IsNotNull(testsOnDomain2.GetValueOnAppDomain("f"));
+        }
     }
 }


Change isolation storage parameters for windows GL application.  If not then we get "unable to determine application identity of the caller."

--- cocos2d/support/CCUserDefault.cs
+++ cocos2d/support/CCUserDefault.cs
@@ -151,8 +151,13 @@
     		}

     #else
+#if !WINDOWSGL
             myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication();
-
+#else
+            myIsolatedStorage = IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
+                                IsolatedStorageScope.Domain |
+                                IsolatedStorageScope.Assembly, null, null);
+#endif
     		// only create xml file once if it doesnt exist
     		if ((!isXMLFileExist())) {
     			createXMLFile();


[Modify] Edit it

[Modify] Edit it

--- websocket-sharp/Server/HttpRequestEventArgs.cs
+++ websocket-sharp/Server/HttpRequestEventArgs.cs
@@ -81,7 +81,7 @@
     }

     /// <summary>
-    /// Gets the HTTP response data used to return a response to the client.
+    /// Gets the HTTP response data to return to the client.
     /// </summary>
     /// <value>
     /// A <see cref="HttpListenerResponse"/> that represents the response data.


Added drag and drop file uploads

Added drag and drop file uploads

--- Toxy/MainWindow.xaml.cs
+++ Toxy/MainWindow.xaml.cs
@@ -144,8 +144,33 @@
             SetStatus(null);
             InitFriends();

+            TextToSend.AddHandler(DragOverEvent, new DragEventHandler(RichTextBox_DragOver), true);
+            TextToSend.AddHandler(DropEvent, new DragEventHandler(RichTextBox_Drop), true);
+
             if (tox.GetFriendlistCount() > 0)
                 this.ViewModel.SelectedChatObject = this.ViewModel.ChatCollection.OfType<IFriendObject>().FirstOrDefault();
+        }
+
+        private void RichTextBox_Drop(object sender, DragEventArgs e)
+        {
+            if (e.Data.GetDataPresent(DataFormats.FileDrop))
+            {
+                var docPath = (string[])e.Data.GetData(DataFormats.FileDrop);
+                SendFile(this.ViewModel.SelectedChatNumber, docPath[0]);
+            }
+        }
+
+        private void RichTextBox_DragOver(object sender, DragEventArgs e)
+        {
+            if (e.Data.GetDataPresent(DataFormats.FileDrop))
+            {
+                e.Effects = DragDropEffects.All;
+            }
+            else
+            {
+                e.Effects = DragDropEffects.None;
+            }
+            e.Handled = false;
         }

         private void loadTox()
@@ -1725,14 +1750,20 @@
                 return;

             string filename = dialog.FileName;
+
+            SendFile(selectedChatNumber, filename);
+        }
+
+        private void SendFile(int chatNumber, string filename)
+        {
             FileInfo info = new FileInfo(filename);
-            int filenumber = tox.NewFileSender(selectedChatNumber, (ulong)info.Length, filename.Split('\\').Last<string>());
+            int filenumber = tox.NewFileSender(chatNumber, (ulong)info.Length, filename.Split('\\').Last<string>());

             if (filenumber == -1)
                 return;

-            FileTransfer ft = convdic[selectedChatNumber].AddNewFileTransfer(tox, selectedChatNumber, filenumber, filename, (ulong)info.Length, true);
-            ft.Control.SetStatus(string.Format("Waiting for {0} to accept...", tox.GetName(selectedChatNumber)));
+            FileTransfer ft = convdic[chatNumber].AddNewFileTransfer(tox, chatNumber, filenumber, filename, (ulong)info.Length, true);
+            ft.Control.SetStatus(string.Format("Waiting for {0} to accept...", tox.GetName(chatNumber)));
             ft.Control.AcceptButton.Visibility = Visibility.Collapsed;
             ft.Control.DeclineButton.Visibility = Visibility.Visible;


Quick and dirty fix for opening finished transfers

--- Toxy/MainWindow.xaml.cs
+++ Toxy/MainWindow.xaml.cs
@@ -543,6 +543,7 @@
                 if (dialog.ShowDialog() == true) //guess what, this bool is nullable
                 {
                     transfer.Stream = new FileStream(dialog.FileName, FileMode.Create);
+                    transfer.FileName = dialog.FileName;
                     tox.FileSendControl(friendnumber, 1, filenumber, ToxFileControl.ACCEPT, new byte[0]);
                 }
             };
@@ -570,8 +571,7 @@
             };

             transfer.Control.OnFolderOpen += delegate() {
-                string filePath = Path.Combine(Environment.CurrentDirectory, filename);
-                Process.Start("explorer.exe", @"/select, " + filePath);
+                Process.Start("explorer.exe", @"/select, " + transfer.FileName);
             };

             transfers.Add(transfer);


#62: add a link to older logs

#62: add a link to older logs

--- Views/Home/Index.cshtml
+++ Views/Home/Index.cshtml
@@ -2,3 +2,5 @@

And here're today's conference logs:

+
+Older logs (mostly actual in period 2008-08-22 — 2016-12-09)


Added breakpoint creator.

Added breakpoint creator.

--- NtApiDotNet/Win32/Rpc/CodeGenUtils.cs
+++ NtApiDotNet/Win32/Rpc/CodeGenUtils.cs
@@ -780,9 +780,24 @@
             method.Statements.Add(new CodeThrowExceptionStatement(new CodeObjectCreateExpression(exception_type.ToRef(), args.Select(o => GetPrimitive(o)).ToArray())));
         }

+        public static CodeMethodInvokeExpression GetStaticMethod(Type type, string name, params CodeExpression[] ps)
+        {
+            return new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(type), name, ps);
+        }
+
+        public static CodeMethodInvokeExpression GetStaticMethod(CodeTypeReference type, string name, params CodeExpression[] ps)
+        {
+            return new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(type), name, ps);
+        }
+
         public static CodeExpression DeRef(this CodeExpression expr)
         {
-            return new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(typeof(RpcUtils)), nameof(RpcUtils.DeRef), expr);
+            return GetStaticMethod(typeof(RpcUtils), nameof(RpcUtils.DeRef), expr);
+        }
+
+        public static void AddBreakpoint(this CodeMemberMethod method)
+        {
+            method.Statements.Add(GetStaticMethod(typeof(System.Diagnostics.Debugger), nameof(System.Diagnostics.Debugger.Break)));
         }
     }
 }


Fix stuff

Fix stuff

--- OAuthCallbackHttpTrigger/endpoint.csx
+++ OAuthCallbackHttpTrigger/endpoint.csx
@@ -1,4 +1,4 @@
-#r Newtonsoft.Json
+#r "Newtonsoft.Json"

 using Newtonsoft.Json.Linq;
 using System.Configuration;
@@ -26,7 +26,7 @@
         tmpl.Append("
");
         tmpl.AppendFormat("", authData.username);
         tmpl.Append("
");
-        tmpl.AppendFormat("You're logged in as {1}.", authData.username, authData.username)
+        tmpl.AppendFormat("You're logged in as {1}.", authData.username, authData.username);
         tmpl.Append("
");
     }
     catch (System.Exception)


Bump version

Bump version

--- OpenParser/Properties/AssemblyInfo.cs
+++ OpenParser/Properties/AssemblyInfo.cs
@@ -32,5 +32,5 @@
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.3.0")]
-[assembly: AssemblyFileVersion("1.0.3.0")]
+[assembly: AssemblyVersion("1.0.4.0")]
+[assembly: AssemblyFileVersion("1.0.4.0")]


Thank you, Strawberry Fride, for a patch that brings MSSQL even with MySQL

--- OpenSim/Data/MSSQL/MSSQLRegionData.cs
+++ OpenSim/Data/MSSQL/MSSQLRegionData.cs
@@ -157,11 +157,11 @@

             //After this we have a empty fully configured DataSet.
         }
-
+
         /// <summary>
         /// Dispose the database
         /// </summary>
-        public void Dispose() {}
+        public void Dispose() { }

         /// <summary>
         /// Loads the objects present in the region.
@@ -344,35 +344,34 @@
                     && (prim.GetEffectiveObjectFlags() & (uint)PrimFlags.Temporary) == 0
                     && (prim.GetEffectiveObjectFlags() & (uint)PrimFlags.TemporaryOnRez) == 0)
                 {
-                    lock (_PrimsDataSet)
+
+                    DataTable prims = _PrimsDataSet.Tables["prims"];
+                    DataTable shapes = _PrimsDataSet.Tables["primshapes"];
+
+                    DataRow primRow = prims.Rows.Find(prim.UUID.ToString());
+                    if (primRow == null)
                     {
-                        DataTable prims = _PrimsDataSet.Tables["prims"];
-                        DataTable shapes = _PrimsDataSet.Tables["primshapes"];
-
-                        DataRow primRow = prims.Rows.Find(prim.UUID.ToString());
-                        if (primRow == null)
-                        {
-                            primRow = prims.NewRow();
-                            fillPrimRow(primRow, prim, obj.UUID, regionUUID);
-                            prims.Rows.Add(primRow);
-                        }
-                        else
-                        {
-                            fillPrimRow(primRow, prim, obj.UUID, regionUUID);
-                        }
-
-                        DataRow shapeRow = shapes.Rows.Find(prim.UUID.ToString());
-                        if (shapeRow == null)
-                        {
-                            shapeRow = shapes.NewRow();
-                            fillShapeRow(shapeRow, prim);
-                            shapes.Rows.Add(shapeRow);
-                        }
-                        else
-                        {
-                            fillShapeRow(shapeRow, prim);
-                        }
+                        primRow = prims.NewRow();
+                        fillPrimRow(primRow, prim, obj.UUID, regionUUID);
+                        prims.Rows.Add(primRow);
                     }
+                    else
+                    {
+                        fillPrimRow(primRow, prim, obj.UUID, regionUUID);
+                    }
+
+                    DataRow shapeRow = shapes.Rows.Find(prim.UUID.ToString());
+                    if (shapeRow == null)
+                    {
+                        shapeRow = shapes.NewRow();
+                        fillShapeRow(shapeRow, prim);
+                        shapes.Rows.Add(shapeRow);
+                    }
+                    else
+                    {
+                        fillShapeRow(shapeRow, prim);
+                    }
+
                 }
             }

@@ -582,9 +581,9 @@

             //Insert new values
             string sql = @"INSERT INTO [land]
-([UUID],[RegionUUID],[LocalLandID],[Bitmap],[Name],[Description],[OwnerUUID],[IsGroupOwned],[Area],[AuctionID],[Category],[ClaimDate],[ClaimPrice],[GroupUUID],[SalePrice],[LandStatus],[LandFlags],[LandingType],[MediaAutoScale],[MediaTextureUUID],[MediaURL],[MusicURL],[PassHours],[PassPrice],[SnapshotUUID],[UserLocationX],[UserLocationY],[UserLocationZ],[UserLookAtX],[UserLookAtY],[UserLookAtZ],[AuthbuyerID])
+([UUID],[RegionUUID],[LocalLandID],[Bitmap],[Name],[Description],[OwnerUUID],[IsGroupOwned],[Area],[AuctionID],[Category],[ClaimDate],[ClaimPrice],[GroupUUID],[SalePrice],[LandStatus],[LandFlags],[LandingType],[MediaAutoScale],[MediaTextureUUID],[MediaURL],[MusicURL],[PassHours],[PassPrice],[SnapshotUUID],[UserLocationX],[UserLocationY],[UserLocationZ],[UserLookAtX],[UserLookAtY],[UserLookAtZ],[AuthbuyerID],[OtherCleanTime],[Dwell])
 VALUES
-(@UUID,@RegionUUID,@LocalLandID,@Bitmap,@Name,@Description,@OwnerUUID,@IsGroupOwned,@Area,@AuctionID,@Category,@ClaimDate,@ClaimPrice,@GroupUUID,@SalePrice,@LandStatus,@LandFlags,@LandingType,@MediaAutoScale,@MediaTextureUUID,@MediaURL,@MusicURL,@PassHours,@PassPrice,@SnapshotUUID,@UserLocationX,@UserLocationY,@UserLocationZ,@UserLookAtX,@UserLookAtY,@UserLookAtZ,@AuthbuyerID)";
+(@UUID,@RegionUUID,@LocalLandID,@Bitmap,@Name,@Description,@OwnerUUID,@IsGroupOwned,@Area,@AuctionID,@Category,@ClaimDate,@ClaimPrice,@GroupUUID,@SalePrice,@LandStatus,@LandFlags,@LandingType,@MediaAutoScale,@MediaTextureUUID,@MediaURL,@MusicURL,@PassHours,@PassPrice,@SnapshotUUID,@UserLocationX,@UserLocationY,@UserLocationZ,@UserLookAtX,@UserLookAtY,@UserLookAtZ,@AuthbuyerID,@OtherCleanTime,@Dwell)";

             using (AutoClosingSqlCommand cmd = _Database.Query(sql))
             {
@@ -881,6 +880,9 @@
             if (UUID.TryParse((string)row["SnapshotUUID"], out snapshotID))
                 newData.SnapshotID = snapshotID;

+            newData.OtherCleanTime = Convert.ToInt32(row["OtherCleanTime"]);
+            newData.Dwell = Convert.ToInt32(row["Dwell"]);
+
             try
             {
                 newData.UserLocation =
@@ -1226,7 +1228,7 @@
         /// <returns></returns>
         private SqlParameter[] CreateLandParameters(LandData land, UUID regionUUID)
         {
-            SqlParameter[] parameters = new SqlParameter[32];
+            SqlParameter[] parameters = new SqlParameter[34];

             parameters[0] = _Database.CreateParameter("UUID", land.GlobalID);
             parameters[1] = _Database.CreateParameter("RegionUUID", regionUUID);
@@ -1263,6 +1265,8 @@
             parameters[29] = _Database.CreateParameter("UserLookAtY", land.UserLookAt.Y);
             parameters[30] = _Database.CreateParameter("UserLookAtZ", land.UserLookAt.Z);
             parameters[31] = _Database.CreateParameter("AuthBuyerID", land.AuthBuyerID);
+            parameters[32] = _Database.CreateParameter("OtherCleanTime", land.OtherCleanTime);
+            parameters[33] = _Database.CreateParameter("Dwell", land.Dwell);

             return parameters;
         }


Fix memory leak. Dispose in recreating instances.

--- src/QuikSharp/QuikService.cs
+++ src/QuikSharp/QuikService.cs
@@ -151,7 +151,7 @@
             try
             {
                 // here all tasks must exit gracefully
-                var isCleanExit = Task.WaitAll(new[] {_requestTask, _responseTask, _callbackReceiverTask}, 5000);
+                var isCleanExit = Task.WaitAll(new[] {_requestTask, _responseTask, _callbackReceiverTask, _callbackInvokerTask}, 5000);
                 Trace.Assert(isCleanExit, "All tasks must finish gracefully after cancellation token is cancelled!");
             }
             finally
@@ -173,6 +173,8 @@
         {
             if (IsStarted) return;
             IsStarted = true;
+            _cts?.Dispose();
+            _cancelRegistration.Dispose();
             _cts = new CancellationTokenSource();
             _cancelledTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
             _cancelRegistration = _cts.Token.Register(() => _cancelledTcs.TrySetResult(true));
@@ -191,51 +193,54 @@
                             Trace.WriteLine("Request/response channel connected");
                             try
                             {
-                                var stream = new NetworkStream(_responseClient.Client);
-                                var writer = new StreamWriter(stream);
-                                while (!_cts.IsCancellationRequested)
+                                using (var stream = new NetworkStream(_responseClient.Client))
+                                using (var writer = new StreamWriter(stream))
                                 {
-                                    IMessage message = null;
-                                    try
+                                    while (!_cts.IsCancellationRequested)
                                     {
-                                        // BLOCKING
-                                        message = EnvelopeQueue.Take(_cts.Token);
-                                        var request = message.ToJson();
-                                        //Trace.WriteLine("Request: " + request);
-                                        // scenario: Quik is restarted or script is stopped
-                                        // then writer must throw and we will add a message back
-                                        // then we will iterate over messages and cancel expired ones
-                                        if (!message.ValidUntil.HasValue || message.ValidUntil >= DateTime.UtcNow)
+                                        IMessage message = null;
+                                        try
                                         {
-                                            writer.WriteLine(request);
-                                            writer.Flush();
+                                            // BLOCKING
+                                            message = EnvelopeQueue.Take(_cts.Token);
+                                            var request = message.ToJson();
+                                            //Trace.WriteLine("Request: " + request);
+                                            // scenario: Quik is restarted or script is stopped
+                                            // then writer must throw and we will add a message back
+                                            // then we will iterate over messages and cancel expired ones
+                                            if (!message.ValidUntil.HasValue || message.ValidUntil >= DateTime.UtcNow)
+                                            {
+                                                writer.WriteLine(request);
+                                                writer.Flush();
+                                            }
+                                            else
+                                            {
+                                                Trace.Assert(message.Id.HasValue, "All requests must have correlation id");
+                                                Responses[message.Id.Value]
+                                                    .Key.SetException(
+                                                        new TimeoutException("ValidUntilUTC is less than current time"));
+                                                KeyValuePair<TaskCompletionSource<IMessage>, Type> tcs; // <IMessage>
+                                                Responses.TryRemove(message.Id.Value, out tcs);
+                                            }
                                         }
-                                        else
+                                        catch (OperationCanceledException)
                                         {
-                                            Trace.Assert(message.Id.HasValue, "All requests must have correlation id");
-                                            Responses[message.Id.Value]
-                                                .Key.SetException(
-                                                    new TimeoutException("ValidUntilUTC is less than current time"));
-                                            KeyValuePair<TaskCompletionSource<IMessage>, Type> tcs; // <IMessage>
-                                            Responses.TryRemove(message.Id.Value, out tcs);
+                                            // EnvelopeQueue.Take(_cts.Token) was cancelled via the token
+                                        }
+                                        catch (IOException)
+                                        {
+                                            // this catch is for unexpected and unchecked connection termination
+                                            // add back, there was an error while writing
+                                            if (message != null)
+                                            {
+                                                EnvelopeQueue.Add(message);
+                                            }
+
+                                            break;
                                         }
                                     }
-                                    catch (OperationCanceledException)
-                                    {
-                                        // EnvelopeQueue.Take(_cts.Token) was cancelled via the token
-                                    }
-                                    catch (IOException)
-                                    {
-                                        // this catch is for unexpected and unchecked connection termination
-                                        // add back, there was an error while writing
-                                        if (message != null)
-                                        {
-                                            EnvelopeQueue.Add(message);
-                                        }
-
-                                        break;
-                                    }
                                 }
+
                             }
                             catch (IOException e)
                             {
@@ -261,6 +266,7 @@
                             {
                                 _responseClient.Client.Shutdown(SocketShutdown.Both);
                                 _responseClient.Close();
+                                _responseClient.Dispose();
                                 _responseClient =
                                     null; // У нас два потока работают с одним сокетом, но только один из них должен его закрыть !
                                 Trace.WriteLine("Response channel disconnected");
@@ -285,58 +291,61 @@

                             try
                             {
-                                var stream = new NetworkStream(_responseClient.Client);
-                                var reader = new StreamReader(stream, Encoding.GetEncoding(1251)); //true
-                                while (!_cts.IsCancellationRequested)
+                                using (var stream = new NetworkStream(_responseClient.Client))
+                                using (var reader = new StreamReader(stream, Encoding.GetEncoding(1251))) //true
                                 {
-                                    var readLineTask = reader.ReadLineAsync();
-                                    var completedTask = await Task.WhenAny(readLineTask, _cancelledTcs.Task).ConfigureAwait(false);
-                                    if (completedTask == _cancelledTcs.Task || _cts.IsCancellationRequested)
+                                    while (!_cts.IsCancellationRequested)
                                     {
-                                        break;
+                                        var readLineTask = reader.ReadLineAsync();
+                                        var completedTask = await Task.WhenAny(readLineTask, _cancelledTcs.Task).ConfigureAwait(false);
+                                        if (completedTask == _cancelledTcs.Task || _cts.IsCancellationRequested)
+                                        {
+                                            break;
+                                        }
+
+                                        Trace.Assert(readLineTask.Status == TaskStatus.RanToCompletion);
+                                        var response = readLineTask.Result;
+                                        if (response == null)
+                                        {
+                                            throw new IOException("Lua returned an empty response or closed the connection");
+                                        }
+
+                                        // No IO exceptions possible for response, move its processing
+                                        // to the threadpool and wait for the next mesaage
+                                        // A new task here gives c.30% boost for full TransactionSpec echo
+
+                                        // ReSharper disable once UnusedVariable
+                                        var doNotAwaitMe = Task.Factory.StartNew(r =>
+                                        {
+                                            //var r = response;
+                                            //Trace.WriteLine("Response:" + response);
+                                            try
+                                            {
+                                                var message = (r as string).FromJson(this);
+                                                Trace.Assert(message.Id.HasValue && message.Id > 0);
+                                                // it is a response message
+                                                if (!Responses.ContainsKey(message.Id.Value))
+                                                    throw new ApplicationException("Unexpected correlation ID");
+                                                KeyValuePair<TaskCompletionSource<IMessage>, Type> tcs;
+                                                Responses.TryRemove(message.Id.Value, out tcs);
+                                                if (!message.ValidUntil.HasValue || message.ValidUntil >= DateTime.UtcNow)
+                                                {
+                                                    tcs.Key.SetResult(message);
+                                                }
+                                                else
+                                                {
+                                                    tcs.Key.SetException(
+                                                        new TimeoutException("ValidUntilUTC is less than current time"));
+                                                }
+                                            }
+                                            catch (LuaException e)
+                                            {
+                                                Trace.TraceError(e.ToString());
+                                            }
+                                        }, response, TaskCreationOptions.PreferFairness);
                                     }
-
-                                    Trace.Assert(readLineTask.Status == TaskStatus.RanToCompletion);
-                                    var response = readLineTask.Result;
-                                    if (response == null)
-                                    {
-                                        throw new IOException("Lua returned an empty response or closed the connection");
-                                    }
-
-                                    // No IO exceptions possible for response, move its processing
-                                    // to the threadpool and wait for the next mesaage
-                                    // A new task here gives c.30% boost for full TransactionSpec echo
-
-                                    // ReSharper disable once UnusedVariable
-                                    var doNotAwaitMe = Task.Factory.StartNew(r =>
-                                    {
-                                        //var r = response;
-                                        //Trace.WriteLine("Response:" + response);
-                                        try
-                                        {
-                                            var message = (r as string).FromJson(this);
-                                            Trace.Assert(message.Id.HasValue && message.Id > 0);
-                                            // it is a response message
-                                            if (!Responses.ContainsKey(message.Id.Value))
-                                                throw new ApplicationException("Unexpected correlation ID");
-                                            KeyValuePair<TaskCompletionSource<IMessage>, Type> tcs;
-                                            Responses.TryRemove(message.Id.Value, out tcs);
-                                            if (!message.ValidUntil.HasValue || message.ValidUntil >= DateTime.UtcNow)
-                                            {
-                                                tcs.Key.SetResult(message);
-                                            }
-                                            else
-                                            {
-                                                tcs.Key.SetException(
-                                                    new TimeoutException("ValidUntilUTC is less than current time"));
-                                            }
-                                        }
-                                        catch (LuaException e)
-                                        {
-                                            Trace.TraceError(e.ToString());
-                                        }
-                                    }, response, TaskCreationOptions.PreferFairness);
                                 }
+
                             }
                             catch (TaskCanceledException)
                             {
@@ -365,6 +374,7 @@
                             {
                                 _responseClient.Client.Shutdown(SocketShutdown.Both);
                                 _responseClient.Close();
+                                _responseClient.Dispose();
                                 _responseClient = null; // У нас два потока работают с одним сокетом, но только один из них должен его закрыть !
                                 Trace.WriteLine("Response channel disconnected");
                             }
@@ -393,35 +403,37 @@
                             Trace.WriteLine("Callback channel connected");
                             try
                             {
-                                var stream = new NetworkStream(_callbackClient.Client);
-                                var reader = new StreamReader(stream, Encoding.GetEncoding(1251)); //true
-                                while (!_cts.IsCancellationRequested)
+                                using (var stream = new NetworkStream(_callbackClient.Client))
+                                using (var reader = new StreamReader(stream, Encoding.GetEncoding(1251))) //true
                                 {
-                                    var readLineTask = reader.ReadLineAsync();
-                                    var completedTask = await Task.WhenAny(readLineTask, _cancelledTcs.Task).ConfigureAwait(false);
-
-                                    if (completedTask == _cancelledTcs.Task || _cts.IsCancellationRequested)
+                                    while (!_cts.IsCancellationRequested)
                                     {
-                                        break;
-                                    }
-
-                                    Trace.Assert(readLineTask.Status == TaskStatus.RanToCompletion);
-                                    var callback = readLineTask.Result;
-                                    if (callback == null)
-                                    {
-                                        throw new IOException("Lua returned an empty response or closed the connection");
-                                    }
-
-                                    try
-                                    {
-                                        var message = callback.FromJson(this);
-                                        Trace.Assert(!(message.Id.HasValue && message.Id > 0));
-                                        // it is a callback message
-                                        await _receivedCallbacksChannel.Writer.WriteAsync(message);
-                                    }
-                                    catch (Exception e) // deserialization exception is possible
-                                    {
-                                        Trace.TraceError(e.ToString());
+                                        var readLineTask = reader.ReadLineAsync();
+                                        var completedTask = await Task.WhenAny(readLineTask, _cancelledTcs.Task).ConfigureAwait(false);
+
+                                        if (completedTask == _cancelledTcs.Task || _cts.IsCancellationRequested)
+                                        {
+                                            break;
+                                        }
+
+                                        Trace.Assert(readLineTask.Status == TaskStatus.RanToCompletion);
+                                        var callback = readLineTask.Result;
+                                        if (callback == null)
+                                        {
+                                            throw new IOException("Lua returned an empty response or closed the connection");
+                                        }
+
+                                        try
+                                        {
+                                            var message = callback.FromJson(this);
+                                            Trace.Assert(!(message.Id.HasValue && message.Id > 0));
+                                            // it is a callback message
+                                            await _receivedCallbacksChannel.Writer.WriteAsync(message);
+                                        }
+                                        catch (Exception e) // deserialization exception is possible
+                                        {
+                                            Trace.TraceError(e.ToString());
+                                        }
                                     }
                                 }
                             }
@@ -452,6 +464,7 @@
                             {
                                 _callbackClient.Client.Shutdown(SocketShutdown.Both);
                                 _callbackClient.Close();
+                                _callbackClient.Dispose();
                                 _callbackClient = null;
                                 Trace.WriteLine("Callback channel disconnected");
                             }
@@ -466,9 +479,10 @@
                 {
                     while (!_cts.IsCancellationRequested)
                     {
+                        var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
                         try
                         {
-                            var message = await _receivedCallbacksChannel.Reader.ReadAsync(_cts.Token);
+                            var message = await _receivedCallbacksChannel.Reader.ReadAsync(cts.Token);
                             try
                             {
                                 ProcessCallbackMessage(message);
@@ -480,6 +494,10 @@
                         }
                         catch (OperationCanceledException)
                         {
+                        }
+                        finally
+                        {
+                            cts.Dispose();
                         }
                     }
                 },
@@ -507,6 +525,7 @@
                         ct.ThrowIfCancellationRequested();
                         try
                         {
+                            _responseClient?.Dispose();
                             _responseClient = new TcpClient
                             {
                                 ExclusiveAddressUse = true,
@@ -532,6 +551,7 @@
                         ct.ThrowIfCancellationRequested();
                         try
                         {
+                            _callbackClient?.Dispose();
                             _callbackClient = new TcpClient
                             {
                                 ExclusiveAddressUse = true,
@@ -860,9 +880,10 @@
                 request.Id = GetNewUniqueId();
             }

+            CancellationTokenSource ct = null;
             if (timeout > 0)
             {
-                var ct = new CancellationTokenSource();
+                ct = new CancellationTokenSource();
                 ctRegistration = ct.Token.Register(() =>
                 {
                     tcs.TrySetException(new TimeoutException("Send operation timed out"));
@@ -884,8 +905,11 @@
             }
             finally
             {
-                if (timeout > 0)
-                    ctRegistration.Dispose();
+	            if (timeout > 0)
+	            {
+		            ct?.Dispose();
+		            ctRegistration.Dispose();
+	            }
             }

             return (response as TResponse);


Don't use using using

Don't use using using

--- osu.Framework.Tests/Threading/ThreadedTaskSchedulerTest.cs
+++ osu.Framework.Tests/Threading/ThreadedTaskSchedulerTest.cs
@@ -22,18 +22,19 @@

             const int target_count = 128;

-            using var taskScheduler = new ThreadedTaskScheduler(4, "test");
+            var taskScheduler = new ThreadedTaskScheduler(4, "test");

-            for (int i = 0; i < target_count; i++)
+            using (taskScheduler)
             {
-                Task.Factory.StartNew(() =>
+                for (int i = 0; i < target_count; i++)
                 {
-                    Interlocked.Increment(ref runCount);
-                    Thread.Sleep(100);
-                }, default, TaskCreationOptions.HideScheduler, taskScheduler);
+                    Task.Factory.StartNew(() =>
+                    {
+                        Interlocked.Increment(ref runCount);
+                        Thread.Sleep(100);
+                    }, default, TaskCreationOptions.HideScheduler, taskScheduler);
+                }
             }
-
-            taskScheduler.Dispose();

             // test against double disposal crashes.
             taskScheduler.Dispose();


Adjust test values

Adjust test values

--- osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
+++ osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
@@ -17,18 +17,18 @@
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";

-        [TestCase(6.6369583000323935d, 206, "diffcalc-test")]
-        [TestCase(1.4476531024675374d, 45, "zero-length-sliders")]
+        [TestCase(6.7115569159190587d, 206, "diffcalc-test")]
+        [TestCase(1.4391311903612753d, 45, "zero-length-sliders")]
         public void Test(double expectedStarRating, int expectedMaxCombo, string name)
             => base.Test(expectedStarRating, expectedMaxCombo, name);

-        [TestCase(8.8816128335486386d, 206, "diffcalc-test")]
-        [TestCase(1.7540389962596916d, 45, "zero-length-sliders")]
+        [TestCase(8.9757300665532966d, 206, "diffcalc-test")]
+        [TestCase(1.7437232654020756d, 45, "zero-length-sliders")]
         public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
             => Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());

-        [TestCase(6.6369583000323935d, 239, "diffcalc-test")]
-        [TestCase(1.4476531024675374d, 54, "zero-length-sliders")]
+        [TestCase(6.7115569159190587d, 239, "diffcalc-test")]
+        [TestCase(1.4391311903612753d, 54, "zero-length-sliders")]
         public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
             => Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());


Revert unnecessary async change

Revert unnecessary async change

--- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
+++ osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
@@ -387,7 +387,7 @@
             }
         }

-        public override async Task StartMatch()
+        public override Task StartMatch()
         {
             Debug.Assert(Room != null);

@@ -395,7 +395,7 @@
             foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready))
                 ChangeUserState(user.UserID, MultiplayerUserState.WaitingForLoad);

-            await ((IMultiplayerClient)this).LoadRequested();
+            return ((IMultiplayerClient)this).LoadRequested();
         }

         public override Task AbortGameplay()


Use ParseMultiValue() in TryParse() for improved error reporting.

--- Sandra.UI.WF/Storage/SettingReader.cs
+++ Sandra.UI.WF/Storage/SettingReader.cs
@@ -239,17 +239,19 @@
             {
                 try
                 {
-                    JsonTerminalSymbol symbol = ReadSkipComments();
-                    if (symbol != null)
-                    {
-                        PValue rootValue = ParseValue(symbol);
-
-                        JsonTerminalSymbol extraSymbol = ReadSkipComments();
-                        if (extraSymbol != null)
-                        {
-                            Errors.Add(new TextErrorInfo(FileShouldHaveEndedAlreadyMessage, extraSymbol.Start, extraSymbol.Length));
-                        }
-
+                    PValue rootValue;
+                    JsonTerminalSymbol symbol;
+
+                    bool hasRootValue = ParseMultiValue(FileShouldHaveEndedAlreadyMessage, out rootValue, out symbol);
+
+                    JsonTerminalSymbol extraSymbol = ReadSkipComments();
+                    if (extraSymbol != null)
+                    {
+                        Errors.Add(new TextErrorInfo(FileShouldHaveEndedAlreadyMessage, extraSymbol.Start, extraSymbol.Length));
+                    }
+
+                    if (hasRootValue)
+                    {
                         bool validMap = PType.Map.TryGetValidValue(rootValue, out map);
                         if (!validMap)
                         {


refactor(mapper): add IsNullableType helper method

--- src/Runtime.Mapper/Mapper.cs
+++ src/Runtime.Mapper/Mapper.cs
@@ -289,7 +289,7 @@
             {
                 Expression assignmentExpression = AssignOrConvertAndAssignExpression(sourceType, destinationType, sourceVar, destinationVar);

-                if (sourceType.IsGenericType && sourceType.GetGenericTypeDefinition() == typeof(Nullable<>))
+                if (IsNullableType(sourceType))
                 {
                     Expression checkNullAssignmentExpression = Expression.IfThenElse(
                         Expression.Equal(Expression.Property(sourceVar, "HasValue"), Expression.Constant(true)),
@@ -315,6 +315,11 @@
             return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>);
         }

+        private static bool IsNullableType(Type type)
+        {
+            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
+        }
+
         private static bool IsValueTypeOrString(Type type)
         {
             return type.IsValueType


updated to use latest syntax

updated to use latest syntax

--- src/Samples/Stuff/Program.cs
+++ src/Samples/Stuff/Program.cs
@@ -24,30 +24,29 @@
         static void Main(string[] args)
         {
             XmlConfigurator.ConfigureAndWatch(new FileInfo(".\\log4net.config"));
-            RunConfiguration cfg = RunnerConfigurator.New(x =>
-            {
-                x.AfterStoppingServices(h => { Console.WriteLine("AfterStoppingServices action invoked, services are stopping"); });

-                x.EnableDashboard();
+            var h = HostFactory.New(x =>
+                {
+                    x.AfterStoppingServices(n => Console.WriteLine("AfterStoppingServices action invoked, services are stopping"));

-                x.RunAsFromCommandLine();
+                    x.EnableDashboard();
+
+                    x.Service<TownCrier>(s =>
+                        {
+                            s.SetServiceName("TownCrier");
+                            s.ConstructUsing(name => new TownCrier());
+                            s.WhenStarted(tc => tc.Start());
+                            s.WhenStopped(tc => tc.Stop());
+                        });

-                x.ConfigureService<TownCrier>(s =>
-                {
-                    s.Named("TownCrier");
-                    s.HowToBuildService(name=> new TownCrier());
-                    s.WhenStarted(tc => tc.Start());
-                    s.WhenStopped(tc => tc.Stop());
+                    x.RunAsLocalSystem();
+
+                    x.SetDescription("Sample Topshelf Host");
+                    x.SetDisplayName("Stuff");
+                    x.SetServiceName("stuff");
                 });

-                x.RunAsLocalSystem();
-
-                x.SetDescription("Sample Topshelf Host");
-                x.SetDisplayName("Stuff");
-                x.SetServiceName("stuff");
-            });
-
-            Runner.Host(cfg, args);
+            h.Run();
         }
     }
 }


修改 StringMatch.cs

修改 StringMatch.cs

--- csharp/ToolGood.Words/TextMatch/StringMatch.cs
+++ csharp/ToolGood.Words/TextMatch/StringMatch.cs
@@ -28,14 +28,19 @@
 //            List<int> newKeywordLength = new List<int>();
 //            //List<int> newKeywordIndex = new List<int>();
 //            var index = 0;
-//            foreach (var keyword in keywords) {
-//                if (HasMatch(keyword) == false) {
+//            foreach (var keyword in keywords)
+//            {
+//                if (HasMatch(keyword) == false)
+//                {
 //                    newKeyword.Add(keyword);
 //                    newKeywordLength.Add(keyword.Length);
 //                    //newKeywordIndex.Add(index);
-//                } else {
+//                }
+//                else
+//                {
 //                    var list = MatchKeywordBuild(keyword);
-//                    foreach (var item in list) {
+//                    foreach (var item in list)
+//                    {
 //                        newKeyword.Add(item);
 //                        newKeywordLength.Add(item.Length);
 //                        //newKeywordIndex.Add(index);
@@ -55,48 +60,60 @@
 //            TrieNode root = allNode[0];

 //            var allNode2 = new List<TrieNode3>();
-//            for (int i = 0; i < allNode.Count; i++) {
+//            for (int i = 0; i < allNode.Count; i++)
+//            {
 //                allNode2.Add(new TrieNode3());
 //            }

-//            for (int i = 0; i < allNode2.Count; i++) {
+//            for (int i = 0; i < allNode2.Count; i++)
+//            {
 //                var oldNode = allNode[i];
 //                var newNode = allNode2[i];

-//                foreach (var item in oldNode.m_values) {
+//                foreach (var item in oldNode.m_values)
+//                {
 //                    var key = item.Key;
 //                    var index = item.Value.Index;
-//                    if (key == 0) {
+//                    if (key == 0)
+//                    {
 //                        newNode.HasWildcard = true;
 //                        newNode.WildcardNode = allNode2[index];
 //                        continue;
 //                    }
 //                    newNode.Add(key, allNode2[index]);
 //                }
-//                foreach (var item in oldNode.Results) {
+//                foreach (var item in oldNode.Results)
+//                {
 //                    newNode.SetResults(item);
 //                }

 //                var failure = oldNode.Failure;
-//                while (failure != root) {
-//                    if (oldNode.IsWildcard && failure.Layer < oldNode.WildcardLayer) {
+//                while (failure != root)
+//                {
+//                    if (oldNode.IsWildcard && failure.Layer < oldNode.WildcardLayer)
+//                    {
 //                        break;
 //                    }
-//                    foreach (var item in failure.m_values) {
+//                    foreach (var item in failure.m_values)
+//                    {
 //                        var key = item.Key;
 //                        var index = item.Value.Index;
-//                        if (key == 0) {
+//                        if (key == 0)
+//                        {
 //                            newNode.HasWildcard = true;
-//                            if (newNode.WildcardNode == null) {
+//                            if (newNode.WildcardNode == null)
+//                            {
 //                                newNode.WildcardNode = allNode2[index];
 //                            }
 //                            continue;
 //                        }
-//                        if (newNode.HasKey(key) == false) {
+//                        if (newNode.HasKey(key) == false)
+//                        {
 //                            newNode.Add(key, allNode2[index]);
 //                        }
 //                    }
-//                    foreach (var item in failure.Results) {
+//                    foreach (var item in failure.Results)
+//                    {
 //                        newNode.SetResults(item);
 //                    }
 //                    failure = failure.Failure;
@@ -106,8 +123,10 @@
 //            allNode = null;
 //            root = null;

+//            //var root2 = allNode2[0];
 //            TrieNode3[] first = new TrieNode3[char.MaxValue + 1];
-//            foreach (var item in allNode2[0].m_values) {
+//            foreach (var item in allNode2[0].m_values)
+//            {
 //                first[item.Key] = item.Value;
 //            }
 //            _first = first;
@@ -120,19 +139,24 @@

 //            Dictionary<int, List<TrieNode>> allNodeLayers = new Dictionary<int, List<TrieNode>>();
 //            #region 第一次关键字
-//            for (int i = 0; i < keywords.Count; i++) {
+//            for (int i = 0; i < keywords.Count; i++)
+//            {
 //                var p = keywords[i];
 //                var nd = root;
 //                var start = 0;
-//                while (p[start] == 0) { // 0 为 通配符
+//                while (p[start] == 0)
+//                { // 0 为 通配符
 //                    start++;
 //                }
-//                for (int j = start; j < p.Length; j++) {
+//                for (int j = start; j < p.Length; j++)
+//                {
 //                    nd = nd.Add((char)p[j]);
-//                    if (nd.Layer == 0) {
+//                    if (nd.Layer == 0)
+//                    {
 //                        nd.Layer = j + 1 - start;
 //                        List<TrieNode> trieNodes;
-//                        if (allNodeLayers.TryGetValue(nd.Layer, out trieNodes) == false) {
+//                        if (allNodeLayers.TryGetValue(nd.Layer, out trieNodes) == false)
+//                        {
 //                            trieNodes = new List<TrieNode>();
 //                            allNodeLayers[nd.Layer] = trieNodes;
 //                        }
@@ -144,31 +168,42 @@
 //            #endregion

 //            #region 第二次关键字 通配符
-//            for (int i = 0; i < keywords.Count; i++) {
+//            for (int i = 0; i < keywords.Count; i++)
+//            {
 //                var p = keywords[i];
-//                if (p.Contains((char)0) == false) {
+//                if (p.Contains((char)0) == false)
+//                {
 //                    continue;
 //                }
 //                var start = 0;
-//                while (p[start] == 0) { // 0 为 通配符
+//                while (p[start] == 0)
+//                { // 0 为 通配符
 //                    start++;
 //                }
 //                List<TrieNode> trieNodes = new List<TrieNode>() { root };

-//                for (int j = start; j < p.Length; j++) {
+//                for (int j = start; j < p.Length; j++)
+//                {
 //                    List<TrieNode> newTrieNodes = new List<TrieNode>();
 //                    var c = p[j];
-//                    if (c == 0) {
-//                        foreach (var nd in trieNodes) {
+//                    if (c == 0)
+//                    {
+//                        foreach (var nd in trieNodes)
+//                        {
 //                            newTrieNodes.AddRange(nd.m_values.Values);
 //                        }
-//                    } else {
-//                        foreach (var nd in trieNodes) {
+//                    }
+//                    else
+//                    {
+//                        foreach (var nd in trieNodes)
+//                        {
 //                            var nd2 = nd.Add(c);
-//                            if (nd2.Layer == 0) {
+//                            if (nd2.Layer == 0)
+//                            {
 //                                nd2.Layer = j + 1 - start;
 //                                List<TrieNode> tnodes;
-//                                if (allNodeLayers.TryGetValue(nd2.Layer, out tnodes) == false) {
+//                                if (allNodeLayers.TryGetValue(nd2.Layer, out tnodes) == false)
+//                                {
 //                                    tnodes = new List<TrieNode>();
 //                                    allNodeLayers[nd.Layer] = tnodes;
 //                                }
@@ -179,7 +214,8 @@
 //                    }
 //                    trieNodes = newTrieNodes;
 //                }
-//                foreach (var nd in trieNodes) {
+//                foreach (var nd in trieNodes)
+//                {
 //                    nd.SetResults(i);
 //                }
 //            }
@@ -188,8 +224,10 @@
 //            #region 添加到 allNode
 //            var allNode = new List<TrieNode>();
 //            allNode.Add(root);
-//            foreach (var trieNodes in allNodeLayers) {
-//                foreach (var nd in trieNodes.Value) {
+//            foreach (var trieNodes in allNodeLayers)
+//            {
+//                foreach (var nd in trieNodes.Value)
+//                {
 //                    allNode.Add(nd);
 //                }
 //            }
@@ -198,7 +236,8 @@
 //            #endregion

 //            #region 第一次 Set Failure
-//            for (int i = 1; i < allNode.Count; i++) {
+//            for (int i = 1; i < allNode.Count; i++)
+//            {
 //                var nd = allNode[i];
 //                nd.Index = i;
 //                TrieNode r = nd.Parent.Failure;
@@ -206,7 +245,8 @@
 //                while (r != null && !r.m_values.ContainsKey(c)) r = r.Failure;
 //                if (r == null)
 //                    nd.Failure = root;
-//                else {
+//                else
+//                {
 //                    nd.Failure = r.m_values[c];
 //                    foreach (var result in nd.Failure.Results)
 //                        nd.SetResults(result);
@@ -215,29 +255,38 @@
 //            #endregion

 //            #region 第二次 Set Failure
-//            for (int i = 1; i < allNode.Count; i++) {
+//            for (int i = 1; i < allNode.Count; i++)
+//            {
 //                var nd = allNode[i];
 //                if (nd.Layer == 1) { continue; }
-//                if (nd.m_values.ContainsKey((char)0)) {
+//                if (nd.m_values.ContainsKey((char)0))
+//                {
 //                    nd.HasWildcard = true;
 //                }
-//                if (nd.Failure.HasWildcard) {
+//                if (nd.Failure.HasWildcard)
+//                {
 //                    nd.HasWildcard = true;
 //                }
-//                if (nd.Char == 0) {
+//                if (nd.Char == 0)
+//                {
 //                    nd.IsWildcard = true;
 //                    continue;
-//                } else if (nd.Parent.IsWildcard) {
+//                }
+//                else if (nd.Parent.IsWildcard)
+//                {
 //                    nd.IsWildcard = true;
 //                    nd.WildcardLayer = nd.Parent.WildcardLayer + 1;
-//                    if (nd.Failure != root) {
-//                        if (nd.Failure.Layer < nd.WildcardLayer) {
+//                    if (nd.Failure != root)
+//                    {
+//                        if (nd.Failure.Layer < nd.WildcardLayer)
+//                        {
 //                            nd.Failure = root;
 //                        }
 //                    }
 //                    continue;
 //                }
 //            }
+//            root.Failure = root;
 //            #endregion

 //            return allNode;
@@ -247,9 +296,11 @@
 //        #region HasMatch
 //        private bool HasMatch(string keyword)
 //        {
-//            for (int i = 0; i < keyword.Length; i++) {
+//            for (int i = 0; i < keyword.Length; i++)
+//            {
 //                char c = keyword[i];
-//                if (c == '.' || c == '?' || c == '\\' || c == '[') {
+//                if (c == '.' || c == '?' || c == '\\' || c == '[')
+//                {
 //                    return true;
 //                }
 //            }
@@ -264,14 +315,16 @@
 //            Dictionary<int, List<string>> parameterDict = new Dictionary<int, List<string>>();
 //            SeparateParameters(keyword, stringBuilder, parameterDict);

-//            if (parameterDict.Count == 0) {
+//            if (parameterDict.Count == 0)
+//            {
 //                return new List<string>() { stringBuilder.ToString() };
 //            }
 //            List<string> parameters = new List<string>();
 //            KeywordBuild(parameterDict, 0, parameterDict.Keys.Count - 1, "", parameters);
 //            var keywordFmt = stringBuilder.ToString();
 //            HashSet<string> list = new HashSet<string>();
-//            foreach (var item in parameters) {
+//            foreach (var item in parameters)
+//            {
 //                list.Add(string.Format(keywordFmt, item.Split((char)1)));
 //            }
 //            return list.ToList();
@@ -282,75 +335,110 @@
 //            var index = 0;
 //            var parameterIndex = 0;

-//            while (index < keyword.Length) {
+//            while (index < keyword.Length)
+//            {
 //                var c = keyword[index];
-//                if (c == '.') {
-//                    if (index + 1 < keyword.Length && keyword[index + 1] == '?') {
+//                if (c == '.')
+//                {
+//                    if (index + 1 < keyword.Length && keyword[index + 1] == '?')
+//                    {
 //                        parameterDict[parameterIndex] = new List<string>() { "", ((char)0).ToString() };
 //                        stringBuilder.Append("{" + parameterIndex + "}");
 //                        parameterIndex++;
 //                        index += 2;

-//                    } else {
+//                    }
+//                    else
+//                    {
 //                        stringBuilder.Append(((char)0));
 //                        index++;
 //                    }
-//                } else if (c == '\\') {
-//                    if (index + 2 < keyword.Length && keyword[index + 2] == '?') {
+//                }
+//                else if (c == '\\')
+//                {
+//                    if (index + 2 < keyword.Length && keyword[index + 2] == '?')
+//                    {
 //                        parameterDict[parameterIndex] = new List<string>() { "", keyword[index + 1].ToString() };
 //                        stringBuilder.Append("{" + parameterIndex + "}");
 //                        parameterIndex++;
 //                        index += 3;
-//                    } else if (index + 1 < keyword.Length) {
+//                    }
+//                    else if (index + 1 < keyword.Length)
+//                    {
 //                        stringBuilder.Append(keyword[index + 1]);
 //                        index += 2;
-//                    } else {
+//                    }
+//                    else
+//                    {
 //                        throw new Exception($"【{keyword}】出错了，最后一位为\\");
 //                    }
-//                } else if (c == '[') {
+//                }
+//                else if (c == '[')
+//                {
 //                    index++;
 //                    var ps = new List<string>();
-//                    while (index < keyword.Length) {
+//                    while (index < keyword.Length)
+//                    {
 //                        c = keyword[index];
-//                        if (c == ']') {
+//                        if (c == ']')
+//                        {
 //                            break;
-//                        } else if (c == '\\') {
-//                            if (index + 1 < keyword.Length) {
+//                        }
+//                        else if (c == '\\')
+//                        {
+//                            if (index + 1 < keyword.Length)
+//                            {
 //                                ps.Add(keyword[index + 1].ToString());
 //                                index += 2;
 //                            }
-//                        } else {
+//                        }
+//                        else
+//                        {
 //                            ps.Add(c.ToString());
 //                            index++;
 //                        }
 //                    }
-//                    if (c != ']') {
+//                    if (c != ']')
+//                    {
 //                        throw new Exception($"【{keyword}】出错了，最后一位不为]");
 //                    }
-//                    if (index + 1 < keyword.Length && keyword[index + 1] == '?') {
+//                    if (index + 1 < keyword.Length && keyword[index + 1] == '?')
+//                    {
 //                        ps.Insert(0, "");
 //                        parameterDict[parameterIndex] = ps;
 //                        stringBuilder.Append("{" + parameterIndex + "}");
 //                        parameterIndex++;
 //                        index += 2;
-//                    } else {
+//                    }
+//                    else
+//                    {
 //                        parameterDict[parameterIndex] = ps;
 //                        stringBuilder.Append("{" + parameterIndex + "}");
 //                        parameterIndex++;
 //                        index++;
 //                    }
-//                } else {
-//                    if (index + 1 < keyword.Length && keyword[index + 1] == '?') {
+//                }
+//                else
+//                {
+//                    if (index + 1 < keyword.Length && keyword[index + 1] == '?')
+//                    {
 //                        parameterDict[parameterIndex] = new List<string>() { "", c.ToString() };
 //                        stringBuilder.Append("{" + parameterIndex + "}");
 //                        parameterIndex++;
 //                        index += 2;
-//                    } else {
-//                        if (c == '{') {
+//                    }
+//                    else
+//                    {
+//                        if (c == '{')
+//                        {
 //                            stringBuilder.Append("{{");
-//                        } else if (c == '}') {
+//                        }
+//                        else if (c == '}')
+//                        {
 //                            stringBuilder.Append("}}");
-//                        } else {
+//                        }
+//                        else
+//                        {
 //                            stringBuilder.Append(c);
 //                        }
 //                        index++;
@@ -363,12 +451,17 @@
 //        {
 //            const char span = (char)1;
 //            var list = parameterDict[index];
-//            if (index == end) {
-//                foreach (var item in list) {
+//            if (index == end)
+//            {
+//                foreach (var item in list)
+//                {
 //                    result.Add((keyword + span + item).Substring(1));
 //                }
-//            } else {
-//                foreach (var item in list) {
+//            }
+//            else
+//            {
+//                foreach (var item in list)
+//                {
 //                    KeywordBuild(parameterDict, index + 1, end, keyword + span + item, result);
 //                }
 //            }
@@ -388,27 +481,36 @@
 //        public string FindFirst(string text)
 //        {
 //            TrieNode3 ptr = null;
-//            for (int i = 0; i < text.Length; i++) {
+//            for (int i = 0; i < text.Length; i++)
+//            {
 //                var t = text[i];

 //                TrieNode3 tn;
-//                if (ptr == null) {
+//                if (ptr == null)
+//                {
 //                    tn = _first[t];
-//                } else {
-//                    if (ptr.TryGetValue(t, out tn) == false) {
-//                        if (ptr.HasWildcard) {
+//                }
+//                else
+//                {
+//                    if (ptr.TryGetValue(t, out tn) == false)
+//                    {
+//                        if (ptr.HasWildcard)
+//                        {
 //                            var result = FindFirst(text, i + 1, ptr.WildcardNode);
-//                            if (result != null) {
+//                            if (result != null)
+//                            {
 //                                return result;
 //                            }
 //                        }
 //                        tn = _first[t];
 //                    }
 //                }
-//                if (tn != null) {
-//                    if (tn.End) {
+//                if (tn != null)
+//                {
+//                    if (tn.End)
+//                    {
 //                        var length = _keywordLength[tn.Results[0]];
-//                        return text.Substring(i - length, length);
+//                        return text.Substring(i - length + 1, length);
 //                        //return _keywords[tn.Results[0]];
 //                    }
 //                }
@@ -418,21 +520,26 @@
 //        }
 //        private string FindFirst(string text, int index, TrieNode3 ptr)
 //        {
-//            for (int i = index; i < text.Length; i++) {
+//            for (int i = index; i < text.Length; i++)
+//            {
 //                var t = text[i];
 //                TrieNode3 tn;
-//                if (ptr.TryGetValue(t, out tn) == false) {
-//                    if (ptr.HasWildcard) {
+//                if (ptr.TryGetValue(t, out tn) == false)
+//                {
+//                    if (ptr.HasWildcard)
+//                    {
 //                        var result = FindFirst(text, i + 1, ptr.WildcardNode);
-//                        if (result != null) {
+//                        if (result != null)
+//                        {
 //                            return result;
 //                        }
 //                    }
 //                    return null;
 //                }
-//                if (tn.End) {
+//                if (tn.End)
+//                {
 //                    var length = _keywordLength[tn.Results[0]];
-//                    return text.Substring(i - length, length);
+//                    return text.Substring(i - length + 1, length);
 //                    //return _keywords[tn.Results[0]];
 //                }
 //                ptr = tn;
@@ -453,24 +560,33 @@
 //            TrieNode3 ptr = null;
 //            List<string> list = new List<string>();

-//            for (int i = 0; i < text.Length; i++) {
+//            for (int i = 0; i < text.Length; i++)
+//            {
 //                var t = text[i];
 //                TrieNode3 tn;
-//                if (ptr == null) {
+//                if (ptr == null)
+//                {
 //                    tn = _first[t];
-//                } else {
-//                    if (ptr.TryGetValue(t, out tn) == false) {
-//                        if (ptr.HasWildcard) {
+//                }
+//                else
+//                {
+//                    if (ptr.TryGetValue(t, out tn) == false)
+//                    {
+//                        if (ptr.HasWildcard)
+//                        {
 //                            FindAll(text, i + 1, ptr.WildcardNode, list);
 //                        }
 //                        tn = _first[t];
 //                    }
 //                }
-//                if (tn != null) {
-//                    if (tn.End) {
-//                        foreach (var item in tn.Results) {
+//                if (tn != null)
+//                {
+//                    if (tn.End)
+//                    {
+//                        foreach (var item in tn.Results)
+//                        {
 //                            var length = _keywordLength[tn.Results[0]];
-//                            var key = text.Substring(i - length, length);
+//                            var key = text.Substring(i - length + 1, length);
 //                            list.Add(key);
 //                        }
 //                    }
@@ -481,19 +597,24 @@
 //        }
 //        private void FindAll(string text, int index, TrieNode3 ptr, List<string> list)
 //        {
-//            for (int i = index; i < text.Length; i++) {
+//            for (int i = index; i < text.Length; i++)
+//            {
 //                var t = text[i];
 //                TrieNode3 tn;
-//                if (ptr.TryGetValue(t, out tn) == false) {
-//                    if (ptr.HasWildcard) {
+//                if (ptr.TryGetValue(t, out tn) == false)
+//                {
+//                    if (ptr.HasWildcard)
+//                    {
 //                        FindAll(text, i + 1, ptr.WildcardNode, list);
 //                    }
 //                    return;
 //                }
-//                if (tn.End) {
-//                    foreach (var item in tn.Results) {
+//                if (tn.End)
+//                {
+//                    foreach (var item in tn.Results)
+//                    {
 //                        var length = _keywordLength[tn.Results[0]];
-//                        var key = text.Substring(i - length, length);
+//                        var key = text.Substring(i - length + 1, length);
 //                        list.Add(key);
 //                    }
 //                }
@@ -512,24 +633,33 @@
 //        public bool ContainsAny(string text)
 //        {
 //            TrieNode3 ptr = null;
-//            for (int i = 0; i < text.Length; i++) {
+//            for (int i = 0; i < text.Length; i++)
+//            {
 //                var t = text[i];
 //                TrieNode3 tn;
-//                if (ptr == null) {
+//                if (ptr == null)
+//                {
 //                    tn = _first[t];
-//                } else {
-//                    if (ptr.TryGetValue(t, out tn) == false) {
-//                        if (ptr.HasWildcard) {
+//                }
+//                else
+//                {
+//                    if (ptr.TryGetValue(t, out tn) == false)
+//                    {
+//                        if (ptr.HasWildcard)
+//                        {
 //                            var result = ContainsAny(text, i + 1, ptr.WildcardNode);
-//                            if (result) {
+//                            if (result)
+//                            {
 //                                return true;
 //                            }
 //                        }
 //                        tn = _first[t];
 //                    }
 //                }
-//                if (tn != null) {
-//                    if (tn.End) {
+//                if (tn != null)
+//                {
+//                    if (tn.End)
+//                    {
 //                        return true;
 //                    }
 //                }
@@ -539,16 +669,20 @@
 //        }
 //        private bool ContainsAny(string text, int index, TrieNode3 ptr)
 //        {
-//            for (int i = index; i < text.Length; i++) {
+//            for (int i = index; i < text.Length; i++)
+//            {
 //                var t = text[i];
 //                TrieNode3 tn;
-//                if (ptr.TryGetValue(t, out tn) == false) {
-//                    if (ptr.HasWildcard) {
+//                if (ptr.TryGetValue(t, out tn) == false)
+//                {
+//                    if (ptr.HasWildcard)
+//                    {
 //                        return ContainsAny(text, i + 1, ptr.WildcardNode);
 //                    }
 //                    return false;
 //                }
-//                if (tn.End) {
+//                if (tn.End)
+//                {
 //                    return true;
 //                }
 //                ptr = tn;
@@ -570,23 +704,32 @@
 //            StringBuilder result = new StringBuilder(text);

 //            TrieNode3 ptr = null;
-//            for (int i = 0; i < text.Length; i++) {
+//            for (int i = 0; i < text.Length; i++)
+//            {
 //                TrieNode3 tn;
-//                if (ptr == null) {
+//                if (ptr == null)
+//                {
 //                    tn = _first[text[i]];
-//                } else {
-//                    if (ptr.TryGetValue(text[i], out tn) == false) {
-//                        if (ptr.HasWildcard) {
+//                }
+//                else
+//                {
+//                    if (ptr.TryGetValue(text[i], out tn) == false)
+//                    {
+//                        if (ptr.HasWildcard)
+//                        {
 //                            Replace(text, i + 1, ptr.WildcardNode, replaceChar, result);
 //                        }
 //                        tn = _first[text[i]];
 //                    }
 //                }
-//                if (tn != null) {
-//                    if (tn.End) {
+//                if (tn != null)
+//                {
+//                    if (tn.End)
+//                    {
 //                        var maxLength = _keywordLength[tn.Results[0]];
 //                        var start = i + 1 - maxLength;
-//                        for (int j = start; j <= i; j++) {
+//                        for (int j = start; j <= i; j++)
+//                        {
 //                            result[j] = replaceChar;
 //                        }
 //                    }
@@ -598,19 +741,24 @@

 //        private void Replace(string text, int index, TrieNode3 ptr, char replaceChar, StringBuilder result)
 //        {
-//            for (int i = index; i < text.Length; i++) {
+//            for (int i = index; i < text.Length; i++)
+//            {
 //                var t = text[i];
 //                TrieNode3 tn;
-//                if (ptr.TryGetValue(t, out tn) == false) {
-//                    if (ptr.HasWildcard) {
+//                if (ptr.TryGetValue(t, out tn) == false)
+//                {
+//                    if (ptr.HasWildcard)
+//                    {
 //                        Replace(text, i + 1, ptr.WildcardNode, replaceChar, result);
 //                    }
 //                    return;
 //                }
-//                if (tn.End) {
+//                if (tn.End)
+//                {
 //                    var maxLength = _keywordLength[tn.Results[0]];
 //                    var start = i + 1 - maxLength;
-//                    for (int j = start; j <= i; j++) {
+//                    for (int j = start; j <= i; j++)
+//                    {
 //                        result[j] = replaceChar;
 //                    }
 //                }


partial path unification fixed block handling

--- CIL/IL.cs
+++ CIL/IL.cs
@@ -146,9 +146,9 @@
             var _this = code.IsStatic ? Enumerable.Empty<T>() : new[] { decompiler.Param("this", code.DeclaringType) };
             args = _this.Concat(code.GetParameters().Select(decompiler.Param)).ToArray();
             Process(il, decompiler, eval, new Dictionary<Label, T>(), args, il.Locals.Select(decompiler.Local).ToArray());
-            return eval.Pop();
-        }
-
+            return eval.Count > 1 ? decompiler.Block(eval.Reverse()) : eval.Pop();
+        }
+
         static void Process<T>(ILReader il, IExpression<T> exp, Stack<T> eval, Dictionary<Label, T> env, T[] args, T[] locals)
         {
             // the simplest way to add branch sharing, but less efficient
@@ -285,16 +285,26 @@
                         if (!env.TryGetValue(thenStart, out var _then))
                         {
                             il.Seek(thenStart);                    // seek to _then when branch condition true
-                            var tmp = new Stack<T>();
+                            var tmp = new Stack<T>(eval);
                             Process(il, exp, tmp, env, args, locals);
-                            _then = env[thenStart] = tmp.Pop();   // extract _then expression
+                            // pop until tmp element matches an element in eval, then create block expression
+                            var block = new Stack<T>();
+                            while (!eval.Contains(tmp.Peek()))
+                                block.Push(tmp.Pop());
+                            // extract _then expression
+                            _then = env[thenStart] = block.Count > 1 ? exp.Block(block) : block.Peek();
                         }
                         if (!env.TryGetValue(elseStart, out var _else))
                         {
                             il.Seek(elseStart);                    // seek to _else when branch condition false
-                            var tmp = new Stack<T>();
+                            var tmp = new Stack<T>(eval);
                             Process(il, exp, tmp, env, args, locals);
-                            _else = env[thenStart] = tmp.Pop();   // extract _else expression
+                            // pop until tmp element matches an element in eval, then create block expression
+                            var block = new Stack<T>(eval);
+                            while (!eval.Contains(tmp.Peek()))
+                                block.Push(tmp.Pop());
+                            // extract _then expression
+                            _else = env[thenStart] = block.Count > 1 ? exp.Block(block) : block.Peek();   // extract _else expression
                         }
                         eval.Push(exp.If(cond, _then, _else));
                         break;
@@ -579,12 +589,12 @@
                         break;
                     case OpType.Ret:
                         //Debug.Assert(eval.Count == 1);
-                        if (eval.Count > 1)
-                        {
-                            var block = eval.Reverse().ToList();
-                            eval.Clear();
-                            eval.Push(exp.Block(block));
-                        }
+                        //if (eval.Count > 1)
+                        //{
+                        //    var block = eval.Reverse().ToList();
+                        //    eval.Clear();
+                        //    eval.Push(exp.Block(block));
+                        //}
                         eval.Push(exp.Return(eval.Pop()));
                         //break;
                         return;


Fix draw visualiser trimming last property in scroll

--- osu.Framework/Graphics/Visualisation/DrawableInspector.cs
+++ osu.Framework/Graphics/Visualisation/DrawableInspector.cs
@@ -5,6 +5,7 @@
 using System.Linq;
 using osu.Framework.Allocation;
 using osu.Framework.Bindables;
+using osu.Framework.Extensions;
 using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.UserInterface;
@@ -18,7 +19,7 @@

         private const float width = 600;

-        private readonly FillFlowContainer content;
+        private readonly GridContainer content;
         private readonly TabControl<Tab> inspectorTabControl;
         private readonly Container tabContentContainer;
         private readonly PropertyDisplay propertyDisplay;
@@ -29,33 +30,41 @@
             Width = width;
             RelativeSizeAxes = Axes.Y;

-            Child = content = new FillFlowContainer
+            Child = content = new GridContainer
             {
                 RelativeSizeAxes = Axes.Both,
-                Direction = FillDirection.Vertical,
-                Children = new Drawable[]
+                RowDimensions = new[]
                 {
-                    inspectorTabControl = new BasicTabControl<Tab>
+                    new Dimension(GridSizeMode.AutoSize),
+                    new Dimension()
+                },
+                ColumnDimensions = new[] { new Dimension() },
+                Content = new[]
+                {
+                    new Drawable[]
                     {
-                        RelativeSizeAxes = Axes.X,
-                        Height = 20,
-                        Margin = new MarginPadding
+                        inspectorTabControl = new BasicTabControl<Tab>
                         {
-                            Horizontal = 10,
-                            Vertical = 5
+                            RelativeSizeAxes = Axes.X,
+                            Height = 20,
+                            Margin = new MarginPadding
+                            {
+                                Horizontal = 10,
+                                Vertical = 5
+                            },
+                            Items = Enum.GetValues(typeof(Tab)).Cast<Tab>().ToList()
                         },
-                        Items = Enum.GetValues(typeof(Tab)).Cast<Tab>().ToList()
-                    },
-                    tabContentContainer = new Container
-                    {
-                        RelativeSizeAxes = Axes.Both,
-                        Children = new Drawable[]
+                        tabContentContainer = new Container
                         {
-                            propertyDisplay = new PropertyDisplay(),
-                            transformDisplay = new TransformDisplay()
+                            RelativeSizeAxes = Axes.Both,
+                            Children = new Drawable[]
+                            {
+                                propertyDisplay = new PropertyDisplay(),
+                                transformDisplay = new TransformDisplay()
+                            }
                         }
                     }
-                }
+                }.Invert()
             };
         }


Rewrite LocalisableStringDescriptor.

Rewrite LocalisableStringDescriptor.

--- osu.Framework/Localisation/LocalisableStringDescriptor.cs
+++ osu.Framework/Localisation/LocalisableStringDescriptor.cs
@@ -2,78 +2,151 @@
 // See the LICENCE file in the repository root for full licence text.

 using System;
-using System.Linq;
+using System.Diagnostics.CodeAnalysis;
 using osu.Framework.Configuration;
+
+#nullable enable

 namespace osu.Framework.Localisation
 {
     ///
-    /// A class representing text that can be localised and formatted.
+    /// A descriptor representing text that can be localised and formatted.
     ///
     public readonly struct LocalisableStringDescriptor : IEquatable<LocalisableStringDescriptor>
     {
-        ///
-        /// The text to be localised.
-        ///
-        public readonly (string Original, string Fallback) Text;
+        // Reuse string1 and string2 in different types to save size.
+        // TODO: this is a good candidate of discriminated union (https://github.com/dotnet/csharplang/issues/113).
+        private readonly StringLocalisationType type;
+        private readonly string? string1;
+        private readonly string? string2;
+        private readonly object?[]? args;
+
+        public static implicit operator string(LocalisableStringDescriptor localised) => localised.ToString();
+
+        public static implicit operator LocalisableStringDescriptor(string text) => new LocalisableStringDescriptor(StringLocalisationType.None, text, null, null);
+
+        public override string ToString()
+            => type == StringLocalisationType.Translation
+            ? string.Format(string1!, args!)
+            : (string1 ?? string.Empty);
+
+        public bool Equals(LocalisableStringDescriptor other) =>
+            type == other.type &&
+            string1 == other.string1 &&
+            string2 == other.string2 &&
+            ReferenceEquals(args, other.args); // SequenceEqual cannot handle equality of boxed value types. Equality arg by arg isn't worthy.
+
+        public bool TryGetPlainText([NotNullWhen(true)] out string? text)
+        {
+            if (type == StringLocalisationType.None)
+            {
+                text = string1 ?? string.Empty;
+                return true;
+            }
+            else
+            {
+                text = null;
+                return false;
+            }
+        }

         ///
-        /// The arguments to format <see cref="Text"/> with.
+        /// Creates a new <see cref="LocalisableStringDescriptor"/>. This localises based on the value of <see cref="FrameworkSetting.ShowUnicode"/>.
         ///
-        public readonly object[] Args;
+        /// <param name="romanized">The romanised string.</param>
+        /// <param name="unicode">The unicode string.</param>
+        /// <returns>The descriptor of the localisable string.</returns>
+        public static LocalisableStringDescriptor FromRomanization(string romanized, string unicode)
+            => new LocalisableStringDescriptor(StringLocalisationType.Romanization, romanized, unicode, null);

-        ///
-        /// Whether <see cref="Text"/> should be localised.
-        ///
-        internal readonly bool ShouldLocalise;
-
-        ///
-        /// Creates a new <see cref="LocalisableStringDescriptor"/>. This localises based on the value of <see cref="FrameworkSetting.Locale"/>.
-        ///
-        /// <param name="text">The text to be localised.</param>
-        /// <param name="args">The arguments to format the text with.</param>
-        public LocalisableStringDescriptor(string text, params object[] args)
-            : this((text, text), true, args)
+        public bool TryGetRomanization([NotNullWhen(true)] out string? romanized, [NotNullWhen(true)] out string? unicode)
         {
+            if (type == StringLocalisationType.Romanization)
+            {
+                romanized = string1;
+                unicode = string2;
+                return true;
+            }
+            else
+            {
+                romanized = null;
+                unicode = null;
+                return false;
+            }
         }

         ///
         /// Creates a new <see cref="LocalisableStringDescriptor"/>. This localises based on the value of <see cref="FrameworkSetting.Locale"/>.
+        /// Supposed usage:
+        ///
+        /// LocalisableStringDescriptor.FromTranslatable("Played_count_self", "You have played {0:N0} times!", count);
+        ///
         ///
-        /// <param name="text">The text to be localised. Accepts a fallback value which is used when <see cref="FrameworkSetting.ShowUnicode"/> is false.</param>
+        /// <param name="key">The key to look for localisation by <see cref="LocalisationManager"/>.</param>
+        /// <param name="fallback">The fallback text to use when localised text is not found.</param>
         /// <param name="args">The arguments to format the text with.</param>
-        public LocalisableStringDescriptor((string original, string fallback) text, params object[] args)
-            : this(text, true, args)
+        /// <returns>The descriptor of the localisable string.</returns>
+        public static LocalisableStringDescriptor FromTranslatable(string key, string fallback, params object[] args)
+            => new LocalisableStringDescriptor(StringLocalisationType.Translation, fallback, key, args);
+
+        ///
+        /// Creates a new <see cref="LocalisableStringDescriptor"/>. This localises based on the value of <see cref="FrameworkSetting.Locale"/>.
+        /// Supposed usage:
+        ///
+        /// LocalisableStringDescriptor.FromInterpolatedTranslatable($"You have played {count:N0} times!", "Played_count_self");
+        ///
+        ///
+        /// <param name="interpolated">The interpolation string containing fallback and arguments.</param>
+        /// <param name="key">The key to look for localisation by <see cref="LocalisationManager"/>.</param>
+        /// <returns>The descriptor of the localisable string.</returns>
+        public static LocalisableStringDescriptor FromInterpolatedTranslatable(FormattableString interpolated, string key)
+            => new LocalisableStringDescriptor(StringLocalisationType.Translation, interpolated.Format, key, interpolated.GetArguments());
+
+        public bool TryGetTranslatable([NotNullWhen(true)] out string? key, [NotNullWhen(true)] out string? fallback, [NotNullWhen(true)] out object?[]? args)
         {
+            if (type == StringLocalisationType.Translation)
+            {
+                key = string2;
+                fallback = string1;
+                args = this.args;
+                return true;
+            }
+            else
+            {
+                key = null;
+                fallback = null;
+                args = null;
+                return false;
+            }
+        }
+
+        private LocalisableStringDescriptor(StringLocalisationType type, string string1, string? string2, object?[]? args)
+        {
+            this.type = type;
+            this.string1 = string1;
+            this.string2 = string2;
+            this.args = args;
         }

         ///
-        /// Creates a new <see cref="LocalisableStringDescriptor"/>.
+        /// Represents the type of a <see cref="LocalisableStringDescriptor"/>.
         ///
-        /// <param name="text">The text to use when <see cref="FrameworkSetting.ShowUnicode"/> is true.</param>
-        /// <param name="shouldLocalise">Whether the text should be localised.</param>
-        /// <param name="args">The arguments to format the text with.</param>
-        private LocalisableStringDescriptor((string original, string fallback) text, bool shouldLocalise, params object[] args)
+        private enum StringLocalisationType
         {
-            if (string.IsNullOrEmpty(text.original))
-                text.original = text.fallback ?? string.Empty;
-            if (string.IsNullOrEmpty(text.fallback))
-                text.fallback = text.original;
+            ///
+            /// The <see cref="LocalisableStringDescriptor"/> is constructed from plain text and not localisable.
+            ///
+            None,

-            Text = text;
-            ShouldLocalise = shouldLocalise;
-            Args = args;
+            ///
+            /// The <see cref="LocalisableStringDescriptor"/> is constructed from Romanization/Unicode pair.
+            ///
+            Romanization,
+
+            ///
+            /// The <see cref="LocalisableStringDescriptor"/> is constructed from translation key.
+            ///
+            Translation,
         }
-
-        public static implicit operator string(LocalisableStringDescriptor localised) => localised.Text.Original;
-
-        public static implicit operator LocalisableStringDescriptor(string text) => new LocalisableStringDescriptor((text, text), false);
-
-        public override string ToString() => Text.Original;
-
-        public bool Equals(LocalisableStringDescriptor other) =>
-            Text == other.Text &&
-            ShouldLocalise == other.ShouldLocalise &&
-            Args.SequenceEqual(other.Args);
     }
 }


Remove always-false condition

Remove always-false condition

--- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -100,9 +100,6 @@

         private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
         {
-            if (!(hitObject is Fruit))
-                return;
-
             float offsetPosition = hitObject.X;
             double startTime = hitObject.StartTime;


Remove unneeded lifetime assignment

Remove unneeded lifetime assignment

--- osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
+++ osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
@@ -52,10 +52,7 @@
         protected override void FreeAfterUse()
         {
             ClearTransforms();
-
             Alpha = 1;
-            LifetimeStart = double.MinValue;
-            LifetimeEnd = double.MaxValue;

             base.FreeAfterUse();
         }


Bring up to date.

Bring up to date.

--- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -103,16 +103,11 @@

                 if (tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
                 {
-                    var allSamples = new List<List<SampleInfo>>();
-
+                    List<List<SampleInfo>> allSamples;
                     if (curveData != null)
-                    {
-                        allSamples.Add(curveData.HeadSamples);
-                        allSamples.AddRange(curveData.RepeatSamples);
-                        allSamples.Add(curveData.TailSamples);
-                    }
+                        allSamples = curveData.RepeatSamples;
                     else
-                        allSamples.Add(samples);
+                        allSamples = new List<List<SampleInfo>> { samples };

                     int i = 0;
                     for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)


Add local `UserLookupCache` to avoid online requests being fired from multiplayer tests

--- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -11,6 +11,7 @@
 using osu.Framework.Screens;
 using osu.Framework.Testing;
 using osu.Game.Beatmaps;
+using osu.Game.Database;
 using osu.Game.Graphics.UserInterface;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Online.Rooms;
@@ -39,6 +40,9 @@
         private TestMultiplayer multiplayerScreen;
         private TestMultiplayerClient client;

+        [Cached(typeof(UserLookupCache))]
+        private UserLookupCache lookupCache = new TestUserLookupCache();
+
         [BackgroundDependencyLoader]
         private void load(GameHost host, AudioManager audio)
         {


Implemented MoveFile.

Implemented MoveFile.

--- Client.cs
+++ Client.cs
@@ -129,6 +129,26 @@
         }

         /// <summary>
+        /// Moves a file or folder to a new location.
+        /// </summary>
+        /// <param name="Path">Specifies the file or folder to be moved from relative to root.</param>
+        /// <param name="Destination">Specifies the destination path, including the new name for the file or folder,
+        /// relative to root.</param>
+        /// <returns>FileEntry for the moved file or folder.</returns>
+        public FileEntry MoveFile(string Path, string Destination)
+        {
+            List<QueryParameter> parameters = new List<QueryParameter>();
+
+            parameters.Add(new QueryParameter("root", session.AccessType));
+            parameters.Add(new QueryParameter("locale", session.Locale.ToString()));
+            parameters.Add(new QueryParameter("from_path", Path));
+            parameters.Add(new QueryParameter("to_path", Destination));
+
+            return new FileEntry((JsonDictionary)session.Request(RequestMethod.POST, RequestType.JSON,
+                session.FormatAPIServerUrl("/fileops/move"), parameters));
+        }
+
+        /// <summary>
         /// Retrieves file and folder metadata.
         /// </summary>
         /// <param name="Path"></param>


Make Type.IsDefined() attribute check non-recursive (speedup)

--- Mapper.cs
+++ Mapper.cs
@@ -107,7 +107,7 @@
 			//we need to have a proper look at what's really public at some point
 			//this will do for now

-			if (type.IsDefined (typeof (InterfaceAttribute), true))
+			if (type.IsDefined (typeof (InterfaceAttribute), false))
 				return true;

 			if (type.IsSubclassOf (typeof (MarshalByRefObject)))


add a few more tests

add a few more tests

--- csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs
+++ csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs
@@ -72,6 +72,30 @@
         }

         [Test]
+        public void BuiltinOptionsCanBeRetrieved()
+        {
+            // non-custom options (that are not extensions but regular fields) can only be accessed via descriptor.Options
+            var fileOptions = UnittestProto3Reflection.Descriptor.Options;
+            Assert.AreEqual("Google.Protobuf.TestProtos", fileOptions.CsharpNamespace);
+        }
+
+        [Test]
+        public void OptionPresenceCanBeDetected()
+        {
+            // case 1: the descriptor has no options at all so the options message is not present
+            Assert.IsNull(TestAllTypes.Descriptor.Options);
+
+            // case 2: the descriptor has some options, but not the one we're looking for
+            // HasExtension will be false and GetExtension returns extension's default value
+            Assert.IsFalse(UnittestProto3Reflection.Descriptor.Options.HasExtension(FileOpt1));
+            Assert.AreEqual(0, UnittestProto3Reflection.Descriptor.Options.GetExtension(FileOpt1));
+
+            // case 3: option is present
+            Assert.IsTrue(UnittestCustomOptionsProto3Reflection.Descriptor.Options.HasExtension(FileOpt1));
+            Assert.AreEqual(9876543210UL, UnittestCustomOptionsProto3Reflection.Descriptor.Options.GetExtension(FileOpt1));
+        }
+
+        [Test]
         public void ScalarOptions()
         {
             var d = CustomOptionOtherValues.Descriptor;


Add an overloaded RunAsync accepting ApplicationOptions + ResizableWindow=true by default


more nick plugging tweaks

more nick plugging tweaks

--- DataProcessing/Program.cs
+++ DataProcessing/Program.cs
@@ -201,7 +201,7 @@

                     foreach (var nick in nicks){
                         if (msg.Contains(nick)){
-                            if (!msg.Contains(nick + " ") && !msg.Contains(" " + nick))
+                            if (!msg.Contains(nick + " ") && !msg.Contains(" " + nick) && !msg.Contains(nick + ",") && !msg.Contains(nick + ":"))
                                 continue;
                             mentionedNick = nick;
                             containsNick = true;