tag:blogger.com,1999:blog-291539192024-03-27T17:08:09.038+00:00MonoTorrentA cross platform open source .NET Framework based BitTorrent Client written in C#Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.comBlogger154125tag:blogger.com,1999:blog-29153919.post-31072154747083801672011-04-19T16:44:00.003+01:002011-04-19T16:47:29.345+01:00Moonlight 4.0 SDK... it's nearly there!Three delicious screenshots.<br /><br />1) Writing some code.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-3zE0gVE7puI/Ta2uGIZTCQI/AAAAAAAAAeo/oD1uB9KxAsA/s1600/A.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 250px;" src="http://3.bp.blogspot.com/-3zE0gVE7puI/Ta2uGIZTCQI/AAAAAAAAAeo/oD1uB9KxAsA/s400/A.png" alt="" id="BLOGGER_PHOTO_ID_5597321332247431426" border="0" /></a><br />2) Launching from MonoDevelop:<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-ls8pU-wnTN8/Ta2uQNTb-CI/AAAAAAAAAew/1T5qZ5h46A4/s1600/B.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 215px;" src="http://3.bp.blogspot.com/-ls8pU-wnTN8/Ta2uQNTb-CI/AAAAAAAAAew/1T5qZ5h46A4/s400/B.png" alt="" id="BLOGGER_PHOTO_ID_5597321505363720226" border="0" /></a><br /><br />3) Debugging!<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-hJ3LuAzLoyI/Ta2uZNka-1I/AAAAAAAAAe4/lMP9W-P8myk/s1600/C.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 250px;" src="http://2.bp.blogspot.com/-hJ3LuAzLoyI/Ta2uZNka-1I/AAAAAAAAAe4/lMP9W-P8myk/s400/C.png" alt="" id="BLOGGER_PHOTO_ID_5597321660053781330" border="0" /></a><br />4) Profit?<br /><br />This should hopefully be available with the next MonoDevelop release :)Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com45tag:blogger.com,1999:blog-29153919.post-47072880990922798692011-03-04T11:13:00.003+00:002011-03-04T11:37:28.294+00:00Mono.Nat 1.1.0Quite a long time ago support for NAT-PMP devices was contributed to Mono.Nat. As I didn't own a NAT-PMP capable device, I had to trust that the code was good at it worked. From all reports, it did under most circumstances. However it had a couple of issues which prevented it from being enabled by default. Patches to fix these issues have finally been contributed and have now been merged into Mono.Nat and released!<br /><br />If you want to forward ports from your application via your upnp or nat-pmp capable routers, look no further, <a href="http://projects.qnetp.net/news/show/8">Mono.Nat is here</a>! There are also packages available from the <a href="https://build.opensuse.org/package/show?package=mono-nat&project=home%3Aalan_mcgovern">opensuse build service project</a> I have.<br /><br />If you have any issues, submit a bug report and I'll get on them!Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com104tag:blogger.com,1999:blog-29153919.post-66499637285380602752010-12-17T14:23:00.005+00:002010-12-17T14:27:31.073+00:00A picture is worth 1000 wordsTwo to three weeks ago if you ran this battery of garbage collection related tests in moonlight, they'd mostly be red. How things have changed :)<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_nFGxSFZnUc0/TQtysUiIU3I/AAAAAAAAAdg/eUktoaf05Ak/s1600/Work.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 237px; height: 400px;" src="http://3.bp.blogspot.com/_nFGxSFZnUc0/TQtysUiIU3I/AAAAAAAAAdg/eUktoaf05Ak/s400/Work.png" alt="" id="BLOGGER_PHOTO_ID_5551657071415939954" border="0" /></a>Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com87tag:blogger.com,1999:blog-29153919.post-3248519496131959882010-12-06T11:49:00.006+00:002010-12-06T16:18:51.779+00:00Writing a profiler for monoAs some of you may know, my day job is working on <a href="http://www.mono-project.com/Moonlight">Moonlight</a>, the FOSS implementation of Silverlight. This is a fairly complex piece of code as we have C++ interoperating with C# and Javascript, so managing the lifecycle of objects can be difficult. C++ refcounts, C# has an automatic garbage collector and Javascript... well, let's just say that it complicates things a *lot* when combined with the other two languages.<br /><br />One of the tricks we use to ensure that objects do not die early is to use a (normal) <a href="http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.gchandle%28VS.71%29.aspx">GCHandle</a> on our C# objects to ensure the garbage collector doesn't mark them as junk before they're definitely finished with in C++. However, we were having issues where some of our objects appeared to never ever get garbage collected even though they should be eligible. We suspected that excess GCHandles were causing it but we had no easy way of tracking this. Without detailed information on when and where the GCHandles were allocated, it would be next to impossible to track down where we were going wrong. After several aborted attempts to track this with Console.WriteLine I figured there must be a better way.<br /><br />How about a custom profiler for mono?<br /><br />Documentation on this is fairly sparse currently, so hopefully this will help. Thanks go to <a href="http://www.advogato.org/person/lupus/">Paolo Molaro</a> for helping me when I got stuck! This code is mostly copied/pasted from moonlight so it's C++ ish and also may contain some moonlight specific code.<br /><br /><span style="font-size:130%;">Step 1:</span><br />Open up the "mono/metadata/profiler.h" file and check out all the profiler hooks that are available. They are all documented in profiler.c, so open that if you want a description of how they all work. Take note of the ones you'll need. All I needed to do was track GCHandle allocations, so I just wanted this hook:<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>void mono_profiler_install_gc_roots (MonoProfileGCHandleFunc handle_callback, MonoProfileGCRootFunc roots_callback);<br /></code></pre><br /><br /><span style="font-size:130%;">Step 2:</span><br />I now know what function I need to provide and the data I want to gather, so it's time to create a struct to store my data. This is an opaque struct which will be passed to the mono runtime and passed back in every profiler hook, so you can stick whatever you want in it. I know I want to store stacktraces, allocated gchandles and a typename, so I'll start with just those. I'll also declare my prototype for my profiler hook here.<br /><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>struct _MonoProfiler {<br />const char *type_name;<br />GPtrArray *gchandles;<br />GPtrArray *stacktraces;<br />Moonlight::Mutex locker; /* used to ensure only one thread accesses the arrays */<br /><br />static void track_gchandle (_MonoProfiler *prof, int op, int type, uintptr_t handle, MonoObject *obj);<br />};<br /><br />typedef _MonoProfiler MonoProfiler;<br /></code></pre><br /><br /><br /><span style="font-size:130%;">Step 3:</span><br />Implementation time! First the constructor for the MonoProfiler. I use an environment variable to limit which types we store stacktraces for as they're expensive to generate and consume a lot of memory to store when you have 10,000s of them. The important functions are the last three. The first registers our profiler with the runtime. The second installs the hook so we can get notifications for gchandle allocations. The final call enables the gc_handle events in the profiler which makes the runtime invoke the hook we just registered.<br /><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>_MonoProfiler::_MonoProfiler ()<br />{<br /> type_name = g_getenv ("GCHANDLES_FOR_TYPE");<br /><br /> gchandles = g_ptr_array_new ();<br /> stacktraces = g_ptr_array_new_with_free_func (g_free);<br /> /* Register the profiler with mono */<br /> mono_profiler_install (this, NULL);<br /> /* Supply a function for the gc_roots hook */<br /> mono_profiler_install_gc_roots (track_gchandle, NULL);<br /> /* Enable gc_roots tracking in the profiler so our hook is invoked */<br /> mono_profiler_set_events (MONO_PROFILE_GC_ROOTS);<br />}<br /></code></pre><br /><br />Now all I need is the implementation for track_gchandle and I'm done!<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>void<br />MonoProfiler::track_gchandle (MonoProfiler *prof, int op, int type, uintptr_t handle, MonoObject *obj)<br />{<br /> // Ignore anything that isn't a strong GC handle (docs say type == 2 is a strong gchandle)<br /> if (type != 2)<br /> return;<br /><br /> prof->locker.Lock ();<br /><br /> GPtrArray *gchandles = prof->gchandles;<br /> GPtrArray *stacktraces = prof->stacktraces;<br /><br /> if (op == MONO_PROFILER_GC_HANDLE_CREATED) {<br /> // Add the GCHandle to this array<br /> g_ptr_array_add (gchandles, (gpointer) handle);<br /> // If the target of the gchandle is of the correct type, store its stack trace<br /> // Otherwise store NULL so that we can keep the index of the gchandle and corresponding<br /> // stack trace in sync.<br /> if (prof->type_name && !strcmp (prof->type_name, mono_class_get_name (mono_object_get_class(obj))))<br /> g_ptr_array_add (stacktraces, get_stack_trace ());<br /> else<br /> g_ptr_array_add (stacktraces, NULL);<br /> } else if (op == MONO_PROFILER_GC_HANDLE_DESTROYED) {<br /> // Walk our list of gchandles and when we find the index of the destroyed handle<br /> // remove the handle and corresponding stacktrace<br /> for (int i = 0; i < (int)gchandles->len; i++) {<br /> if (g_ptr_array_index (gchandles, i) == (gpointer) handle) {<br /> g_ptr_array_remove_index_fast (gchandles, i);<br /> g_ptr_array_remove_index_fast (stacktraces, i);<br /> break;<br /> }<br /> }<br /> }<br /><br /> prof->locker.Unlock ();<br />}<br /></code></pre><br /><br /><span style="font-size:130%;">Step 4:</span><br />Profit! When you're ready, you can iterate over the gchandle array and pull out whatever statistics you want. For example here is the code I use to iterate the gchandles array and work out how many instances of each object type there are.<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>void<br />accumulate_g_ptr_array_by_type (gpointer data, gpointer user_data)<br />{<br /> // The hashtable I passed in as the user_data in g_ptr_array_foreach<br /> // which i am using to link a type name to a count<br /> GHashTable *by_type = (GHashTable*) user_data;<br /><br /> // Get the MonoObject from the gchandle.<br /> MonoObject *ob = mono_gchandle_get_target (GPOINTER_TO_INT (data));<br /><br /> // Get the type name from the mono_object<br /> const char *name = mono_class_get_name (mono_object_get_class(ob));<br /><br /> // Find out how many instances we have already<br /> int count = GPOINTER_TO_INT (g_hash_table_lookup (by_type, name)) + 1;<br /><br /> // Update the hashtable with an incremented count.<br /> g_hash_table_insert (by_type, name, GINT_TO_POINTER (count));<br />}<br /></code></pre><br /><br />I run that hashtable through another function to order to sort the list by the number of each type of object allocated so I can have a nicely formatted list as follows:<br /><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>1 instances GCHandled of type Surface<br />1 instances GCHandled of type Deployment<br />4 instances GCHandled of type NameScope<br />20 instances GCHandled of type ControlTemplate<br />218 instances GCHandled of type MonoType<br />3985 instances GCHandled of type Uri<br /></code></pre><br /><br />Needless to say, it's now immediately obvious to me that Uris are being GChandled and never freed, so i just have to enable tracing of their stacktraces and I can see exactly where we allocated the handle and from there figure out why we haven't freed it.Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com150tag:blogger.com,1999:blog-29153919.post-58415013214556592472010-10-02T19:38:00.004+01:002010-10-02T19:50:27.766+01:00MonoTorrent Lives! (on github)The mono project, who've been hosting MonoTorrent since day 1, recently decided to host all their code at <a href="http://www.github.com">github</a>. As such, <a href="http://github.com/mono/monotorrent">MonoTorrent now lives in github</a>! I took this opportunity to rename the module from 'bitsharp' to 'monotorrent' to avoid confusing people.<br /><br />So what does this mean? Well, it means it's trivial for you to commit patches now! All you do is set up a github account, fork monotorrent, commit your changes to your fork and issue a pull request. I'll then automagically be informed of your changes and can click a button to either merge them directly into monotorrent or reject them (with an explanation). No more emailing patches or attaching them to forum posts. Pretty nifty, eh?<br /><br />So get forking and lets see how great we can make MonoTorrent!Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com22tag:blogger.com,1999:blog-29153919.post-70664744917817569062010-06-12T14:44:00.002+01:002010-06-12T15:32:56.441+01:00Hackwee Day IV - The final showdownYesterday was a bit of an anti-climax codewise. Pretty much everything I had planned on doing I had completed by midday. I then had a nice long chat with <a href="http://www.lamalex.net/">lamalex</a> about what I had to do to get ipod support into his udev branch and hashed out a rough idea about how to upstream these changes. I believe he's going to do a bit of work over the next week or so and once that's complete I'll be able to upstream the remainder of my patches.<br /><br />So at the end of the 4 days the state of things is:<br /><br />Device support:<br />1) Hotplugging works<br />2) Syncing music to and from the device works<br />3) Removing music from the device works<br />4) Basic properties can be read about the device and displayed in banshee<br />5) Playlists aren't supported yet but should be fairly simple to do<br /><br />Code written:<br />1) I've upstreamed my patches for libgpod-sharp<br />2) I spoke with lamalex about upstreaming the rest of my work there.<br />3) There are now <a href="https://build.opensuse.org/project/show?project=home%3AFunkyM%3Aiphone">packages available</a> for openSUSE 11.2 courtesy of FunkyM which can be used to provide most of the required packages.<br />4) There are still some <a href="https://launchpad.net/gudev-sharp">bleeding edge libraries</a> required which aren't packaged anywhere. These will have to be packaged.<br />5) I think the best way to support the libgpod based iDevice would be to create a new addin so that you can run both the old addin and the new addin in parallel as some of the required libraries for libgpod based iDevice support are very new and not widely available as of yet. This will take a little bit of time.<br /><br />I'm off on holidays next week so when I get back I'll start pushing my remaining work upstream. Once that's done it's just the boring process of streamlining the build to remove the dozen manual steps which are currently required :) All in all, it's been a successful hackweek. But as with all good hacks, the 5:1 rule applies. For every day spent adding functionality, I'll need 5 to make it stable and usable for everyone.<br /><br />I have to thank <a href="http://cfergeau.blogspot.com/">teuf</a> again for all the help over the last few days (and for libgpod itself!), Nathaniel McCallum for the awesome start to the .NET bindings for libgpod, <a href="http://www.lamalex.net/">Alex Launi</a> for the banshee gio/udev work which saved me a lot of time and also for sparing the time yesterday to talk about how I can get my work upstreamed.<br /><br />If I've forgotten anyone or anything, sorry! But it's been a hectic week and it was hard enough keeping track of all the packages and patches I had floating around, never mind everyone I was talking to :)<br /><br />I'll post again when I get everything upstreamed and I'll have a nice set of instructions for anyone wishing to test out their iDevice with banshee.Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com9tag:blogger.com,1999:blog-29153919.post-62851654555003885432010-06-10T22:25:00.002+01:002010-06-10T22:38:12.739+01:00Hackweek Day III - The coffee shortageToday was a slow but interesting day (again). I took all my banshee work and rebased it on top of <a href="http://www.lamalex.net/">lamalexs</a> work to <a href="http://gitorious.org/~lamalex/banshee/lamalex-udev">bring udev/gio support</a> to banshee. This saved me having to write my own backend, but it meant that once again I had to start upgrading core parts of my OS. Luckily nothing blew up this time!<br /><br />So after a few hours hacking and pulling my hair out because seemingly simple commands were failing (they still are failing, i just worked around it) I now have proper detection implemented. There are no more hardcoded horrible hacks :) Anyone with the right pre-requisites and the udev branch of banshee with my patch can have basic iDevice support (tracks only).<br /><br />This is awesome!<br /><br />This will not be pushed to the main banshee repository as it depends on udev/gio to be useful. I will instead push my patches upstream to lamalex and his udev branch. All that has to be done then is to push the udev work to mainline banshee and everyone can enjoy full iDevice support.<br /><br />Tomorrow I hope to get in touch with lamalex and iron out a few bugs I've experienced and then upstream my work to him. My bugfixes to the <a href="http://gitorious.org/~teuf/libgpod/teuf-sandbox/commits/mono">libgpod-sharp bindings</a> have already been upstreamed and should be merged with the main codebase very soon.Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com8tag:blogger.com,1999:blog-29153919.post-66965066308790993122010-06-09T22:06:00.003+01:002010-06-09T22:57:42.275+01:00Hackweek Day II - Attack of the codeToday has been reasonably productive. I found some bugs in the libgpod bindings which were fairly major and implemented some more features in banshee.<br /><br />First the fun bug. The libgpod bindings did the normal thing of defining a managed struct which mirrored the native struct that libgpod uses. This means we can do a trivial byte copy of the unmanaged memory into one of our managed structs and trivially access the information in a safe way. This is all well and good right up until you realise that when you update a property on that struct, you are not actually changing the value of the unmanaged memory. Essentially you have two copies of the same data which are disconnected - one in managed memory and one in native memory. Updating one copy is not reflected in the other and there's no easy way to keep them synced without the risk of losing data.<br /><br />After a bit of brainstorming with <a href="http://blog.neteril.org/">Jeremie</a>, we came up with a rather neat and nifty solution. Take this struct as an example:<br /><br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>struct NativeDate {<br /> public int Day;<br /> public int Month;<br /> public int Year;<br />}<br /></code></pre><br /><br />If native code allocates one of those and passes it to .NET as an IntPtr, there is a trivial way to map this to managed code without requiring a copy of the data.<br /><br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>unsafe class Date {<br /> IntPtr Native {<br /> get; set;<br /> }<br /><br /> public int Day {<br /> get { return ((NativeDate *)Native)->Day; }<br /> set { ((NativeDate *) Native)->Day = value; }<br /> }<br /> public int Month {<br /> get { return ((NativeDate *) Native)->Month; }<br /> set { ((NativeDate *) Native)->Month = value; }<br /> }<br /><br /> public int Year {<br /> get { return ((NativeDate *) Native)->Year; }<br /> set { ((NativeDate *) Native)->Year = value; }<br /> }<br /><br /> public Date (IntPtr native)<br /> {<br /> Native = native;<br /> }<br />}<br /></code></pre><br /><br />All we do is use a bit of unsafe code to take the pointer that we got from native code and cast it to a NativeStruct* and read or write the data as appropriate. Now both managed land and unmanaged land are working off the same hunk of memory so they can never get out of sync.<br /><br />Now for the features! Banshee can now sync music to my iPhone and remove it aswell! This means all the basic stuff that's required for basic syncing is supported now. It took a lot longer than it should've to get this working because my iPhone wasn't actually set up right. There was a command (i can't remember the name of it now) which should've been run automatically to populate iTunes_Control/Device with some required information. This was never run for some bizarre reason (yay for bleeding edge) which meant nothing I did could ever actually update the database.<br /><br />However once I got that setup, things progressed pretty rapidly. There's one major feature left which is automatic detection when the device is mounted. If i can get this done tomorrow i'll try to upstream everything and get people testing this by the weekend (or friday!).Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com6tag:blogger.com,1999:blog-29153919.post-19662266011723579212010-06-08T23:03:00.002+01:002010-06-08T23:27:32.536+01:00Hackweek V - Day 1So at the end of Day 1, things have progressed well enough. I managed to get all my requirements installed (just about!) though to do it I needed to install older versions of some of the ipod stack. Hopefully this won't bite me in the ass as the week progresses ;)<br /><br />So a brief synopsis of the state of the world as I found it after one day:<br /><br />1) Things are still very bleeding edge. On opensuse you still need just the right packages and svn checkouts to even begin to get things working. Nothing you need is in the standard repositories, I had to use the awesome <a href="http://en.opensuse.org/Build_Service">opensuse build service</a> to provide some packages (gvfs >= 1.5.1, libplist, umuxd) and had to compile just the right version of libimobiledevice manually. Not a pleasant experience.<br /><br />2) Once all that horribleness is done, actually detecting the ipod in banshee is difficult. HAL is useless. You need either gio# or udev to be able to detect the device when it's plugged in. This puts further requirements on your distro to ship the latest goodies. I worked around this by hardcoding my iphones mount point for now ;)<br /><br />3) Banshee is good to work with. Integrating DAPs with banshee is easier than it used to be. Hopefully things will simplify the more I delve into things and learn what's available. Some things in the existing code I hope are only there for historical reasons and can be removed with this rewrite ;)<br /><br />4) So far I have two big checkpoints: Banshee can <a href="http://twitpic.com/1v0cpw">load the tracks on my iphone</a> and it can also <a href="http://twitpic.com/1v1toh">import them to its collection</a>. I'm in the process of allowing you to copy tracks to the device, but I hit a few issues with libgpod which have yet to be investigated.<br /><br />So things are looking good for the first day. Hopefully day 2 will be full of interesting developments. Maybe I'll get playlist syncronisation working, maybe I'll get track uploading working. Who knows!Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com4tag:blogger.com,1999:blog-29153919.post-29415780317777124452010-06-04T17:32:00.004+01:002010-06-04T20:40:24.270+01:00Hackweek VNext week is Hackweek V in Novell, the week in which we can work on whatever we want. This year, my <a href="https://features.opensuse.org/309693">hackweek proposal</a> is to give banshee a new ipod addin based on <a href="http://www.gtkpod.org/libgpod/">libgpod</a>. This should fix <a href="https://bugzilla.gnome.org/show_bug.cgi?id=553311">some</a> <a href="https://bugzilla.gnome.org/show_bug.cgi?id=506537">of</a> <a href="https://bugzilla.gnome.org/show_bug.cgi?id=615476">banshees</a> <a href="https://bugzilla.gnome.org/show_bug.cgi?id=532581">bugs</a>.<br /><br />While working on this I'll try live <a href="http://twitter.com/amcgovern">tweet</a> as I hack so if you're interested, do tune in from tuesday morning onwards (GMT). Mondays a bank holiday so I'm going to have to complete my hack in 4 days.<br /><br />If you can, do give the proposal a +1. I've no idea what affect it'll have but do it anyway! ;)<br /><br />EDIT: Link the correct hackweek proposal. Whoopsie :)Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com10tag:blogger.com,1999:blog-29153919.post-6231842633111852572010-05-17T23:02:00.002+01:002010-05-17T23:25:07.774+01:00Performance benchmarks... or lack thereofA friend of mine was over the other day and I was showing him this cool website I had recently heard of, <a href="http://www.grooveshark.com">grooveshark</a>. So after admiring it for a little while he came out with "pity it's written in flash". So of course I asked the obvious question "Why?" and got the obvious answer "Because it's inefficient and sucking up all your CPU". He popped open 'top' and sure enough it was using 35% of my cpu, his point was proven.<br /><br />However, that is completely and utterly irrelevant. This does not make flash a bad technology nor does it make grooveshark a badly written application. Maybe 35% cpu usage is just the cost of running in the browser. It's like the old complaint against firefox, "Firefox uses too much memory". By itself that means absolutely nothing. It's nonsense. For that to have any real meaning you'd have to compare firefox to another browser doing the same task. A legitimate complaint might be "Firefox uses 3 times more memory than opera when I view www.example.com, 300 mb versus 100mb". An arbitrary decision as to what amounts to be "too much" is useless without a reference. This is exactly what far too many people are doing to flash.<br /><br />Sure, grooveshark uses more CPU and memory than banshee, but banshee isn't running inside firefox. It annoys me that people make such arbitrary decisions and try to pass off as fact that flash is inherently bad and HTML5 is inherently so much better and yet have no reference to compare against. I haven't seen anything even close to the complexity of grooveshark written using html5. So before you get on your high horse decrying all plugins, show me a comparable application which uses $COMPETING_TECHNOLOGY_OF_CHOICE and performs measurably better.<br /><br /></rant>Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com16tag:blogger.com,1999:blog-29153919.post-68773952751678924132010-05-10T00:56:00.006+01:002010-05-10T01:37:44.993+01:00Zencomic - increasing happiness since nineteen digity twoToday I had some spare time and decided to <a href="http://git.neteril.org/zencomic/diff/GarfieldMinusGarfieldComicAddin/GarfieldMinusGarfieldComicAddin.cs?id=5a9b662c37b7faa1dc36af212a5b94a3492363b8">hack together a quick addin</a> for <a href="http://blog.neteril.org/2010/05/09/zencomic-0-3-holy-crepe/">Zencomic</a>. I choose garfield minus garfield as I feel it reflects my life pretty closely in some aspects (not the manic depressive aspects though ;) ).<br /><br />One of the downsides of working with a distributed team is that everyone starts work at a different time. On some of the quieter days when people are away on holidays, I'm sometimes the only hacker actively working on <a href="http://www.mono-project.com/Moonlight">Moonlight</a> in the wee small hours of 9am UTC. By the time the Americans wake up, I've sometimes left a nice little monologue in the IRC channel detailing my frustrations and successes during the morning.<br /><br />Anyone, now for the obligatory screenshot<br /><br /><span style="display: block;" id="formatbar_Buttons"><span class="on" style="display: block;" id="formatbar_CreateLink" title="Link" onmouseover="ButtonHoverOn(this);" onmouseout="ButtonHoverOff(this);" onmouseup="" onmousedown="CheckFormatting(event);FormatbarButton('richeditorframe', this, 8);ButtonMouseDown(this);"></span></span><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 399px; height: 206px;" src="http://1.bp.blogspot.com/_nFGxSFZnUc0/S-dMO66E79I/AAAAAAAAAWs/HHmOFG95FTA/s400/hack+of+the+day.png" alt="" id="BLOGGER_PHOTO_ID_5469424091679485906" border="0" /><br />The addin was hacked together over the course of 30 minutes. It should be able to open any garfield-garfield comic, but if you find a particular one it fails on do let me know. Also, please don't suggest I switch my beautiful string splitting code into a Regex unless you plan on writing it yourself ;)<br /><br />UPDATE: <a href="http://git.neteril.org/zencomic/commit/?id=2f4b69cfa567e7ab19ed3cb71274ddaa50760310">I just fixed the last known issue</a>. The addin now detects exactly how many pages of g-g goodness there are rather than being hardcoded to assume 101 pages (the current number).Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com3tag:blogger.com,1999:blog-29153919.post-26222487927019180152010-05-05T10:08:00.002+01:002010-05-05T10:09:20.521+01:00hapy birfday 2 me<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://icanhascheezburger.files.wordpress.com/2008/07/funny-pictures-cat-wonders-why-the-food-is-on-fire.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 500px; height: 375px;" src="http://icanhascheezburger.files.wordpress.com/2008/07/funny-pictures-cat-wonders-why-the-food-is-on-fire.jpg" alt="" border="0" /></a><br /><div style="text-align: center; font-style: italic;">My confusion this morning was evident<br /></div>Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com6tag:blogger.com,1999:blog-29153919.post-58178014087438586222010-01-10T22:36:00.004+00:002010-01-10T22:58:20.881+00:00FOSDEM 2010So Ruben has been working on getting a <a href="http://weblog.savanne.be/188-mono-fosdem-2010-schedule">bunch of talks organised</a> for the Mono room in FOSDEM and I've been lucky enough to get a slot. I have a 30 minute slot, so I figured that would be long enough to talk about 2 things properly and then take questions.<br /><br />I've decided that I want to talk about two things.<br /><br /><ol><li>The piece picker algorithm</li><li>How i handle threading</li></ol>The piece picking code handles which segment of the file will be downloaded next. It has gone through several iterations, each time adding new features. Eventaully the whole class became so hard to maintain and test that I just had to rewrite things from the ground up using a new approach. This might give some people ideas on how to tackle problems in their own projects but even if it doesn't, it should still be interesting ;)<br /><br />MonoTorrent handles a lot of concurrent connections. Using one thread per connection just wouldn't scale yet using plain asynchronous sockets and locking has huge potential for deadlocks and race conditions. Getting this right took well over a year - probably due to my lack of experience at this - but right now I think I've hit the winning threading solution. This part of the talk should be useful to anyone who's worked on an app which does a lot of socket operations. Maybe I'll wake up in a few months time and see a dozen apps using that technique.<br /><br />Anyway, feel free to drop in for my talk (and all the others!). If you have any questions about any other aspects of MonoTorrent, or programming in general, feel free to find me and ask.Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com6tag:blogger.com,1999:blog-29153919.post-53960388478970535062009-12-20T21:21:00.007+00:002009-12-21T11:41:02.669+00:00Expression Trees - serializing your data<span style="font-weight: bold;">Update:</span> Just to clarify - the code snippets below are under the MIT/X11 license.<br /><br />I spent a few hours over the weekend writing a binary serializer using expression trees. I wanted to see how things would look using the new features available in .NET 4.0. My requirements were pretty simple:<br /><br />1) Serialize all public properties in a type or a subset of them<br />2) Control the order in which they're serialized - sometimes you need to interop with an existing and you must write your data in a specific order<br />3) Control how a primitive is converted - Do you need to write value types in big endian, little endian, middle endian?<br />4) Easy to use API.<br /><br />So lets start with the API. This is what I was hoping to use:<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>public class Secondary<br />{<br />public int First { get; set; }<br />public int Second { get; set; }<br />public int Third { get { return First + Second; } }<br />}<br /><br />public class MyClass<br />{<br />public byte ByteProp { get; set; }<br />public short ShortProp { get; set; }<br />public int IntProp { get; set; }<br />public long LongProp { get; set; }<br />public string StringProp { get; set; }<br />}<br /><br />static void Main(string[] args)<br />{<br />// Register a message so that all public fields will be serialized<br />Message.Register<MyClass>();<br /><br />// Register a message so that only some fields are serialized and<br />// they are serialized in the specified order<br />Message.Register<Secondary>(<br /> d => d.Second,<br /> d => d.First<br />);<br /><br />// Create a stream to serialize the data to<br />Stream s = new MemoryStream();<br />var message = new MyClass {<br /> IntProp = 1,<br /> LongProp= 2,<br /> ByteProp= 3,<br /> ShortProp = 4,<br /> StringProp = "Hello World"<br />};<br /><br />// Encode the message to the stream<br />MessageEncoder.Encode(message, s);<br /><br />// Rewind the stream and then decode the message<br />s.Position = 0;<br />var decoded = MessageDecoder.Decode<MyClass>(s);<br />}<br /></code></pre><br />It's pretty standard stuff. You can work with the standard serializer logic (serialize properties alphabetically) by registering an object without specifying any specific properties or you can customise which properties are serialized. This could also be done using attributes, but using attributes to control the order in which properties are serialized would be more error prone than the above.<br /><br />Firstly, sometimes you need to write your data in big endian, others you need little endian. Sometimes you won't care. What you need is to be able to control this:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>MessageEncoder.RegisterPrimitiveEncoder<int>((value, stream) => {<br /> stream.Write(BitConverter.GetBytes(value));<br />});<br /></code></pre><br />It's simple. Any type which can be directly converted to an array of bytes is classified as a 'primitive'. Each primitive can have an encoder/decoder pair registered as above.<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>public static class MessageEncoder<br />{<br />static Dictionary<Type, Delegate> encoders;<br />static Dictionary<Type, Delegate> primitives;<br /><br />static MessageEncoder()<br />{<br /> encoders = new Dictionary<Type, Delegate>();<br /> primitives = new Dictionary<Type, Delegate>();<br /> RegisterPrimitiveEncoders();<br />}<br /><br />static void RegisterPrimitiveEncoders()<br />{<br /> RegisterPrimitiveEncoder<byte>((value, stream) =><br /> stream.WriteByte(value)<br /> );<br /><br /> RegisterPrimitiveEncoder<short>((value, stream) =><br /> stream.Write(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(value)))<br /> );<br /><br /> RegisterPrimitiveEncoder<int>((value, stream) =><br /> stream.Write(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(value)))<br /> );<br /><br /> RegisterPrimitiveEncoder<long>((value, stream) =><br /> stream.Write(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(value)))<br /> );<br /><br /> var intWriter = (Action<int, Stream>)primitives[typeof (int)];<br /> RegisterPrimitiveEncoder<string>((value, stream) => {<br /> var buffer = Encoding.UTF8.GetBytes(value);<br /> intWriter(buffer.Length, stream);<br /> stream.Write(buffer);<br /> });<br />}<br /><br />public static void RegisterPrimitiveEncoder<T>(Action<T, Stream> encoder)<br />{<br /> primitives [typeof (T)] = encoder;<br />}<br /><br />public static void RegisterMessage<T>(params Expression<Func<T, object>>[] properties)<br />{<br /> RegisterMessage<T>(properties.Select(p => p.AsPropertyInfo ()));<br />}<br /><br />public static void RegisterMessage<T>(IEnumerable<PropertyInfo> properties)<br />{<br /> var propertyEncoders = new List<Expression>();<br /><br /> // The encode function takes an instance of the class we're decoding and the Stream<br /> // which we should write the data to.<br /> ParameterExpression source = Expression.Parameter(typeof(T), "source_param");<br /> ParameterExpression stream = Expression.Parameter(typeof(Stream), "stream");<br /><br /> // For each property, get the encoder which will convert the value of the property to a byte[]<br /> // which can be written to the stream.<br /> foreach (var property in properties) {<br /> // Get the encoder for this property type<br /> var action = primitives[property.PropertyType];<br /> // Create a var which holds the Action <T, Stream> which encodes the data to the stream<br /> Expression converter = Expression.Constant(action, action.GetType ());<br /> // Invoke the encoder passing the value of the property and the 'stream'<br /> Expression invoker = Expression.Invoke(converter, Expression.Property(source, property), stream);<br /> // Add the encoder for this property to the list.<br /> propertyEncoders.Add(invoker);<br /> }<br /><br /> // Create an expression block which will execute each of the encoders one by one<br /> Expression block = Expression.Block(propertyEncoders);<br /> encoders.Add(typeof(T), Expression.Lambda<Action<T, Stream>>(<br /> block,<br /> source,<br /> stream<br /> ).Compile());<br />}<br /><br />public static void Encode<T>(T message, Stream s)<br />{<br /> var encoder = (Action<T, Stream>)encoders[typeof (T)];<br /> encoder (message, s);<br />}<br />}<br /></code></pre><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>using System;<br />using System.Collections.Generic;<br />using System.Linq;<br />using System.Text;<br />using System.Linq.Expressions;<br />using System.Reflection;<br />using System.Net;<br />using System.IO;<br /><br />namespace Encoder<br />{<br />public static class MessageDecoder<br />{<br /> static Dictionary<Type, Delegate> decoders;<br /> static Dictionary<Type, Delegate> primitives;<br /><br /> static MessageDecoder()<br /> {<br /> decoders = new Dictionary<Type, Delegate>();<br /> primitives = new Dictionary<Type, Delegate>();<br /> RegisterDefaultDecoders();<br /> }<br /><br /> static void RegisterDefaultDecoders()<br /> {<br /> RegisterPrimitiveDecoder<byte>((s) => {<br /> var val = s.ReadByte();<br /> if (val == -1)<br /> throw new EndOfStreamException();<br /> return (byte)val;<br /> });<br /><br /> RegisterPrimitiveDecoder<short>((s) => IPAddress.NetworkToHostOrder (s.ReadShort()));<br /> RegisterPrimitiveDecoder<int>(s => IPAddress.NetworkToHostOrder (s.ReadInt()));<br /> RegisterPrimitiveDecoder<long>(s => IPAddress.NetworkToHostOrder (s.ReadLong()));<br /><br /> var intDecoder = (Func<Stream, int>)primitives[typeof(int)];<br /> RegisterPrimitiveDecoder<string>(s => {<br /> var length = intDecoder(s);<br /> var buffer = new byte[length];<br /> s.Read(buffer, 0, buffer.Length);<br /> return Encoding.UTF8.GetString(buffer);<br /> });<br /> }<br /><br /> public static void RegisterPrimitiveDecoder<T>(Func<Stream, T> decoder)<br /> {<br /> primitives.Add(typeof(T), decoder);<br /> }<br /><br /> public static void RegisterMessage<T>(params Expression<Func<T, object>>[] properties)<br /> {<br /> RegisterMessage<T>(properties.Select(d => d.AsPropertyInfo()));<br /> }<br /><br /> public static void RegisterMessage<T>(IEnumerable<PropertyInfo> properties)<br /> {<br /> var propertyDecoders = new List<Expression>();<br /><br /> // The decode function takes an instance of the class we're decoding and the Stream<br /> // containing the data to decode.<br /> ParameterExpression source = Expression.Parameter(typeof(T), "source_param");<br /> ParameterExpression stream = Expression.Parameter(typeof(Stream), "stream");<br /><br /> // For each property, get the primitive decoder which will read data from the stream and<br /> // return a value of the correct type.<br /> foreach (var property in properties) {<br /> var action = primitives[property.PropertyType];<br /> // Create a var which holds the Func <Stream, T> which decodes the data from the stream<br /> Expression decoder = Expression.Constant(action, action.GetType());<br /> // Invoke the decoder passing 'stream' as the parameter<br /> Expression invoker = Expression.Invoke(decoder, stream);<br /> // Store the return value of the decoder in the property.<br /> Expression setter = Expression.Call(source, property.GetSetMethod(), invoker);<br /> // Add the decoder for this property to the list.<br /> propertyDecoders.Add(setter);<br /> }<br /><br /> // Create a block which will execute the decoders for all the fields one after another.<br /> Expression block = Expression.Block(propertyDecoders);<br /> decoders.Add (typeof (T), Expression.Lambda<Action<T, Stream>>(<br /> block,<br /> source,<br /> stream<br /> ).Compile ());<br /> }<br /><br /> public static T Decode<T>(Stream s) where T : class, new()<br /> {<br /> T t = new T();<br /> var decoder = (Action<T, Stream>)decoders[typeof(T)];<br /> decoder(t, s);<br /> return t;<br /> }<br />}<br />}<br /><br /></code></pre><br />The idea is quite simple. For each class we can generate an ideal serializer using expression trees which doesn't require boxing or casting. This way we can avoid the use of reflection when serializing objects and so avoid the performance penalties incurred that. The code above only handles the simple case where a class consists of primitive types (int, long, string) , though it'd be easy enough to extend it to support more complex scenarios.<br /><br />The serializer as you see it could not have been written with .NET 3.0. Some of the key components like BlockExpression were only introduced with .NET 4.0. If your object contains an array which needs to be serialized, you'll need the new IndexExpression too. Sure, it's possible to fake these using some anonymous delegates and Actions, but that's not pretty :)<br /><br />The total implementation is less than 170 LOC. I'd be willing to bet that with another 100 LOC you could support most constructs. If you're currently a heavy user of reflection to provide object serialization, it's time to update ;)Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com10tag:blogger.com,1999:blog-29153919.post-35381134075846385822009-12-15T00:13:00.002+00:002009-12-15T00:19:35.268+00:00New years resolutionsIt's tradition in quite a lot of countries to make a <a href="http://en.wikipedia.org/wiki/New_Year%27s_resolution">new years resolution</a> on the 1st of January. Most people forget about them within a few days or weeks. This year, I'll be making one I'm going to keep!<br /><br />I want to take part in a dancing [0] flash mob whether it's in this country or another.<br /><br /><object width="560" height="340"><param name="movie" value="http://www.youtube-nocookie.com/v/7EYAUazLI9k&hl=en_GB&fs=1&"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube-nocookie.com/v/7EYAUazLI9k&hl=en_GB&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="340"></embed></object><br /><br />What ideas do you have? Anything strange, interesting, unusual? Leave a comment and let me know, maybe you have a better idea than being part of a flash mob.<br /><br />[0] Me and dancing don't get on particularly well, so it'll be an interesting challenge ;)Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com16tag:blogger.com,1999:blog-29153919.post-5599239153197576912009-12-06T13:33:00.010+00:002009-12-06T16:06:58.651+00:00Yet another INotifyPropertyChanged with Expression Trees - Part 2<a href="http://monotorrent.blogspot.com/2009/12/yet-another-inotifypropertychanged-with_05.html">In my last post</a>, I described a method whereby you can implement INotifyPropertyChanged with zero performance overhead and near-zero boilerplate code. The only boilerplate left was the delegate you had to create to invoke the event:<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>public Book()<br />{<br /> // Boilerplate - eugh!<br /> Action<string> notify = (propertyName) => {<br /> var h = PropertyChanged;<br /> if (h != null)<br /> h(this, new PropertyChangedEventArgs(propertyName));<br /> };<br /><br /> author = new ChangeNotifier<string> (() => Author, notify);<br /> price = new ChangeNotifier<decimal> (() => Price, notify);<br /> quantity = new ChangeNotifier<int> (() => Quantity, notify);<br /> title = new ChangeNotifier<string> (() => Title, notify);<br />}<br /></code></pre><br />The entire point of my implementation was to avoid writing boilerplate, so this was slightly irritating. Unfortunately, there's no trivial way around the problem as the .NET framework really limits what you can do with events. The first thing you'd think of is "pass the actual object into the ChangeNotifier constructor and just raise the event that way". For example my constructors would change to:<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>new ChangeNotifier<string>(() => Author, this);<br /></code></pre><br />That's well and good, right up until you realise that it's impossible for one object to raise an event that's declared on another object.<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>public class A<br />{<br /> public event EventHandler MyEvent;<br />}<br /><br />public class B<br />{<br /> public void AccessEvent (A a)<br /> {<br /> // Invalid - you can't raise an event which is declared in another class<br /> a.MyEvent(this, EventArgs.Empty);<br /><br /> // Invalid - you can't copy the event either<br /> EventHandler h = a.MyEvent;<br /> h(this, EventArgs.Empty);<br /> }<br />}</code></pre>Another alternative would be to pass the event itself into the ChangeNotifier object:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>new ChangeNotifier<string> (() => </code><code>Author, PropertyChanged);<br /></code></pre>But this won't work because a <font style="font-style: italic;">copy</font> of the delegate list is created. That means if anyone adds event handlers later on, they won't be invoked when the property changes. So with that stuck firmly in my mind, I never gave much thought to removing that last remaining bit of boilerplate. That's about to change!<br /><br />What I really want is for my final implementation to look more like this:<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>public class Book : INotifyPropertyChanged<br />{<br /> public event PropertyChangedEventHandler PropertyChanged;<br /><br /> ChangeNotifier<string> author;<br /><br /> public string Author<br /> {<br /> get { return author.Value; }<br /> set { author.Value = value; }<br /> }<br /><br /> public Book()<br /> {<br /> author = ChangeNotifier.Create(() => Author, ????);<br /> }<br />}<br /></code></pre><br />That's short and sweet . The generic types should be automatically inferred, you shouldn't have to create the delegate to raise the event, it's beautiful! The only problem is to figure out what I should replace the question marks with. I need something that will allow me to get at the current list of event handlers from outside of the Book object, i.e. something along the lines of this:<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>Func<PropertyChangedEventHandler> getter = delegate { return PropertyChanged; };<br /></code></pre><br />Prettying it up a little, this is how my Book class looks:<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>public class Book : INotifyPropertyChanged<br />{<br /> public event PropertyChangedEventHandler PropertyChanged;<br /><br /> ChangeNotifier<string> author;<br /><br /> public string Author {<br /> get { return author.Value; }<br /> set { author.Value = value; }<br /> }<br /><br /> public Book()<br /> {<br /> author = ChangeNotifier.Create (() => Author, () => PropertyChanged);<br /> }<br />}<br /></code></pre><br />Beautiful! The more astute readers might notice a problem at this stage. Fine, the ChangeNotifier object can get the event list and raise the event, but it can't fill in the 'sender' - it has no reference to the 'book' object! Have no fear, it's already taken care of! The getter delegate has a reference to the book object (Delegate.Target), so we can fill everything in perfectly! The final implementation of the ChangeNotifier class is this:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>public static class ChangeNotifier<br />{<br /> public static ChangeNotifier<TValue> Create<TValue>(Expression<Func<TValue>> expression, Func<PropertyChangedEventHandler> notifier)<br /> {<br /> return new ChangeNotifier<TValue>(expression, notifier);<br /> }<br />}<br /><br />public class ChangeNotifier<TValue><br />{<br /> Func<PropertyChangedEventHandler> notifier;<br /> string propertyName;<br /> TValue value;<br /><br /> public TValue Value {<br /> get { return value; }<br /> set {<br /> if (!EqualityComparer<TValue>.Default.Equals(this.value, value)) {<br /> this.value = value;<br /> // Get the current list of registered event handlers<br /> // then invoke them with the correct 'sender' and event args<br /> PropertyChangedEventHandler h = notifier();<br /> if (h != null)<br /> h(notifier.Target, new PropertyChangedEventArgs(propertyName));<br /> }<br /> }<br /> }<br /><br /> public ChangeNotifier(Expression<Func<TValue>> expression, Func<PropertyChangedEventHandler> notifier)<br /> {<br /> if (expression.NodeType != ExpressionType.Lambda)<br /> throw new ArgumentException("Value must be a lamda expression", "expression");<br /> if (!(expression.Body is MemberExpression))<br /> throw new ArgumentException("The body of the expression must be a memberref", "expression");<br /><br /> MemberExpression m = (MemberExpression)expression.Body;<br /> this.notifier = notifier;<br /> this.propertyName = m.Member.Name;<br /> }<br />}<br /></code></pre>I have one final trick up my sleeve. Suppose you have a field (Progress) whose value is calculated based on other values (CurrentStep, TotalSteps) and you want to get Notifications whenever any of those fields changes, well, that's easy!<br /><br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>public class Worker : INotifyPropertyChanged<br />{<br /> public event PropertyChangedEventHandler PropertyChanged;<br /><br /> ChangeNotifier<int> currentStep;<br /> ChangeNotifier<int> totalSteps;<br /><br /> public int CurrentStep {<br /> get { return currentStep.Value; }<br /> set { currentStep.Value = value; }<br /> }<br /> public int TotalSteps {<br /> get { return totalSteps.Value; }<br /> set { totalSteps.Value = value; }<br /> }<br /> public double Progress<br /> {<br /> get { return (double)CurrentStep / TotalSteps; }<br /> }<br /><br /> public Worker()<br /> {<br /> Func<PropertyChangedEventHandler> notifier = () => PropertyChanged;<br /><br /> currentStep = ChangeNotifier.Create(() => CurrentStep, notifier);<br /> totalSteps = ChangeNotifier.Create(() => TotalSteps, notifier);<br /><br /> // A PropertyChanged notification will be created for Progress every time<br /> // either the CurrentStep *or* TotalSteps changes.<br /> ChangeNotifier.CreateDependent(<br /> () => Progress,<br /> notifier,<br /> () => CurrentStep,<br /> () => TotalSteps<br /> );<br /> }<br />}<br /></code></pre><br />And the new helper methods are:<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>public static class ChangeNotifier<br />{<br /> static string GetPropertyName(Expression expression)<br /> {<br /> while (!(expression is MemberExpression)) {<br /> if (expression is LambdaExpression)<br /> expression = ((LambdaExpression)expression).Body;<br /> else if (expression is UnaryExpression)<br /> expression = ((UnaryExpression)expression).Operand;<br /> }<br /> <br /> return ((MemberExpression)expression).Member.Name;<br /> }<br /><br /> public static void CreateDependent<TValue>(Expression<Func<TValue>> property, Func<PropertyChangedEventHandler> notifier, params Expression<Func<object>>[] dependents)<br /> {<br /> // The name of the property which is dependent on the value of other properties<br /> var name = GetPropertyName(property);<br /> // The names of the other properties<br /> var dependentNames = dependents.Select<Expression, string>(GetPropertyName).ToArray();<br /> <br /> INotifyPropertyChanged sender = (INotifyPropertyChanged)notifier.Target;<br /> sender.PropertyChanged += (o, e) => {<br /> // If one of our dependents changes, emit a PropertyChanged notification for our property<br /> if (dependentNames.Contains(e.PropertyName)) {<br /> var h = notifier();<br /> if (h != null)<br /> h(o, new PropertyChangedEventArgs (name));<br /> }<br /> };<br /> }<br /><br /> public static ChangeNotifier<TValue> Create<TValue>(Expression<Func<TValue>> expression, Func<PropertyChangedEventHandler> notifier)<br /> {<br /> return new ChangeNotifier<TValue>(expression, notifier);<br /> }<br />}<br /></code></pre><br />The only change is that I need to use a slightly more complicated method of getting the property name as it's possible for certain types to get wrapped in a ConvertExpression.Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com8tag:blogger.com,1999:blog-29153919.post-21937705311527106292009-12-05T14:44:00.001+00:002009-12-05T14:44:46.786+00:00Yet another INotifyPropertyChanged with Expression TreesThere are <a href="http://www.google.ie/#hl=en&source=hp&q=inotifypropertychanged+expression+trees&btnG=Google+Search&meta=&aq=0&oq=inotifypropertychanged+expression+trees&fp=460a3d52f02dd2cb">dozens of examples </a>out there showing you how to avoid having to refer to method names as strings when implementing INotifyPropertyChanged. The most important reason why you don't want to have to do this is because method names can get refactored but the hardcoded strings might be forgotten. No-one wants to end up getting a Changed notification for a property which doesn't exist.<br /><br />My issue with all these examples is that none of them thought far enough ahead. Fine, they all show you how refer to properties without using hardcoded strings but they still require you to write lots of boilerplate code to raise the PropertyChanged event - boilerplate you have to write for every property. What I want is to be able to declare all my properties like:<br /><br /><span style="font-family:monospace;"><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>public string Title {<br /> get { return title; }<br /> set { title = value; }<br />}<br /></code></pre></span><br />and yet still get my property change notifications. I also want this method to be reasonably high performance. I don't want every property change to have extra memory or CPU overhead as every developer expects that changing the value of a property will not do any complex calculations. So how can I accomplish this?<br /><br />To start off with, we can all tell that it's impossible to achieve the required behaviour using just the snippet above. We're going to have to add (at least) one additional level of indirection. That means I should be able to implement my requirements using code like:<br /><br /><span style="font-family:monospace;"><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>public string Title {<br /> get { return title.Value; }<br /> set { title.Value = value; }<br />}<br /></code></pre></span><br />The object 'title' must then contain all the logic required to raise the property changed notification. So what might this magical object look like?<br /><br /><span style="font-family:monospace;"><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>public class ChangeNotifier<TValue><br />{<br /> Action<string> notifyHandler;<br /> string propertyName;<br /> TValue value;<br /><br /> public TValue Value {<br /> get { return value; }<br /> set {<br /> if (!EqualityComparer<TValue>.Default.Equals(this.value, value)) {<br /> this.value = value;<br /> notifyHandler(propertyName);<br /> }<br /> }<br /> }<br /><br /><br /> public ChangeNotifier(Expression<Func<TValue>> expression, Action<string> notifyHandler)<br /> {<br /> if (expression.NodeType != ExpressionType.Lambda)<br /> throw new ArgumentException("Value must be a lamda expression", "expression");<br /> if (!(expression.Body is MemberExpression))<br /> throw new ArgumentException("The body of the expression must be a memberref", "expression");<br /><br /> MemberExpression m = (MemberExpression)expression.Body;<br /> this.propertyName = m.Member.Name;<br /> this.notifyHandler = notifyHandler;<br /> }<br />}<br /></code></pre></span><br />You're probably looking at this thinking "What the hell is this Expression<Func<TValue>> ? How do I even use that monstrosity?". Well... simples!<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>public class Book : INotifyPropertyChanged<br />{<br /> public event PropertyChangedEventHandler PropertyChanged;<br /><br /> ChangeNotifier<string> author;<br /> ChangeNotifier<decimal> price;<br /> ChangeNotifier<int> quantity;<br /> ChangeNotifier<string> title;<br /><br /> public string Author {<br /> get { return author.Value; }<br /> set { author.Value = value; }<br /> }<br /> public decimal Price {<br /> get { return price.Value; }<br /> set { price.Value = value; }<br /> }<br /> public int Quantity {<br /> get { return quantity.Value; }<br /> set { quantity.Value = value; }<br /> }<br /> public string Title {<br /> get { return title.Value; }<br /> set { title.Value = value; }<br /> }<br /><br /> public Book()<br /> {<br /> Action<string> notify = (propertyName) => {<br /> var h = PropertyChanged;<br /> if (h != null)<br /> h(this, new PropertyChangedEventArgs(propertyName));<br /> };<br /><br /> author = new ChangeNotifier<string> (() => Author, notify);<br /> price = new ChangeNotifier<decimal> (() => Price, notify);<br /> quantity = new ChangeNotifier<int> (() => Quantity, notify);<br /> title = new ChangeNotifier<string> (() => Title, notify);<br /> }<br />}<br /></code></pre><br />All that happens here is that when constructing the ChangeNotifier object, an Expression referencing the required Property is passed into the constructor, along with a delegate which will raise the PropertyChanged event. We parse that expression tree to retrieve the method name and store it. After that everything Just Works (tm) with little to no performance penalty. The days of writing boilerplate code for INotifyPropertyChanged are gone! You also have the benefit that you can't make a mistake writing the boilerplate code because you don't write it anymore!Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com12tag:blogger.com,1999:blog-29153919.post-73548865354848425122009-12-04T16:44:00.005+00:002009-12-04T17:19:59.465+00:00Can't you feel the Moonlight? Part deux<a href="http://monotorrent.blogspot.com/2009/12/cant-you-feel-moonlight.html">As I was saying yesterday</a>, the live version of the silverlight toolkit site didn't work right in moonlight. All the pretty charts rendered as you see them below, very empty.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_nFGxSFZnUc0/SxlALCJN7MI/AAAAAAAAATY/RXPdJeRC4T8/s1600-h/Fail.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 195px;" src="http://1.bp.blogspot.com/_nFGxSFZnUc0/SxlALCJN7MI/AAAAAAAAATY/RXPdJeRC4T8/s320/Fail.png" alt="" id="BLOGGER_PHOTO_ID_5411426985560632514" border="0" /></a>I figured that since a slightly older version worked near-flawlessly, surely I could fix the live version with only a few minor tweaks. It's not like the would've completely rewritten the Chart controls within the space of 1 release.<br /><br />I checked everything from DataBinding, to TemplateBinding, to Styles, to Measure/Arrange bugs and nothing was showing up as causing the issue. I finally narrowed it down to a bug in VisualStateGroup. For some reason the Name property was empty even though it was declared with a name in xaml.<br /><br /><a href="http://anonsvn.mono-project.com/viewvc/trunk/moon/class/WPF.Toolkit/VSM/System/Windows/VisualStateGroup.cs?r1=141801&r2=147669">One. Tiny. Patch. Later. </a><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_nFGxSFZnUc0/Sxk-syLWs4I/AAAAAAAAATQ/XjsQkwUu9WQ/s1600-h/Success.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 195px;" src="http://1.bp.blogspot.com/_nFGxSFZnUc0/Sxk-syLWs4I/AAAAAAAAATQ/XjsQkwUu9WQ/s320/Success.png" alt="" id="BLOGGER_PHOTO_ID_5411425366366925698" border="0" /></a><br />Success. I can't believe that the bug was that simple. In the end, those bugs are actually by far the worst. There's no exception thrown or any kind of visible indication that something has failed other than an empty screen. The only reason I found the bug was because the toolkit is opensource and I was running it locally with a few dozen Console.WriteLines, gradually reducing the area of code where I thought the bug was. Unfortunately this fix arrived too late for the 1.99.9 release, but it will definitely be in the release after it.Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com3tag:blogger.com,1999:blog-29153919.post-65007998775818202252009-12-02T17:44:00.005+00:002009-12-02T17:50:44.495+00:00Can't you feel the moonlight?It's time for the obligatory screenshots again. This is what the Data Visualisation demos from the Silverlight Toolkit (March edition) looked like yesterday:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_nFGxSFZnUc0/Sxany0O01YI/AAAAAAAAASo/Jv3MiPkoLuk/s1600-h/Before.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 195px;" src="http://4.bp.blogspot.com/_nFGxSFZnUc0/Sxany0O01YI/AAAAAAAAASo/Jv3MiPkoLuk/s320/Before.png" alt="" id="BLOGGER_PHOTO_ID_5410696493788353922" border="0" /></a>Note the empty graphs. It doesn't look very pretty now, does it? However, <a href="http://anonsvn.mono-project.com/viewvc/trunk/moon/src/frameworkelement.cpp?r1=147096&r2=147421">one very minor fix later</a> we now have the following:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_nFGxSFZnUc0/SxaoCMfpTCI/AAAAAAAAAS4/iiMXzjPVS1E/s1600-h/After.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 195px;" src="http://2.bp.blogspot.com/_nFGxSFZnUc0/SxaoCMfpTCI/AAAAAAAAAS4/iiMXzjPVS1E/s320/After.png" alt="" id="BLOGGER_PHOTO_ID_5410696757999389730" border="0" /></a>Things are near-perfect in all the Data Visualization demos. One graph is missing a background colour and the elements in one graph aren't clickable when they should be. Neither should be particularly difficult to fix, the only problem is figuring out the cause.<br /><br />Unfortunately the version of the Toolkit Demo on the live site still doesn't render perfectly, but as we already have one version near-perfect, getting a newer revision to work shouldn't be hard! Things are shaping up to give us a great 2.0 release.Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com1tag:blogger.com,1999:blog-29153919.post-1727319700011705402009-11-30T00:29:00.004+00:002009-11-30T00:51:29.070+00:00What does this say?<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.bbc.co.uk/london/content/images/2007/06/04/2012_logo_white_385x450.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 385px; height: 450px;" src="http://www.bbc.co.uk/london/content/images/2007/06/04/2012_logo_white_385x450.jpg" alt="" border="0" /></a><br />This is the logo for the London olympics. <a href="http://www.wolffolins.com/london2012.php">Wolff Olins</a> were paid a whopping <a href="http://news.bbc.co.uk/sport2/hi/other_sports/olympics_2012/6718243.stm">GBP 400,000</a> to design this. You'd think that if you spent that kind of money, you'd end up with a logo that can be understood by mere mortals, however that's not what we got.<br /><br />When I look at that logo I see the numbers '2' and '0' at the top. That's relatively clear. However, the bottom section is just random gibberish. If I concentrate a little harder, I could convince myself that there's a '1' in the bottom left, but what the hell is at the bottom right? A square with a squiggle beside it? I can't for the life of me figure out what that little square is for. Is it part of the '2' or is it there because they wanted to write "20-12"? I really can't tell.<br /><br />If you're going to design a logo for such an important worldwide event, why can't it be legible? Logo design should be about creating an inventive and visually appealing way of getting some information across. It isn't supposed to be about who can create the most obfusticated advertisement - that defeats the entire purpose! Out of <a href="http://news.bbc.co.uk/2/hi/in_pictures/6722205.stm">this list of a dozen alternative designs</a> my favourite is this one:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://newsimg.bbc.co.uk/media/images/43009000/jpg/_43009947_jason_salt220.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 220px; height: 300px;" src="http://newsimg.bbc.co.uk/media/images/43009000/jpg/_43009947_jason_salt220.jpg" alt="" border="0" /></a>It may not be the best logo I've ever seen, but it does one thing right: It's instantly recognisable and understandable. I know exactly what it's about without having to spend 10 mins rotating the thing trying to make sense out of it.Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com4tag:blogger.com,1999:blog-29153919.post-84656749939222774992009-11-19T14:32:00.002+00:002009-11-19T14:42:39.332+00:00Dear Thierry Henry<object width="560" height="340"><param name="movie" value="http://www.youtube.com/v/51t2segCkCk&hl=en_US&fs=1&"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/51t2segCkCk&hl=en_US&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="340"></embed></object><br /><br />If you truly were sorry that you <span style="font-weight: bold;">twice</span> hit the ball with your hand to prevent it from going wide and then proceeded to score from that opportunity, why didn't you admit it on the spot. You knew what you did, you did it deliberately, it is useless claiming you're sorry now when it's too late for that to mean anything. Your chance to show your true colours was on the field and show them you did.<br /><br />Since the match will not be replayed, why don't you step down from this world cup season if you truly do regret that decision which cost Ireland our world cup chance?Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com10tag:blogger.com,1999:blog-29153919.post-79554176131002817092009-11-02T10:14:00.010+00:002009-11-02T22:24:05.126+00:00Don't BinaryReader.PeekChar () at me!<span style="font-weight: bold;">Update:</span> As pointed out in the comments, there are errors in the implementation of SeekableStream below. That's what happens when you write a class at 2am ;) The errors are that Writing to the stream will be off by 1 byte if you've peeked, and Position will be off by 1 byte if you've peeked. I'll re-read and update the class later.<br /><br /><span style="font-weight: bold;">Update 2:</span> I updated the implementation of PeekableStream to make it a read-only stream wrapper. I'm not pushed about supporting writing on it as it's not something I require. I'll leave it as an exercise to the reader to support writing if they need it ;)<br /><br />How many times has that urge hit you to peek inside a stream and see the secrets hidden inside? Have there been times when you wished that Stream exposed a 'public byte Peek ()" method so you could do this easily? Were you delighted when you discovered that BinaryReader exposed PeekChar () , which did nearly the same thing [0]? If that describes you, you're in for a horrible horrible surprise.<br /><br />I received a bug report during the week saying that MonoTorrent reads all data twice from disk when it is hashing. I responded with "No it doesn't! That'd be crazy!", to which I was handed a screenshot of a ~310MB torrent which in the windows task manager reported ~750MB of I/O Read Bytes. I was told this happened after loading the torrent and calling HashCheck () on it. This was irrefutable evidence that something was up, but I was still unconvinced .<br /><br />So over the weekend I fired up windows and double checked. I could replicate the bizarre statistic. But strangely enough, it wasn't hashing that was causing it! The I/O Read Bytes was up at around 350MB before hashing even started. But that was strange, because the only thing that happened before that was:<br /><br />Torrent torrent = Torrent.Load (path);<br /><br />There's no way a simple forward-only parser reading a 100kB file could possibly result in 350MB of IO reads, could it? Actually, it could! Changing the declaration to the following completely vanished the extra 350MB:<br /><br />Torrent torrent = Torrent.Load (File.ReadAllBytes (path))<br /><br />So what was going wrong? Internally the parser used BinaryReader.PeekChar () to figure out the type of the next element so that correct decoder could be called. I thought this would be a simple array access, or something similar. However what actually happens is that one byte is read from the underlying stream, then the stream seeks 1 byte backwards . <span style="font-style: italic;">In the case of FileStream, this meant that the entire read buffer was refilled from 'disk' [1] every time I peeked. </span>A 100kB file really was really being turned into a 350MB monstrosity! And yes, the Mono implementation unfortunately has to do the same. So how could I fix this?<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://i198.photobucket.com/albums/aa299/yupko/ihasanidea.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 318px;" src="http://i198.photobucket.com/albums/aa299/yupko/ihasanidea.jpg" alt="" border="0" /></a><br />Simples! I could write a PeekableStream, one that's smart enough to not need to do horrible buffer killing seeks. What was the end result? Well, that particular .torrent file loaded nearly 5x faster, ~100ms for everything instead of ~500ms. An average file would experience a much smaller speedup. This one is a bit different in that it contains over 2000 files and the speed up is proportional to the number of BEncoded elements in the .torrent file.<br /><br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>public class PeekableStream : Stream<br />{<br /> bool hasPeek;<br /> Stream input;<br /> byte[] peeked;<br /><br /> public PeekableStream (Stream input)<br /> {<br /> this.input = input;<br /> this.peeked = new byte[1];<br /> }<br /><br /> public override bool CanRead<br /> {<br /> get { return input.CanRead; }<br /> }<br /><br /> public override bool CanSeek<br /> {<br /> get { return input.CanSeek; }<br /> }<br /><br /> public override bool CanWrite<br /> {<br /> get { return false; }<br /> }<br /><br /> public override void Flush()<br /> {<br /> throw new NotSupportedException();<br /> }<br /><br /> public override long Length<br /> {<br /> get { return input.Length; }<br /> }<br /><br /> public int PeekByte()<br /> {<br /> if (!hasPeek)<br /> hasPeek = Read(peeked, 0, 1) == 1;<br /> return hasPeek ? peeked[0] : -1;<br /> }<br /><br /> public override int ReadByte()<br /> {<br /> if (hasPeek)<br /> {<br /> hasPeek = false;<br /> return peeked[0];<br /> }<br /> return base.ReadByte();<br /> }<br /><br /> public override long Position<br /> {<br /> get<br /> {<br /> if (hasPeek)<br /> return input.Position - 1;<br /> return input.Position;<br /> }<br /> set<br /> {<br /> if (value != Position)<br /> {<br /> hasPeek = false;<br /> input.Position = value;<br /> }<br /> }<br /> }<br /><br /> public override int Read(byte[] buffer, int offset, int count)<br /> {<br /> int read = 0;<br /> if (hasPeek && count > 0)<br /> {<br /> hasPeek = false;<br /> buffer[offset] = peeked[0];<br /> offset++;<br /> count--;<br /> read++;<br /> }<br /> read += input.Read(buffer, offset, count);<br /> return read;<br /> }<br /><br /> public override long Seek(long offset, SeekOrigin origin)<br /> {<br /> long val;<br /> if (hasPeek && origin == SeekOrigin.Current)<br /> val = input.Seek(offset - 1, origin);<br /> else<br /> val = input.Seek(offset, origin);<br /> hasPeek = false;<br /> return val;<br /> }<br /><br /> public override void SetLength(long value)<br /> {<br /> throw new NotSupportedException();<br /> }<br /><br /> public override void Write(byte[] buffer, int offset, int count)<br /> {<br /> throw new NotSupportedException();<br /> }<br />}<br /></code></pre><br /><br />This code is under the MIT/X11 license, so everywhere you use PeekChar () and actually just want to Peek at a byte, use this class instead. Your harddrives will love you for it. If you actually want to peek at a char, extend this class to be able to read a (multi-byte) char from the underlying stream and cache it locally just like the current PeekByte method . A side benefit is that you can now peek at unseekable streams. Not too bad, eh?<br /><br />[0] PeekChar does exactly what it says on the tin. It reads one (multi-byte) character from the stream. So if you're using PeerChar on a binary stream which does not contain valid data as defined by the current Encoding, you're going to corrupt some data or get exceptions. I mention this here in case anyone is using PeekChar () as a way of reading bytes from the stream.<br /><br />[1] I say that the FileStream buffer was being filled from disk, but that's not quite accurate. It was actually being refilled from either the windows cache or the harddrives cache. It's physically impossible for a mere 7200 RPM harddrive to supply data that fast. However I still was physically copying 350MB of data around in memory so that was a huge penalty right there.Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com13tag:blogger.com,1999:blog-29153919.post-59816763186940496222009-10-18T19:22:00.006+01:002009-10-19T11:40:04.384+01:00MonoTorrent 0.80 - Up up and awayMonoTorrent 0.80 has been released. I'd like to say "It's the best release ever", but that always makes me think "If it wasn't the best release ever, why would I release it?"<br /><br />The full release notes can be read on <a href="http://projects.qnetp.net/projects/monotorrent/news">www.monotorrent.com.</a> For the lazy, I'll put a quick blurb about the two new most exciting new features available:<br /><br /><span style="font-size:130%;">Metadata Exchange<br /></span><a class="external" href="http://www.bittorrent.org/beps/bep_0009.html">http://www.bittorrent.org/beps/bep_0009.html</a><br /><br />Put simply, this means you can click on a link like this: magnet:?xt=urn:btih:12345678901234567890 and then the torrent will magically [0] be able to download. Behind the scenes what happens is that peers are found via DHT and then they are queried for the .torrent metadata. Once the metadata has been obtained, the actual downloading can commence and away you go. Finally, I can start a download via text message!<br /><br /><span style="font-size:130%;">Local Peer Discovery</span><br /><br />This allows MonoTorrent to find other peers who are downloading the same torrent on the local network. A simple UDP broadcast message is used for discovery. This is an implementation of style LDP and so is fully compatible with uTorrent and other clients which have implemented this style. The main benefit of this is that in corporate or educational environments, it's possible that many people will be trying to access the same torrent at the same time. This approach allows all these peers to connect to each other and thus transfer the bulk of their data over the internal LAN rather than all of them fighting for bandwidth on the (usually) limited WAN connection.<br /><br />As per usual, there are a bunch of bug fixes and enhancements. This is one more milestone on the way to the final 1.0 release. One which I'm really looking forward to. I might even do some nostalgia posts about the big disasters I created while learning C# and implementing this library ;)<br /><br />[0] Actual product does not contain magic.<br /><br />EDIT: Just clarified that the LPD implementation is the uTorrent style.Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com9tag:blogger.com,1999:blog-29153919.post-11555206689780501462009-06-29T23:05:00.002+01:002009-06-29T23:20:25.084+01:00Mono.Nat 1.0.2I just tagged and released <a href="http://projects.qnetp.net/news/show/4">Mono.Nat 1.0.2</a> . It's a fairly minor bugfix release which addresses a number of minor issues:<br /><ul><li>Added workaround for certain versions of miniupnpd which incorrectly advertise their available services (bug has been reported upstream)</li><li>Fixed some other minor issues with routers reporting incorrect services.</li><li>Added extra API to make it easy to log the full handshake/request process to help diagnose issues</li><li>Stopping and Starting discovery will rediscover all available devices correctly</li><li>Full support for computers with multiple network cards on multiple subnets</li><li>Rewrote the internals to ensure that the asynchronous API is 100% asychronous - prevents calls to BeginXXX blocking on some slower routers.</li></ul> Precompiled binaries and sourcecode can be downloaded <a href="http://projects.qnetp.net/projects/list_files/mono-nat">here</a> and packages will soon be winding their way to a repository near you.<br /><br />If you want to forward ports automagically on a upnp empowered router near you, this is the library for you!Alanhttp://www.blogger.com/profile/17518005985877464643noreply@blogger.com4