telegramdesktop/tdesktop/dev • 7906be3 • 19 files, +85/-85
Change #if-ery to be more in line with cmake logic (LINUX = NOT WIN32 AND NOT APPLE)
#tdesktop
Change #if-ery to be more in line with cmake logic (LINUX = NOT WIN32 AND NOT APPLE)
#tdesktop
🫡3💅1
UnigramDev/Unigram/develop • c04e326 • 10 files, +217/-118
Build one parser or the other, never both
TdPointerParser was a flag that added the pointer parsers to the reader ones;
TdParsers is a choice between them - Reader, Pointer, or Both. The app has no
use for two parsers over one schema: it is 44,508 generated lines of .NET
Native compile time for a set nothing calls.
The property is what the generator reads, and it now emits either set or both.
The constants it pairs with, TD_READER_PARSER and TD_POINTER_PARSER, are what
the hand-written code reads, so ClientJson, Client and ClientService compile
exactly the half that was generated - the entry points, ParseObject, the
reading half of Utf8JsonExtensions, the interface overloads and their
implementations. The writing half stays in every mode: requests are serialized
with Utf8JsonWriter whichever parser was generated.
Both is Telegram.Benchmarks, whose whole argument is the two of them racing
over one corpus and agreeing field for field. Deleting the reader set from the
generator would delete the cross-check that says the pointer parsers are right,
so nothing is deleted - which also means Reader walks the app back in one edit.
Generated lines: Reader 149,712, Pointer 167,143, Both 211,651. Reader is still
byte for byte what shipped. All three compile - the throwaway project builds
Client.cs, ClientJson.cs and the generated file in each - except ClientService,
whose #if blocks nothing here can compile. The app stays on Both until the
pointer path has been built and run once.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 2e431bf • 4 files, +53/-3
Charge file handling separately from parsing
Files are re-entered through ClientResultHandler mid-payload, so a dictionary
lookup, a FileExists syscall the first time an id is seen and an
EventAggregator publish all happen inside the interval Record measures. At
startup a chat list is hundreds of files never seen before, and every bit of
that was being charged to the parser.
CommitFile is timed on its own, the rates are over the parse with it taken
out, and the total is still shown beside them - that one is what an update
really costs, the other is what compares to the benchmark.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • a812a15 • 4 files, +46/-2
Count the file existence checks separately too
FileExists is already GetFileAttributesExFromAppW, one metadata syscall behind
a WinRT hop - there is no faster call to swap in, so the only moves left are
making it rarer or moving it off the TDLib thread. Both depend on how many
there are, which nothing here knows: the check only runs for a file seen for
the first time whose download is already complete.
So it is counted and timed on its own before anything is redesigned around it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Build one parser or the other, never both
TdPointerParser was a flag that added the pointer parsers to the reader ones;
TdParsers is a choice between them - Reader, Pointer, or Both. The app has no
use for two parsers over one schema: it is 44,508 generated lines of .NET
Native compile time for a set nothing calls.
The property is what the generator reads, and it now emits either set or both.
The constants it pairs with, TD_READER_PARSER and TD_POINTER_PARSER, are what
the hand-written code reads, so ClientJson, Client and ClientService compile
exactly the half that was generated - the entry points, ParseObject, the
reading half of Utf8JsonExtensions, the interface overloads and their
implementations. The writing half stays in every mode: requests are serialized
with Utf8JsonWriter whichever parser was generated.
Both is Telegram.Benchmarks, whose whole argument is the two of them racing
over one corpus and agreeing field for field. Deleting the reader set from the
generator would delete the cross-check that says the pointer parsers are right,
so nothing is deleted - which also means Reader walks the app back in one edit.
Generated lines: Reader 149,712, Pointer 167,143, Both 211,651. Reader is still
byte for byte what shipped. All three compile - the throwaway project builds
Client.cs, ClientJson.cs and the generated file in each - except ClientService,
whose #if blocks nothing here can compile. The app stays on Both until the
pointer path has been built and run once.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 2e431bf • 4 files, +53/-3
Charge file handling separately from parsing
Files are re-entered through ClientResultHandler mid-payload, so a dictionary
lookup, a FileExists syscall the first time an id is seen and an
EventAggregator publish all happen inside the interval Record measures. At
startup a chat list is hundreds of files never seen before, and every bit of
that was being charged to the parser.
CommitFile is timed on its own, the rates are over the parse with it taken
out, and the total is still shown beside them - that one is what an update
really costs, the other is what compares to the benchmark.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • a812a15 • 4 files, +46/-2
Count the file existence checks separately too
FileExists is already GetFileAttributesExFromAppW, one metadata syscall behind
a WinRT hop - there is no faster call to swap in, so the only moves left are
making it rarer or moving it off the TDLib thread. Both depend on how many
there are, which nothing here knows: the check only runs for a file seen for
the first time whose download is already complete.
So it is counted and timed on its own before anything is redesigned around it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • 5db4b60 • 1 files, +0/-14
Drop the ENABLE_CALLS switch
Every configuration defined it, so the one place that read it - a #if !ENABLE_CALLS
block declaring a stand-in VoipDataSaving enum in the Telegram.Native.Calls
namespace - has never compiled. The real enum comes from the component's own IDL.
The DefineConstants half of this went in with 5ca91366f.
Not built.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 44284ff • 5 files, +5/-152
Hoist GetUserDefaultLocaleName into Locale
Four popups carried a byte-identical copy each: the constant, both P/Invoke
branches and the wrapper, for one call apiece from their
GetRegionalSettingsAwareDecimalFormatter. Locale is where a question about the
user's locale belongs, and it is already on every one of those call paths.
Both branches move across as they were, except that the .NET 9+ one now takes a
char* over a fixed buffer rather than a Span<char>. The source-generated P/Invoke
only marshals Span<T> where runtime marshalling is disabled, which is not where a
UWP app sits, and char is not blittable while it is enabled - so the span could
not cross either way. That branch had never been compiled before now.
GetRegionalSettingsAwareDecimalFormatter itself is still duplicated four times.
Builds on the .NET 10 project; the .NET Native branch is unchanged but not built.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Drop the ENABLE_CALLS switch
Every configuration defined it, so the one place that read it - a #if !ENABLE_CALLS
block declaring a stand-in VoipDataSaving enum in the Telegram.Native.Calls
namespace - has never compiled. The real enum comes from the component's own IDL.
The DefineConstants half of this went in with 5ca91366f.
Not built.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 44284ff • 5 files, +5/-152
Hoist GetUserDefaultLocaleName into Locale
Four popups carried a byte-identical copy each: the constant, both P/Invoke
branches and the wrapper, for one call apiece from their
GetRegionalSettingsAwareDecimalFormatter. Locale is where a question about the
user's locale belongs, and it is already on every one of those call paths.
Both branches move across as they were, except that the .NET 9+ one now takes a
char* over a fixed buffer rather than a Span<char>. The source-generated P/Invoke
only marshals Span<T> where runtime marshalling is disabled, which is not where a
UWP app sits, and char is not blittable while it is enabled - so the span could
not cross either way. That branch had never been compiled before now.
GetRegionalSettingsAwareDecimalFormatter itself is still duplicated four times.
Builds on the .NET 10 project; the .NET Native branch is unchanged but not built.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • b2ec787 • 1 files, +6/-0
Carrying both parsers costs compile time, not binary size
Three x64 bundles built the same way: Reader 77,228,975 bytes, Pointer
77,754,662, Both 77,749,472. Both and Pointer are within 5 KB, because ILC
strips the set nothing calls. The 526 KB between Reader and Pointer is the
pointer parsers being larger native code, the same 40% the line counts show.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 85ee4c2 • 3 files, +131/-19
Register CompositionTarget.Rendering per view
Verified by Fela: video and lottie both work in the call window again.
CsWinRT keeps one event source per statics object for the whole process, so a
second view's CompositionTarget.Rendering += never reaches add_Rendering - the
delegate is appended to the registration the first view made, and the handler runs
on that view's thread. Every secondary view's frame callback lands on the main
view's thread. Composition objects do not mind, being agile, which is why the call
blobs animated throughout; WriteableBitmap does mind, and DrawFrame died on
RPC_E_WRONG_THREAD.
CompositionTargetRendering registers through the ABI instead, so each view gets a
registration of its own. The statics object is a process-wide agile singleton -
measured, along with its IAgileObject - so the call runs on the calling thread and
registers there. Proved in a throwaway app first: in the same run, the projected
subscription fired on view 1's thread and the ABI one on view 2's.
Reported as https://github.com/microsoft/CsWinRT/issues/2524.
Also drops the marshalling branch added to RegisterRendering earlier, which was
aimed at the wrong cause and never once fired; the presenter's own _loader stays.
Left alone, and noted in the todo: CompositionVSync, VisualUtilities,
PremiumProgressBar, PremiumSlider and GiftCraftPopup still subscribe through the
projection. Same latent bug, invisible so far because they only touch Composition.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 388e015 • 8 files, +114/-66
Alias CompositionTarget rather than #if every call site
Fela's idea, and a better one: CsWinRT.cs already keeps the global usings, so the
stand-in goes in there and the twenty call sites stay written the way they were.
#if NET9_0_OR_GREATER
global using CompositionTarget = Telegram.Common.CompositionTargetImpl;
#else
global using CompositionTarget = Windows.UI.Xaml.Media.CompositionTarget;
#endif
Aliasing in both directions pays for itself twice: .NET Native binds to the real
type as before, and the alias settles the ambiguity with
Windows.UI.Composition.CompositionTarget, which is the only reason those call sites
spelled the namespace out. They lose the prefix and a comment explaining it, so the
net change is shorter code than before the port.
That also carries the fix to everything else that draws per frame -
CompositionVSync, VisualUtilities, PremiumProgressBar, PremiumSlider,
GiftCraftPopup - which until now was running on the first view's thread in any
secondary window and getting away with it only because Composition objects are
agile.
Rendered goes through the ABI too, via ICompositionTargetStatics3. Forwarding it to
the projection, as it was a commit ago, left one honest half and one broken half in
a type that exists to fix exactly that.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Carrying both parsers costs compile time, not binary size
Three x64 bundles built the same way: Reader 77,228,975 bytes, Pointer
77,754,662, Both 77,749,472. Both and Pointer are within 5 KB, because ILC
strips the set nothing calls. The 526 KB between Reader and Pointer is the
pointer parsers being larger native code, the same 40% the line counts show.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 85ee4c2 • 3 files, +131/-19
Register CompositionTarget.Rendering per view
Verified by Fela: video and lottie both work in the call window again.
CsWinRT keeps one event source per statics object for the whole process, so a
second view's CompositionTarget.Rendering += never reaches add_Rendering - the
delegate is appended to the registration the first view made, and the handler runs
on that view's thread. Every secondary view's frame callback lands on the main
view's thread. Composition objects do not mind, being agile, which is why the call
blobs animated throughout; WriteableBitmap does mind, and DrawFrame died on
RPC_E_WRONG_THREAD.
CompositionTargetRendering registers through the ABI instead, so each view gets a
registration of its own. The statics object is a process-wide agile singleton -
measured, along with its IAgileObject - so the call runs on the calling thread and
registers there. Proved in a throwaway app first: in the same run, the projected
subscription fired on view 1's thread and the ABI one on view 2's.
Reported as https://github.com/microsoft/CsWinRT/issues/2524.
Also drops the marshalling branch added to RegisterRendering earlier, which was
aimed at the wrong cause and never once fired; the presenter's own _loader stays.
Left alone, and noted in the todo: CompositionVSync, VisualUtilities,
PremiumProgressBar, PremiumSlider and GiftCraftPopup still subscribe through the
projection. Same latent bug, invisible so far because they only touch Composition.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 388e015 • 8 files, +114/-66
Alias CompositionTarget rather than #if every call site
Fela's idea, and a better one: CsWinRT.cs already keeps the global usings, so the
stand-in goes in there and the twenty call sites stay written the way they were.
#if NET9_0_OR_GREATER
global using CompositionTarget = Telegram.Common.CompositionTargetImpl;
#else
global using CompositionTarget = Windows.UI.Xaml.Media.CompositionTarget;
#endif
Aliasing in both directions pays for itself twice: .NET Native binds to the real
type as before, and the alias settles the ambiguity with
Windows.UI.Composition.CompositionTarget, which is the only reason those call sites
spelled the namespace out. They lose the prefix and a comment explaining it, so the
net change is shorter code than before the port.
That also carries the fix to everything else that draws per frame -
CompositionVSync, VisualUtilities, PremiumProgressBar, PremiumSlider,
GiftCraftPopup - which until now was running on the first view's thread in any
secondary window and getting away with it only because Composition objects are
agile.
Rendered goes through the ABI too, via ICompositionTargetStatics3. Forwarding it to
the projection, as it was a commit ago, left one honest half and one broken half in
a type that exists to fix exactly that.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • 068f474 • 1 files, +9/-4
Note that the alias covers every frame callback
The todo still said CompositionVSync and VisualUtilities were left on the broken
path; the alias in 6a2466fb3 took them with it, along with Rendered.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • ea510bd • 2 files, +0/-41
Removed unused code
UnigramDev/Unigram/develop • 8de24ae • 15 files, +33/-281
Make the app AOT-clean
The whole trim surface of the app was five warnings in two files, and both were
dead weight.
TypeCrosserGenerator is deleted: nothing called it. Its one call site was
[Conditional("DEBUG")], so Release stripped the call but still compiled the body,
and ILC saw Assembly.GetTypes() over the TDLib types. Note that Conditional still
binds the name, so #if DEBUG around the class does not compile - the reference has
to go with it, and the Compile entry in Telegram.csproj too, which is not globbed.
TypeContainerGenerator has no call sites at all, so #if DEBUG is enough for it, and
there is now a todo to rewrite it as a source generator. Its GetConstructors and
GetProperties are the last reflection left in the app.
Fourteen classes that cross the WinRT ABI are marked partial, which is CsWinRT1028:
without it the marshalling code cannot be generated at build time and falls back to
reflection, which is what AOT trims away. Two of them are LottieGen output, so
regenerating those icons will drop the keyword again.
PublishAot moves into Telegram.Modern.csproj. Passed on the command line it is a
global property, so it flows into the ProjectReference and Telegram.Generators -
netstandard2.0 - fails with NETSDK1207.
Compile verified on the modern project; the ILC run to confirm the warning count is
still to come. .NET Native untouched by all of this bar the two deletions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Note that the alias covers every frame callback
The todo still said CompositionVSync and VisualUtilities were left on the broken
path; the alias in 6a2466fb3 took them with it, along with Rendered.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • ea510bd • 2 files, +0/-41
Removed unused code
UnigramDev/Unigram/develop • 8de24ae • 15 files, +33/-281
Make the app AOT-clean
The whole trim surface of the app was five warnings in two files, and both were
dead weight.
TypeCrosserGenerator is deleted: nothing called it. Its one call site was
[Conditional("DEBUG")], so Release stripped the call but still compiled the body,
and ILC saw Assembly.GetTypes() over the TDLib types. Note that Conditional still
binds the name, so #if DEBUG around the class does not compile - the reference has
to go with it, and the Compile entry in Telegram.csproj too, which is not globbed.
TypeContainerGenerator has no call sites at all, so #if DEBUG is enough for it, and
there is now a todo to rewrite it as a source generator. Its GetConstructors and
GetProperties are the last reflection left in the app.
Fourteen classes that cross the WinRT ABI are marked partial, which is CsWinRT1028:
without it the marshalling code cannot be generated at build time and falls back to
reflection, which is what AOT trims away. Two of them are LottieGen output, so
regenerating those icons will drop the keyword again.
PublishAot moves into Telegram.Modern.csproj. Passed on the command line it is a
global property, so it flows into the ProjectReference and Telegram.Generators -
netstandard2.0 - fails with NETSDK1207.
Compile verified on the modern project; the ILC run to confirm the warning count is
still to come. .NET Native untouched by all of this bar the two deletions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram