Planning the Spontaneous

It's more than just a blueprint.

Archive for March, 2010

290310

Posted by Robert Chow on 29/03/2010

I haven’t really focused much on music, but I’ve always loved it.  From listening to it when I was a kid, to picking up the guitar and whacking some tunes in my teens, to going to gigs in the current (although there’s been a lot less of the latter in Devon).

Anyway.  I’m excited.

Posted in Music | Tagged: , , , | Leave a Comment »

Because testing_isnt_all_bad

Posted by Robert Chow on 26/03/2010

I took a course module last year that didn’t end so well. My results were fine, but the teaching was plain appalling.  One of our assignments wanted us to try testing, yet the lesson hadn’t been covered at the time – it was covered after the assignment deadline. The reason behind this was so we had to do the research ourselves instead of being taught it textbook style.  I guess that way does work better – most of the stuff I’m doing now is all research I’ve done myself.  Still, it doesn’t excuse them for the time they handed an assignment out 2 days before the deadline, despite it being a 2 week workload.

My point is, testing is important.  As mundane as it is, it’s necessary.  We all try to avoid it.  We all seem to think we’re already doing it.  But that’s a lie. Unless we are actually devoting time and effort into it, we’re not actually doing it.  And fortunately for myself, I found this the easy way.

First glance tells me it’s fine

I have 3 methods, each an extension method for an integer.  These are:

bool IsPowerOf2();

int NextPowerOf2Up();

int NextPowerOf2Down();

I think from the names they’re pretty much self-explanatory.

When writing these methods, they worked the way they should do.

2.IsPowerOf2(); // returns true

7.IsPowerOf2(); // returns false

5.NextPowerOf2Up(); // returns 8

21.NextPowerOf2Down(); // returns 16

Or at least that’s the way I thought.

I’ve learnt over the few years of my experience that if you’re not thinking testing, then you’re not thinking outside the box.  All you see are the cases that work, and that should work.  Fine.  But then what if someone else thinks outside of the box?

(-1).NextPowerOf2Down();

Logically, no power of 2 should ever exist less than zero, and no integer power of 2 should exist less than 1.  Lucky for me, I already thought of this case, so I had it covered by throwing an exception.  And that’s basically it.

Second glance tells me different

Around Christmas time, I remember blogging that I wanted to look into using a tool called Machine.Specifiations, more commonly known as M.Spec.  M.Spec provides a handy library designed for writing and implementing tests in a fluent manner.  My supervisor asked me to write some of my own tests to cover the extension methods I’ve written, and that was when I started to think testing.

The idea behind testing is to try to cover every single case, without actually testing every single case.  Taking NextPowerOf2Up(), I can identify the different cases; these are values less than 1, and values equal to or greater than 1.  This is because the pivotal point in this method is 1 itself – it’s is the smallest possible power of 2.  We expect that any case less than 1 will result in 1, and any case that is equal to or greater than 1 will run as expected.  I can also split the latter into two more sections – those that are already a power of 2, and those that are not.  This is to ensure that the result does end in the next power of 2 and not the current one.

Sounds fairly simple.  But I did find one case that actually forced me to change the method code. This case covers all numbers equal to and greater than the highest possible power of 2 supported by an integer value.   Trying to ask for a power of two greater than that supported by int should throw an exception.

It was because of this case that I really saw the benefit of testing.  I knew it was important, but just not entirely how.  Sure I knew it was beneficial to see if your method works as expected.  But that’s pretty much really.  No one expects to use the corner cases, but it still needs to be covered.  And it’s using testing that’s helped me to do that.

M.Spec

Anyway, here’s the M.Spec code that I used to write my tests.  Like I said, it’s fairly fluent – M.Spec provides types and extension methods to aid this.

Note: SpecWithResult<> and FailingSpec are very simple containers designed by my supervisor.  SpecWithResult<> holds a result, and FailingSpec holds an exception.

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_0 : SpecWithResult<int>
{
Because of = () => result = 0.NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_1 : SpecWithResult<int>
{
Because of = () => result = 1.NextPowerOf2Up();
It should_equal_2 = () => result.ShouldEqual(2);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_2 : SpecWithResult<int>
{
Because of = () => result = 2.NextPowerOf2Up();
It should_equal_4 = () => result.ShouldEqual(4);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_3 : SpecWithResult<int>
{
Because of = () => result = 3.NextPowerOf2Up();
It should_equal_4 = () => result.ShouldEqual(4);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_4 : SpecWithResult<int>
{
Because of = () => result = 4.NextPowerOf2Up();
It should_equal_8 = () => result.ShouldEqual(8);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_5 : SpecWithResult<int>
{
Because of = () => result = 5.NextPowerOf2Up();
It should_equal_8 = () => result.ShouldEqual(8);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_negative_1 : SpecWithResult<int>
{
Because of = () => result = (-1).NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_127 : SpecWithResult<int>
{
Because of = () => result = 127.NextPowerOf2Up();
It should_equal_128 = () => result.ShouldEqual(128);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_128 : SpecWithResult<int>
{
Because of = () => result = 128.NextPowerOf2Up();
It should_equal_256 = () => result.ShouldEqual(256);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_129 : SpecWithResult<int>
{
Because of = () => result = 129.NextPowerOf2Up();
It should_equal_256 = () => result.ShouldEqual(256);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_negative_128 : SpecWithResult<int>
{
Because of = () => result = (-128).NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_min_value : SpecWithResult<int>
{
Because of = () => result = int.MinValue.NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_max_value : FailingSpec
{
Because of = () => exception = Catch.Exception(() => int.MaxValue.NextPowerOf2Up());
It should_throw_an_exception = () => exception.ShouldBeOfType<ArgumentException>();
It should_throw_an_exception_with_an_inner_exception = () => exception.InnerException.ShouldBeOfType<OverflowException>();
}

Here are the results after running the library with M.Spec

Int32Extensions .NextPowerOf2Up, when given 0
» should equal 1

Int32Extensions .NextPowerOf2Up, when given 1
» should equal 2

Int32Extensions .NextPowerOf2Up, when given 2
» should equal 4

Int32Extensions .NextPowerOf2Up, when given 3
» should equal 4

Int32Extensions .NextPowerOf2Up, when given 4
» should equal 8

Int32Extensions .NextPowerOf2Up, when given 5
» should equal 8

Int32Extensions .NextPowerOf2Up, when given negative 1
» should equal 1

Int32Extensions .NextPowerOf2Up, when given 127
» should equal 128

Int32Extensions .NextPowerOf2Up, when given 128
» should equal 256

Int32Extensions .NextPowerOf2Up, when given 129
» should equal 256

Int32Extensions .NextPowerOf2Up, when given negative 128
» should equal 1

Int32Extensions .NextPowerOf2Up, when given min value
» should equal 1

Int32Extensions .NextPowerOf2Up, when given max value
» should throw an exception
» should throw an exception with an inner exception

Contexts: 13, Specifications: 14

Oh, and for the record, we managed a petition against that particular course and got over 75% backing from the students who took it, including some who took it in previous years too.

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_0 : SpecWithResult<int>
{
Because of = () => result = 0.NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_1 : SpecWithResult<int>
{
Because of = () => result = 1.NextPowerOf2Up();
It should_equal_2 = () => result.ShouldEqual(2);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_2 : SpecWithResult<int>
{
Because of = () => result = 2.NextPowerOf2Up();
It should_equal_4 = () => result.ShouldEqual(4);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_3 : SpecWithResult<int>
{
Because of = () => result = 3.NextPowerOf2Up();
It should_equal_4 = () => result.ShouldEqual(4);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_4 : SpecWithResult<int>
{
Because of = () => result = 4.NextPowerOf2Up();
It should_equal_8 = () => result.ShouldEqual(8);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_5 : SpecWithResult<int>
{
Because of = () => result = 5.NextPowerOf2Up();
It should_equal_8 = () => result.ShouldEqual(8);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_negative_1 : SpecWithResult<int>
{
Because of = () => result = (-1).NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_127 : SpecWithResult<int>
{
Because of = () => result = 127.NextPowerOf2Up();
It should_equal_128 = () => result.ShouldEqual(128);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_128 : SpecWithResult<int>
{
Because of = () => result = 128.NextPowerOf2Up();
It should_equal_256 = () => result.ShouldEqual(256);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_129 : SpecWithResult<int>
{
Because of = () => result = 129.NextPowerOf2Up();
It should_equal_256 = () => result.ShouldEqual(256);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_negative_128 : SpecWithResult<int>
{
Because of = () => result = (-128).NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_min_value : SpecWithResult<int>
{
Because of = () => result = int.MinValue.NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_max_value : FailingSpec
{
Because of = () => exception = Catch.Exception(() => int.MaxValue.NextPowerOf2Up());
It should_throw_an_exception = () => exception.ShouldBeOfType<ArgumentException>();
It should_throw_an_exception_with_an_inner_exception = () => exception.InnerException.ShouldBeOfType<OverflowException>();
}

Posted in Computing, Placement | Tagged: , , , , , , , | Leave a Comment »

9

Posted by Robert Chow on 22/03/2010

Over nine hours it’s taken to get back to Bideford from Manchester. It’s an absolute disgrace. At first I thought the problem would lie at Birmingham New Street, with a sketchy 15 minute wait for the connecting train, and the least of my worries at Exeter with a 45 minute changeover. Yet, it was the latter, with the Birmingham to Exeter train delayed 1 hour 30 minutes. As a result, I had to catch a much later train back to Barnstaple.

I can honestly say, it’s the journeys that I am going to miss the least when I leave my placement in the summer. Don’t get me wrong, I absolutely love the work I’m doing (that reminds me, I’ve got a lot left to do), and being a student will be very disorienting again, I am sure. However, it’s no secret that I’m not too fond of the location. It’s great for a holiday, or a weekend break; but a year, especially for someone growing up in Manchester and loving the city life, it’s not quite my cup of tea.

Anyway.

I’m back in Bideford now. Hopefully I’ll be able to cram in a few more posts before I disappear back up north again.

Posted in Placement, Travel | Leave a Comment »

glGenTexture(Atlas); // part2

Posted by Robert Chow on 19/03/2010

So from the plan I started to devise for my texture atlasing system, I decided to have a quick look at two methods. These two methods can help me to split up the texture in an efficient and appropriate way so I can maximise texture use and efficiency. These are the Binary Split Partitioning method, and the use of Quadtrees.

Binary Split Partitioning

This method takes a space and splits this into two, keeping a reference to each part. Using a binary tree, the tree can be used to keep a track of how the space is split at each stage.  This method works by taking the requested size (the image/sub-texture) and seeing if it fits into the space represented by a leaf node.  If not, then it will see if the requested size fits into the next leaf node using tree traversal.  To maximise efficiency, once a space that is free and large enough for the requested size is found, it splits this space into two;  both are represented as leaf nodes, yet one is the exact size as the requested size (and this is also where the requested texture is allocated) and the other is the remaining unallocated space.

Binary Split Partitioning.  This image represents the stages of how to insert a sub-texture(1) into a space managed by BSP.  Starting off at the top, (1) is requested and seen if it fits into space A.  A is too small, and it is split into 2 partitions, B and C.  (1) is compared to space B, and again splits this space into D and E.  As D is the perfect fit, (1) is allocated to this space.  The last image shows the final state of the tree after inserting (1).  The reason it cannot skip from stage 1 to stage 3 is because there is no possible way of splitting the space and keeping to an efficient BSP system – it is best to split each space linearly.  The splitting measurements are derived from the sub-texture to maximise efficiency.

I managed to find this method rather easy to implement, especially with the help from this page on lightmaps.  Using this, I also managed to come up with a simple demo, showcasing how it fits several random sub-textures into one.

BSP Texture Atlas.  This image shows my demo of inserting random sized images into a texture managed by BSP in progression.

Although easy to implement, there are a couple of drawbacks.  As this is an unbalanced binary tree, it becomes computationally very expensive to insert a sub-texture as the tree structure grows.  Adding tree rotation to balance the tree is possible – it doesn’t affect how the tree works when inserting a sub-texture.  However, it does affect the tree when it comes to deleting allocated space.  This is because the nodes derive from their parents; losing the connection between the parent and the children (which occurs heavily during tree rotation) means that to successfully delete an allocated space at node B derived from parent A and has sibling C, is more-or-less a very difficult feat.  If C is empty, A will be empty after the deallocation of space B – and thus B and C need not exist, leaving the big space referenced by A unsplit.  Taking away the natural bond between parent and child in a BSP tree due to tree rotation (or any other method for that matter) makes this problem rather hard to tackle as it could mean having to search the entire tree for the parent and other sibling.  These leaves me with a problem – do I keep the tree balanced, and make insertion less costly at the expense of not being able to delete; or do I allow for deallocation, whilst keeping insertion computationally expensive?  In the end, I will be inserting more times than not, and very rarely deleting, so I think the former is the best option for now.

Or I could look at quadtrees.

Quadtrees

This is where my planning goes wrong.  And I’ve also not researched quadtrees to the full extent.  I know what they are designed to do, I’m just unsure of how to implement them.

A quadtree is a structure whereby each node has four children.  Each node represents a square block in the texture, and each block is split into four smaller squares – the children.  This is repeatedly done until the size of a single block is a 1×1 square.

Quadtree Colour.  This displays how a texture is split up using quadtrees.  I have used colour to differentiate each node.  I guess it looks quite similar to one of those Dulux colour pickers.

As this structure is set in stone, its depth will be constant and we need not to worry about insertion/deletion costs.  It’s already a rather shallow structure in itself because each node has 4 children, as opposed to the typical 2.

The structure works by marking off what is allocated at the leaf nodes, and this is interpreted at the top.  If all of a node’s children is allocated, then that node too is marked as allocated.  From using this system, we can find if there is any space available in a node, and then check to see if the space provided is big enough to fit in the sub-texture.  My problem is that I’m unsure of how to do implement this efficiently.  For example, a very large sub-texture is inserted at the root, where everything is unallocated so far.  This takes up more than one quadrant of the root, and spills into the other quadrants.  By definition, the one quadrant the texture covers up is marked as allocated, yet the other quadrants are only partly allocated – some of their children are allocated but they also have children that are unallocated.  It is the next step which I cannot figure out; insert in another large texture – there is enough space in the root to place this, but it needs to take up the space given by two already partially filled quadrants.  How does it know how these quadrants are related in terms of position, and how does it then interpret the unallocated space in each of these quadrants?  They obviously need to be interpreted as adjacent, but then the available children of these quadrants also need to be interpreted as adjacent too.  It’s a puzzle I’ve decided to give this a miss, simply because I don’t have the time.

Quadtree Insert.  This depicts the problem I am having trouble with.  Inserting the first image will mark off the top-right quadrant as occupied, whilst the others are partially occupied.  It is easy to see that the second image will fit into the remaining space, but I am unsure of how to implement this.   This is because the second image will look at the root and find it is small enough to fit inside.  It will then look at the quadrants to see if it will fit in either of them, and it cannot because it is too big.   I do not know how to implement the detail where it looks at the quadrants together – I’m not even entirely sure if this is how quadtrees are supposed to work.

Posted in Computing, Placement | Tagged: , , , , | Leave a Comment »

glGenTexture(Atlas); // part1

Posted by Robert Chow on 08/03/2010

So besides having a problem with fonts, I also know at a later stage, I will have to eventually incorporate texture atlases. A texture atlas is a large texture that is home to many smaller sub-images which can be each accessed by using different texture coordinates. The idea behind this is to minimise the number of texture changes is in OpenGl – texture changing is regarded as a very expensive operation and should be kept as minimal as possible.  Using this method would create a huge performance benefit, and would be used extensively with fonts.


Helicopter Texture Atlas.  This shows how someone has taken a model of a helicopter and broken it up into its components, before packing it into a single texture atlas.  This image is from http://www.raudins.com/glenn/projects/Atlas/default.htm

Where To Start?

Approaching this problem, there are multiple things for me to consider.

The first is how to compose the texture atlas. Through a little bit of research, I have found a couple of methods to consider – the Binary Split Partition (BSP) algorithm, and using Quadtrees.

Secondly, the possible notion of a optimisation method. This should take all the textures in use into consideration, and then sort them so the number of texture changes is at a minimum. This would be done by taking the most frequent sub-textures in use, and placing them on to a single texture. Of course, this alone there are many things to consider, such as what frequency algorithm should I use, and how often do the textures need to be optimised.

Another thing for me to consider is the internal format of the textures themselves, and also how the user will interact with the textures.  It would be ideal for the user to believe that they are using separate textures, yet behind the scenes, they are using a texture atlas.

Updating an Old Map

As I already have the Renderer library in place, ideally the introduction of texture atlases will not change the interface dramatically, if not at all.

For the current, the user asks Renderer for a texture handle, with an internal format specified for the texture.  Initially this handle does not correspond to a texture, and seems rather pointless.  Of course the obvious answer to that would be to only create a handle upon creating a texture.  The reason behind using the former of the two options was the flexibility it provided when a texture is used multiple times in a scene graph.   Using a texture multiple times in a scene means that the handle would have to be referred to in each node it is to appear at in the scene graph.  Using the first option means that if the texture image changes, only the contents of the handle is changed, and thus updates automatically in the scene graph.  If it was done using the latter of the two options, it would mean having to change the handle in every single node of the scene graph it corresponded to.  The handle acts as like a middle-man.

When incorporating texture atlases, I would like to keep this functionality; but it does mean that I will have to change the interface in another area of Renderer, and that is using texture co-ordinates.  For the current, the texture co-ordinates are nice and simple.  To access the whole texture, it would use texCoords (0,0) for the bottom-left hand corner, and (1,1) for the top-right hand corner.  To the user on the outside, this should be no different, especially considering the user is thinking that they are just using the single-texture.  To do this, a mapping would be needed to convert the (s,t) texture co-ordinates to the internal texture atlas co-ordinates.  Not really a problem.  But it is if we’re using Vertex Buffer Objects.

Just using the simple (0,0) to (1,1) co-ordinates all the time means that we only need to create the texture co-ordinates in our vertex buffers once, and refer to them all the time.  Yet, they are to change all the time if we are using internal mapping, and especially if we are going to be changing the texture contents that the handles are referring too as well.

I think the best way of solving this is to go about it by making sure that the texture co-ordinates inside the vertex buffer objects are created each time a new texture is bound to a handle.  How I go about this, I am not entirely sure, but it’s definitely something I need to consider about.  This would definitely mean a fairly tight relationship between texture co-ordinates in a scene graph node and the texture that it is bound to – of course they can’t really function without each other anyway, so it does make sense.  Unfortunately, the dependance is there and it is always will be.

Structure

Before all of that comes into place, I also need to make sure that the structure of the system is suitable too, on top of it being maintainable, readable and scalable.  For the current time being, I am using a 3-tier structure.

Texture Atlas Structure.  This shows the intended structure of my texture atlas system.

Starting at the top, there is the Atlas.  This is essentially what the user interface will work through.  The Atlas will hold on to a list of catalogs.  The reason I have done this is to separate the internal formats of the textures.  A sub-image with the intended internal format A cannot be on the same texture as a sub-image of internal format B.   Inside each catalog is a collection of Pages with the corresponding internal format.  A Page is host to a large texture with many smaller sub-images.  Outside of this structure are also the texture handles.  These refer to each of their own sub-images, and hide away the complexity of the atlas to the user, making it seem as if they are handling separate textures.

For the time being, this is still a large learning-curve for me, not only in learning OpenGl, but also in using design patterns and C# licks, and more recently, planning.  I don’t plan very often, so this is one time I hope on trying to do that!  These posts may come a little slow as I’m in Manchester at the moment, and although I thought my health was getting better, it isn’t seemingly all going away.  With a little luck, I’ll accomplish what it is that I want to do over the next couple of weeks.  In the next post, I hope to touch on BSP, which I have implemented; and Quadtrees – something I still need to look at.   Normally I write my blog posts after I”ve implemented what it is I’m blogging about, but this time I’ve decided to it a little differently.  The reason for this is because after I implemented the BSP, I’ve realised it is actually a lot more flexible to implement the Quadtrees instead.  Whether I do or not, I think is dependant on time.  As long as there is a suitable interface, then all in all, it is just an implementation detail, and nothing major to worry about.  This is, at the this current time, by no means, a finished product.

Posted in Computing, Placement | Tagged: , , , , , , | 3 Comments »

Sleep Deprivation

Posted by Robert Chow on 04/03/2010

I have to admit, it’s been a while.

Unfortunately, I’ve been a bit under-the-weather the last week, and as a result unable to attend work.  I haven’t been sleeping properly for x-amount of weeks, and so I’ve ventured home to Manchester to try to solve my problems there.  For the time being, it seems to be paying off.

I did have my placement tutor visit take place just before I came back up and from the sounds of it, she was very pleased with the progress on my part, and on the part of my supervisor.  This is indeed very good news.  She did have one concern tho, and that was the fact I am secluded in the shires of North Devon.  Fair enough, it’s definitely something I know I’m not particularly ecstatic about.

Hopefully, with a bit of luck and well-being, I’ll get my blog posts rolling again.

Posted in Computing, Placement | Leave a Comment »