<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>despatches</title><link>https://icle.es/</link><description>Recent content on despatches</description><generator>Hugo</generator><language>en</language><lastBuildDate>Wed, 06 May 2026 15:22:52 +0100</lastBuildDate><atom:link href="https://icle.es/index.xml" rel="self" type="application/rss+xml"/><item><title>What's a Circuit Breaker?</title><link>https://icle.es/2026/05/06/whats-a-circuit-breaker/</link><pubDate>Wed, 06 May 2026 10:53:27 +0100</pubDate><guid>https://icle.es/2026/05/06/whats-a-circuit-breaker/</guid><description>&lt;p>I&amp;rsquo;d not been interviewed very often. For my first job, sure, I sent around my CV
to a bunch of places and did a bunch of interviews.&lt;/p>
&lt;p>I then started my own company and ran that for nearly two decades, everything
largely self-taught. After that, because of the peculiar shape of my CV, and
&amp;ldquo;falling between a lot of stools&amp;rdquo; as a friend pointed out, interviews were&amp;hellip;
tricky!&lt;/p>
&lt;p>I remember one such interview a few years ago. He was good at diffusing any
anxiety and it was a pretty relaxed interview. He wanted to know if I knew
architectural design patterns - a term I hadn&amp;rsquo;t heard before. He gave me
examples, circuit breaker, pub / sub and a third one I can&amp;rsquo;t remember anymore. I
recognised pub / sub and the other one I think - but was not aware of circuit
breaker. He explained it to me - something about reducing the risk of a
downstream service failing.&lt;/p></description><content:encoded><![CDATA[<p>I&rsquo;d not been interviewed very often. For my first job, sure, I sent around my CV
to a bunch of places and did a bunch of interviews.</p>
<p>I then started my own company and ran that for nearly two decades, everything
largely self-taught. After that, because of the peculiar shape of my CV, and
&ldquo;falling between a lot of stools&rdquo; as a friend pointed out, interviews were&hellip;
tricky!</p>
<p>I remember one such interview a few years ago. He was good at diffusing any
anxiety and it was a pretty relaxed interview. He wanted to know if I knew
architectural design patterns - a term I hadn&rsquo;t heard before. He gave me
examples, circuit breaker, pub / sub and a third one I can&rsquo;t remember anymore. I
recognised pub / sub and the other one I think - but was not aware of circuit
breaker. He explained it to me - something about reducing the risk of a
downstream service failing.</p>
<p>It made sense - but I couldn&rsquo;t remember any instance where I&rsquo;d done that and I
said as much.</p>
<p>No big deal! We moved on.</p>
<p>As you may have noticed from my recent flurry of posts, I&rsquo;ve been going through
a bit of an excavation of all things kraya, and discovering little nuggets that
I&rsquo;d forgotten all about.</p>
<p>Claude code went through all of my emails, all the code repositories and put
together interesting things I had done. The purpose was mainly to pick out
things that could be good blog posts, stories, or just reminders.</p>
<p>Claude had found two circuit breakers.</p>
<p>The first one was built by the seat of my pants in 2004. There was an active
marketing campaign on megabus and the system was struggling. I&rsquo;d plumbed the
depths of the tech stack - the web servers, the database server to get every
last ounce of performance out of the system.</p>
<p>What I really needed was to slow down the deluge of people coming into the
site - just a little bit. I needed to prevent the snowball effect, and I tried a
simple way to achieve it. I wrote the following around my 23rd birthday.</p>
```php
$timestamp = time() - 120;
$sTimestamp = date("d-F-Y H:i");
$sql = "SELECT count(*) FROM tSearches WHERE Script_Start > '$sTimestamp' AND Script_End IS NULL;";

$numSearches = $dbh->getOne($sql);
if ($numSearches > 10)
    sleep(5);

$numSearches = $dbh->getOne($sql);
if ($numSearches > 10)
    sleep(5);

$numSearches = $dbh->getOne($sql);
if ($numSearches > 10)
    sleep(5);
```
<p>(The eagle eyed among you might notice a bug in the above code. It wasn&rsquo;t
resolved for years. Coding live on a production system tends to create bugs. See
if you can find it before reading on.)</p>
<p>(Regular readers may also remember a part of this story from
<a href="https://icle.es/simple-wins.md">I Know People Like You</a>)</p>
<p>The system already tracked searches - so I just needed to check it.</p>
<p>This bit of code checks to see how many of the searches that started in the last
two minutes were incomplete. Except that&rsquo;s not what it did - I forgot to
actually use <code>$timestamp</code> so it only checked the current minute&rsquo;s worth.</p>
<p>I considered failing and letting the user retry manually - but I didn&rsquo;t want to
force the user to take action unless absolutely necessary. This one waited up to
15 seconds and then let the search happen anyway.</p>
<p>If the user was still waiting after 15 seconds, might as well try and do a
search and see what happens. In hindsight, it might have been better to fail at
that point. If there were too many searches after 15 seconds, the database
server was likely already snowballing.</p>
<p>This bit of code survived through to the end of the PHP codebase apart from
minor tweaks. The Java version had layers of circuit breakers. Session limits,
rate limiters and threadpool configuration for scaling up. None of it though,
was called a circuit breaker - not by me or by the team.</p>
<p>So, have I built a circuit breaker? Thinking back, what threw me off was the
wording. I had the feeling that the circuit breaker would &ldquo;protect&rdquo; another
service.</p>
<p>In my mind, the database wasn&rsquo;t another service - it was a part of the same
service.</p>
]]></content:encoded></item><item><title>Holding the Fort</title><link>https://icle.es/2026/04/15/holding-the-fort/</link><pubDate>Wed, 15 Apr 2026 15:26:10 +0100</pubDate><guid>https://icle.es/2026/04/15/holding-the-fort/</guid><description>&lt;p>For many years, I loved my job. I was working on a production system that saw
tens of thousands of orders across the world.&lt;/p>
&lt;p>By the time it was 2015, I did not.&lt;/p>
&lt;p>I had poured blood, sweat and tears into a ticketing system that kraya built —
first for megabus, then for Polskibus. It had broken me, and we were now just
limping along.&lt;/p>
&lt;p>By 2015, we were spending 101–286 hours each month on a support contract that
paid for 60. I raised this with the client and suggested a minimum of a 100%
increase. They refused. Instead, they minimised their requests to just about 80
hours each month. Without the development work, I was now making a loss each
month providing the resources for this support contract.&lt;/p></description><content:encoded><![CDATA[<p>For many years, I loved my job. I was working on a production system that saw
tens of thousands of orders across the world.</p>
<p>By the time it was 2015, I did not.</p>
<p>I had poured blood, sweat and tears into a ticketing system that kraya built —
first for megabus, then for Polskibus. It had broken me, and we were now just
limping along.</p>
<p>By 2015, we were spending 101–286 hours each month on a support contract that
paid for 60. I raised this with the client and suggested a minimum of a 100%
increase. They refused. Instead, they minimised their requests to just about 80
hours each month. Without the development work, I was now making a loss each
month providing the resources for this support contract.</p>
<p>I could cancel the contract, but that would involve letting them have the source
code, which was otherwise kraya&rsquo;s property. There was a six month notice period
— six months of providing all the support they need to run and to migrate the
system to their team.</p>
<p>At some point, I went from taking on the challenge to holding the fort.</p>
<p>I could see that any effort to make things easier would backfire. I built them a
feature for free — they complain about one bug in it — and they need it fixed
urgently. I loosen the rules and deploy an additional server just before the
weekend, letting them know about the risks. Something goes wrong and they are
surprised.</p>
<p>The hardest thing for me to change was the belief that the client&rsquo;s wins were my
wins. If anything, the client&rsquo;s wins were my loss. It meant more unpaid work in
the support contract. It meant more complex systems to support and maintain —
while every penny was being questioned.</p>
<p>I started to say no. No, we will not deploy new servers on a Thursday because
the weekend is too close. No, we will not reduce the QA time on this bit of
functionality you want. No, we will not restore reports which could display
erroneous data.</p>
<p>It all came to a head in one specific instance where the CEO insisted on
speaking to me because they needed something deployed urgently. They needed us
to work through the evening or the weekend. I said no. They offered to pay
double. I said no. They were not happy.</p>
<p>A few months later, they cancelled the contract.</p>
<p>I remember the meeting. It was amicable and friendly. They asked if I could keep
the staff on till the end of the year - they wanted backup in case anything went
wrong during the migration.</p>
<p>I wouldn&rsquo;t want to let them go before Christmas anyway.</p>
<p>I had a brief conversation with my brother to decide what to do next. I already
knew that there was nothing left to keep running. We kept it running for three
more months until the end of December.</p>
<p>I remember going into the main office, gathering everyone around and delivering
the news. Polskibus had cancelled. We are shutting kraya down. We&rsquo;ll keep going
until the end of December.</p>
<p>I remember the Christmas party. It had often ended up being drinks, and being
out all night. It didn&rsquo;t this year - we went home after the meal. The atmosphere
was one of sadness and relief. We&rsquo;d all been through the fires and made it out
the other end. It was over.</p>
<p>It was the end of what I&rsquo;d built over 15 years.</p>
<p>I felt nothing.</p>
]]></content:encoded></item><item><title>I Chose to Keep Going</title><link>https://icle.es/2026/04/14/i-chose-to-keep-going/</link><pubDate>Tue, 14 Apr 2026 20:52:10 +0100</pubDate><guid>https://icle.es/2026/04/14/i-chose-to-keep-going/</guid><description>&lt;p>In 2008, we all watched Pivotal crash and burn. They&amp;rsquo;d taken a year and nearly
£900k to build a new ticketing system for the fringe. On launch, they realised
that it could serve only one customer at a time.
&lt;a href="https://icle.es/saving-the-fringe.md">We built an interim ticketing system for them over the weekend&lt;/a>.&lt;/p>
&lt;p>It was time for kraya to take a leap. We should take the megabus.com ticketing
system to the next level and build a distributed ticketing system that could
potentially scale infinitely.&lt;/p></description><content:encoded><![CDATA[<p>In 2008, we all watched Pivotal crash and burn. They&rsquo;d taken a year and nearly
£900k to build a new ticketing system for the fringe. On launch, they realised
that it could serve only one customer at a time.
<a href="https://icle.es/saving-the-fringe.md">We built an interim ticketing system for them over the weekend</a>.</p>
<p>It was time for kraya to take a leap. We should take the megabus.com ticketing
system to the next level and build a distributed ticketing system that could
potentially scale infinitely.</p>
<p>I&rsquo;d picked JBoss because it was backed by Red Hat - it had all these features
and capabilities - a lot of which we needed. The other option we considered was
Glassfish, but it just didn&rsquo;t have the features we needed. There were other
options but that involved prohibitive licensing fees.</p>
<p>We costed it out at £650k and a year. It was unrealistic but I could not imagine
Stagecoach paying more, or giving us more time. They wanted it for £500k and in
six months. I should&rsquo;ve pushed back, but we&rsquo;d built a booking system over the
weekend, only a few months back - this should be possible, right?</p>
<p>It wasn&rsquo;t.</p>
<p>We got a year in the end — we didn&rsquo;t ask for it, and we didn&rsquo;t know until we
were most of the way down the path. When I heard about the delay it was a mix of
relief and regret. We&rsquo;d burned through most of the budget on getting in
contractors who were leaving imminently. We could have got fewer people on
board, but as permanent staff.</p>
<p>We launched to Canada first, and that wasn&rsquo;t too bad. Then it was the US, and
the nightmare started. The UK launched last on my birthday in 2010.</p>
<p>In the intervening years, we had a budget shortfall of £150k and because we&rsquo;d
rushed to build the product, we&rsquo;d cut corners, and all the contractors had left.
We even had to let go of some of the permanent staff. I asked Stagecoach if
they&rsquo;d fund us the extra £150k as we&rsquo;d asked at the start. They said no. They
renegotiated the contract. They demanded more oversight - and increased our
reporting obligations.</p>
<p>Load testing was already a part of our process and we tested the new system
under load. We identified issues and fixed them. On paper, it looked good.</p>
<p>It wasn&rsquo;t.</p>
<p>The problem wasn&rsquo;t load per se. It was the stability of the system. It struggled
to stay up for extended periods of time. The more nodes there were, the worse it
was.</p>
<p>It was exactly the kind of problem that was hard to replicate and hard to test.
The main option we had was to think through what all it could be and to take
stabs in the dark. I put together a hit list and worked through it methodically.
Convincing Stagecoach to spend the money was sometimes harder than solving the
problem.</p>
<p>Ultimately, the one thing that pushed us over the line in terms of stability was
staggered nightly automated restart of each node.</p>
<p>I thought I was done working with
<a href="https://icle.es/it-gets-everywhere.md">software that needed regular restarts to stay functional</a>
and at first resisted this. We are running enterprise level software, and that
too on Linux. If I was comfortable with such shenanigans, I might have stayed
with Windows. But we were running out of options. It was a hail Mary - it
worked.</p>
<p>I had been working with Linux and related software for years by that point. I,
in fact had servers that had not been restarted for literal years at that point,
with services that were running just as long. A lot of these services didn&rsquo;t
even need a restart on config change - just a reload.</p>
<p>None of these services even had a paid tier. That should have been the clue.</p>
<p>I cannot imagine having to restart PostgreSQL nightly or even Apache. I still do
not understand how something slated for the enterprise market can have leaks
that would warrant a regular restart to keep it working.</p>
<p>Years later, when I looked up issues around JBoss, I realised that it was
notorious for a whole slew of problems with JGroups and clustering. I remember
scouring the internet for details on any kind of issues and coming up empty.</p>
<p>I&rsquo;d fallen into a marketing trap. JBoss was no Apache — it was the commercial
product of Red Hat. I thought all enterprise open source software would be of
the same calibre. It wasn&rsquo;t.</p>
<p>From what I understand, Stagecoach spent millions and maybe two years building
the whole ticketing system inhouse. kraya limped along for a few more years
before being shuttered.</p>
<p>The people involved, though, fortunately seem to have gotten through it largely
unscathed. It gives me a great deal of joy to see so many of the juniors I&rsquo;d
hired now CTO&rsquo;s, VP&rsquo;s, Directors.</p>
<p>I believed that every victory was ours but when something went wrong, it
belonged to me. After all, I made every choice. I chose to pursue the Java EE
ticketing system. I chose JBoss. I chose to keep going.</p>
<p>I was in a narrowing path, with fewer and fewer options.</p>
<p>I knew I was the only one holding that line - I didn&rsquo;t know that there was
another option.</p>
<p>Bit by bit, I lost all sense of what I was paying to fix these mistakes.</p>
]]></content:encoded></item><item><title>Always On</title><link>https://icle.es/2026/04/14/always-on/</link><pubDate>Tue, 14 Apr 2026 19:51:09 +0100</pubDate><guid>https://icle.es/2026/04/14/always-on/</guid><description>&lt;p>I knew as soon as my phone rang what it was about.&lt;/p>
&lt;p>It was the same every time. I would drag myself up to answer the phone - my
body, my mind screamed at me, but I had gotten good at overriding every instinct
through sheer willpower. I could hear the apologetic tone on the other side, and
I could recognise some of the voices after a while. I mustered up all of my
strength to be and sound as awake as possible. I needed to be professional even
if I was still in my underwear.&lt;/p></description><content:encoded><![CDATA[<p>I knew as soon as my phone rang what it was about.</p>
<p>It was the same every time. I would drag myself up to answer the phone - my
body, my mind screamed at me, but I had gotten good at overriding every instinct
through sheer willpower. I could hear the apologetic tone on the other side, and
I could recognise some of the voices after a while. I mustered up all of my
strength to be and sound as awake as possible. I needed to be professional even
if I was still in my underwear.</p>
<p>I had a glass of water on my bedside table. I&rsquo;d pick that up and head to my
office in the spare room. The computer was always on and always ready to go —
like me I guess. I&rsquo;d log on to the servers, and check the logs. If I can
identify which one fell out of the group, I can restart just that one. If I was
too late or if the issue had escalated, I&rsquo;d have to restart the whole cluster —
shut them all down, give them a few seconds, then bring each one up, while
keeping an eye on them. I could do it half asleep after a while.</p>
<p>Falling back asleep wasn&rsquo;t a breeze either - I was tired - exhausted - but I was
now also wired. Waking up in the morning was harder - the alarm would go off and
my body would be limp. I still remember the sheer power of will to drag myself
into the shower, then carry on with the rest of the day.</p>
<p>Of 266 incidents over about two years, I answered 156.</p>
<p>I remember one particular night, though I do not remember how many times I&rsquo;d
woken up beforehand. I was already tired.</p>
<p>megabus.com had gone offline. I got an alert. &ldquo;But someone else is on call
tonight,&rdquo; I told them. &ldquo;We already tried them twice,&rdquo; came the reply. I had to
deal with this. I had to deal with this.</p>
<p>I remember sitting at my desk at home working on fixing it. At some point,
something was different, though I don&rsquo;t remember what. While I was working on
fixing it, I remember being overcome with an overwhelming impulse to get up from
the chair and walk away — I almost imagined myself walking away. I resisted and
shut down that impulse. I fixed megabus as I had always done. In fixing megabus
though, something broke inside me, somewhere deep, in the very core of my being.
I was never the same again.</p>
<p>I analysed the system top to bottom, inside and out. I even waded through JVM
internals.</p>
<p>It got incrementally better, more stable. I think I rewrote every component that
wasn&rsquo;t the core ticketing system. In the end, what pushed it over the line were
two unexpected changes. Automated nightly restarts of each node in the cluster
and a rate limiter.</p>
<p>On the 10th December 2012, the system, now serving Polskibus, had a sale event.
We had a bank of screens on a wall with all the key stats for the system. It
looked cool, and we felt a bit like we were on a TV show. At peak, nearly 15,000
concurrent sessions — six or seven times the average. Over 30,000 bookings in a
single day, three times more than the normal amounts across all systems.</p>
<p>We watched it closely, all day. Nothing broke. Nothing screamed. Everyone
smiled, but there was no celebration.</p>
]]></content:encoded></item><item><title>Did They Have a Problem That Year?</title><link>https://icle.es/2026/04/14/did-they-have-a-problem-that-year/</link><pubDate>Tue, 14 Apr 2026 10:24:26 +0100</pubDate><guid>https://icle.es/2026/04/14/did-they-have-a-problem-that-year/</guid><description>&lt;p>2008 was a heck of a year for kraya, and for me. We were already operating
megabus.com in the UK, USA, and Canada, along with Oxford Tube, the sales
website for coach usa - all for Stagecoach.&lt;/p>
&lt;p>We were also working on the fringe website. We integrated the website with the
brand spanking new ticketing system - which cost nearly £900k.&lt;/p>
&lt;p>We were also hosting websites for Boots, Kellogg&amp;rsquo;s Food Service and dozens of
other clients.&lt;/p></description><content:encoded><![CDATA[<p>2008 was a heck of a year for kraya, and for me. We were already operating
megabus.com in the UK, USA, and Canada, along with Oxford Tube, the sales
website for coach usa - all for Stagecoach.</p>
<p>We were also working on the fringe website. We integrated the website with the
brand spanking new ticketing system - which cost nearly £900k.</p>
<p>We were also hosting websites for Boots, Kellogg&rsquo;s Food Service and dozens of
other clients.</p>
<p>All of this was held together by three or four developers, two systems
administrators and me.</p>
<p>On the 13 June (incidentally, I got married on the same date years later), as I
was just getting ready for a wild night on the town, a call comes through -
which John answers.</p>
<p>I still remember them laughing and then doing a double take &ldquo;oh, you&rsquo;re serious?
let me get Shri&rdquo;</p>
<p>It was the fringe. We&rsquo;d already known that they were having trouble with their
ticketing system. I&rsquo;d even pitched in, made suggestions - looked at their code
to try and help, but none of that was enough. I expected an update.</p>
<p>They wanted to know if we could put together an interim booking system for them
over the weekend. I wasn&rsquo;t sure. I told them I&rsquo;d speak to my team and get back
to them.</p>
<p>I wasn&rsquo;t involved with the work on the fringe up until this point. I knew very
little about it. I was focused on megabus.com. The US version of the site had a
big marketing campaign happening in a few days and that was what I was meant to
be focused on.</p>
<p>By the time I put the phone down, Chris, who had been the lead on the fringe
already had a answer. &ldquo;We can do it!&rdquo;</p>
<p>&ldquo;But how - it&rsquo;s got to take more than a weekend - right?&rdquo;</p>
<p>The fringe website was already built well and had a clean layer interfacing with
the new ticketing system. In fact, that was the bulk of the work that year.</p>
<p>So.. Chris told me - all we would have to do is to implement the functionality
within that thin layer, fattening it up.</p>
<p>He believed we could do it. I believed him.</p>
<p>I hopped in a cab, headed over to the fringe to talk it through. I didn&rsquo;t
promise them we&rsquo;d be able to get something ready by Monday, but I promised we&rsquo;d
do our best.</p>
<p>We were in the office on the weekend, writing code. I remember working on the
basket, sending diffs over email and generally having a good time.</p>
<p>I even had some megabus US fun to keep me entertained in the form of issues with
loading sheets - I was already in the office, so it was one step easier to fix.</p>
<p>One of the bits of functionality which took a surprising amount of time was the
seat allocation. None of us had to worry about that before - it was just
capacity management on megabus. For the fringe though, we had to allocate actual
seats with seat numbers and everything.</p>
<p>With the fringe we had multiple tables which all joined together (thanks
hibernate) to encode a tremendous amount of detail about the seating plans -
including their physical location on a map.</p>
<p>It was too much detail for us, so we had to simplify it all down to get it
working in the timeframe. We kept most of the rest of the structures intact to
keep the data migration easier once the ticketing system was fixed.</p>
<p>By Monday, we had each managed at the most 6 hours of sleep each of the previous
three nights. I still have vivid memories of a suit of armour that we put
together using packing material while we were waiting for bits of data or
details of logic.</p>
<p>I broke the MySQL replication at 01:24, fixed by 01:32</p>
<p>I remember the delirium setting in. Email sent to the client with &ldquo;fun fun fun
fun fun fun fun&rdquo; as the subject</p>
<p>There were random emails to my brother &ldquo;I&rsquo;m still here!&rdquo;</p>
<p>I also remember making makeshift beds with bubblewrap to get a wee nap here and
there. We were all so exhausted - pumped up on coffee and nicotine.</p>
<p>Finally at 03:35 on the Tue email to client: &ldquo;DONE DONE DONE DONE DONE NODE NODE
NODE NODE&rdquo;</p>
<p>Then at 05:33, requesting a PostgreSQL server rebuild for megabus US for their
marketing campaign.</p>
<p>At 10am on Tuesday, the fringe is finally able to sell tickets. The website
promptly fell over from the load, but we nurse it back and it sells 65k+ tickets
in the first week.</p>
<p>It would be at least two more weeks before the ticketing system is fixed and
brought back in.</p>
<p>For the work we did for them that year, and the previous one, we effectively
only charged about 30% - because that&rsquo;s all they could afford. This year, we
asked if they could put our name on the website.</p>
<p>Over the next two weeks(while megabus US was on their marketing campaign), we
fought many battles. There were 750 duplicate bookings. Numerous customer
complaints (thanks to our name being on the website) - almost all of them
blaming us for the failure of the ticketing system. People did not understand
that we put in the interim one, not the one that failed.</p>
<p>Press releases went out from the fringe - only two credited us. Both misspelt
the company name. Both called us a web design company — which, we were not, had
never been, and had no interest in becoming.</p>
<p>In truth, I wanted to be a hero - I think we all did. What we really wanted was
an acknowledgement of what we had done - which was nowhere to be found. We got
paid though - at least for a part of our effort.</p>
<p>For many years after that, I would tell people with pride - &ldquo;did you know - I
saved the fringe, back in 2008,&rdquo; which was inevitably met with something like
&ldquo;oh, did they have a problem that year?&rdquo;</p>
]]></content:encoded></item><item><title>What we Carried</title><link>https://icle.es/2026/04/13/what-we-carried/</link><pubDate>Mon, 13 Apr 2026 20:02:04 +0100</pubDate><guid>https://icle.es/2026/04/13/what-we-carried/</guid><description>&lt;p>I started my company in 2000. I was 17. I built megabus.com in 2003. I was 21.&lt;/p>
&lt;p>It started off small, and little by little, I carried more and more. I became
we, and we carried more and more. Before we realised, we were carrying a great
deal. Ultimately, though, if something went seriously wrong, it would be on my
shoulders.&lt;/p>
&lt;p>The chart does not capture the scaling of the organisation, or other departments
like tech support or hosting, which had dozens of clients.&lt;/p></description><content:encoded><![CDATA[<p>I started my company in 2000. I was 17. I built megabus.com in 2003. I was 21.</p>
<p>It started off small, and little by little, I carried more and more. I became
we, and we carried more and more. Before we realised, we were carrying a great
deal. Ultimately, though, if something went seriously wrong, it would be on my
shoulders.</p>
<p>The chart does not capture the scaling of the organisation, or other departments
like tech support or hosting, which had dozens of clients.</p>

<figure >
    
        <img src="./gantt_combined_1000.png" 
            alt="gantt chart of the main projects done by kraya through its life" />
    
    
    
        <figcaption>
            
            
            
                <p style="margin: -0.5rem 0 0 0;">
                    Data mined by Claude from my emails, issue trackers and code repos
                
                
                
                
                
                
            </p> 
            
        </figcaption>
    
</figure>


<p>The section at the top is the number of active committers for that quarter. You
can see my on my todd at the start and the rise and the fall of the dev team.</p>
<p>The very peak of it was in 2008. We
<a href="https://icle.es/saving-the-fringe.md">built a booking system over the weekend for the Edinburgh festival fringe because their brand new £800k+ system could only handle one person at a time.</a></p>
<p>There were a handful of us building it while I was simultaneously prepping and
managing the megabus US systems for a sales campaign. At the same time we were
operating megabus across the UK, USA, and Canada, Oxford Tube, Coach USA, the
Fringe website itself and numerous other smaller hosting clients like Boots,
Kelloggs Food Service, and so on.</p>
<p>We were a total of ~4 developers and two systems administrators holding all of
these together.</p>
<p>I was recently reminded of a story a friend of mine told me. Before he was my
friend, he worked with me, and one of the things we did together was one the big
megabus deployments when we migrated to a Java EE ticketing system.</p>
<p>One part of the migration was the data. I had developed a tool to migrate the
data and on the evening - everything was prepared, we were off peak, and we had
taken the site offline. He ran the script, which went on for a wee while and it
failed. It was not meant to do that.</p>
<p>The way he tells the story, he told me about the failure. I come over, look at
the errors, say &ldquo;hmmm, that&rsquo;s interesting,&rdquo; and head off to have a cigarette. A
few minutes later I go over to my desk type away furiously, then asked him to
run it again.</p>
<p>It worked, and completed.</p>
<p>I remember that night. I don&rsquo;t remember what the problem was or how I fixed it,
but I do remember that moment when I went over to see how it had failed. In the
short walk from my desk to his, I reiterated in my mind, all the possible backup
plans - with the worst case scenario being to call off the migration on that
day. We would do it another day. It would cost money, but it would be do-able. I
was ok with that.</p>
<p>I was curious as to where my limits were, so I kept pushing, until I would meet
with a wall.</p>
<p>There were no rails and there were no railings - just a cliff edge, unmarked… I
didn&rsquo;t know that - I expected a brick wall.</p>
<p>The worst of it would be only a few years later, in 2011. We built a new Java EE
ticketing system for a fraction of what it should have cost in about 30% of the
time it needed.</p>
<p>I personally responded to over 250 out of hours emergency tickets over an 18
month period. That was hard!</p>
<p>I had run off a cliff edge, and like the roadrunner in the cartoons, it took a
while before I realised there was no ground beneath me.</p>
<p>A few years after I ran off the cliff, the company shut down. A few years later,
I would start my active recovery journey through therapy. A few years later
still, when I felt ready for a leadership role, I was asked to lead a
problematic team - a role they struggled to fill for a while.</p>
<p>The team worked hard and delivered but struggled with the perception of poor
delivery. Trust was thin, stress was high and morale was low. The situation was
so bad that the week before I was supposed to start, the scrum master who was
supposed to be my guide through it all quit.</p>
<p>I was warned by multiple people that this job was loaded with problems. I took
on the job anyway, without a real guide, straight into multiple serious issues.</p>
<p>I loved it and managed to turn the whole thing around in my first week.
Delivered key items, laid the foundations of trust and improved morale. It took
a bit longer to bed everything down. Within weeks, I was asked if I would take
on leading the entire digital team.</p>
<p>It was here, many years later, I got a sense of how unusual it was for such a
tiny team to do so much.</p>
<p>It was here, many years later, I got a sense of how it is to have guardrails, to
have support, peers to lean on.</p>
<p>It was here, for the first time I realised that the job didn&rsquo;t have to be a
lonely one.</p>
]]></content:encoded></item><item><title>Whatcha Thinking?</title><link>https://icle.es/2026/04/13/whatcha-thinking/</link><pubDate>Mon, 13 Apr 2026 12:05:48 +0100</pubDate><guid>https://icle.es/2026/04/13/whatcha-thinking/</guid><description>&lt;p>I loved working on megabus. I was in love with it. My girlfriend at the time had
a habit of asking what I was thinking about when I looked deep in thought. The
answer - every single time, was inevitably megabus. She eventually stopped
asking.&lt;/p>
&lt;p>I was 22 years old.&lt;/p>
&lt;p>When I built the original prototype for megabus.com, I built it using PHP +
PostgreSQL. I put together a document detailing my reasoning for these choices.
I quoted 33 days for it, built it over six weeks and charged £13,200.&lt;/p></description><content:encoded><![CDATA[<p>I loved working on megabus. I was in love with it. My girlfriend at the time had
a habit of asking what I was thinking about when I looked deep in thought. The
answer - every single time, was inevitably megabus. She eventually stopped
asking.</p>
<p>I was 22 years old.</p>
<p>When I built the original prototype for megabus.com, I built it using PHP +
PostgreSQL. I put together a document detailing my reasoning for these choices.
I quoted 33 days for it, built it over six weeks and charged £13,200.</p>
<p>The support contract was £350/month - for one day a month. On the first day,
megabus.com sold 200 orders.</p>
<p>When megabus had its first expansion, I was up overnight bringing new servers
online and scaling it live. I loved it - my code was finally being tested.</p>
<p>Over a week, I&rsquo;d probably burned through many days of effort. I remember the
project manager specifically asking me to invoice for the extra work I put into
it. I even said that I would - except I didn&rsquo;t.</p>
<p>I&rsquo;ve had a long time to think about this - why did I not send that invoice? I
even had approval.</p>
<p>The answer, as with most things of this nature is complicated. I loved the work
and I didn&rsquo;t want it to end. I didn&rsquo;t want a potential conflict trying to figure
out what a reasonable amount was to charge. I felt that I should have done a
better job in the first place - I felt responsible that I had not told them that
scaling of this nature would not have worked without prep work.</p>
<p>I had not scaled anything before.</p>
<p>I was 22 years old.</p>
<p>I was super grateful that someone believed in me. I naively assumed that they
saw all the extra effort I was putting in and that they would reward me for it -
that they would have my back.</p>
<p>I remember adding a bunch of different bits of functionality because I wanted it
there. I didn&rsquo;t want to go through the process of quoting for it, and it getting
potentially rejected, not to mention the waiting for decisions. One key bit of
functionality I remember is adding in a percentage load column for the loading
sheets. I built it, showed it - they loved it! It went live. I did not charge
for it.</p>
<p>At this point, the vast majority of my time was spent on megabus - very little
of it actually paid for.</p>
<p>At a glance, based on the emails sent, I probably spent a minimum of 10 days
each month supporting megabus when I was charging for one day.</p>
<p>In Jan 2004 - I proposed <em>doubling</em> the contract to two days for £4,800/year. It
probably kicked in in Feb 2004. By March 2004, the site exceeded that revenue
each day.</p>
<p>In the following months, I probably spent, on average a minimum of at least
double the time I was paid for. I should have charged for it.</p>
<p>I grew the team, and the support contract based on the minimum I needed to
maintain the product - not based on the amount of time I was spending.</p>
<p>For my 28th birthday, my girlfriend at the time organised a cake which was a
image representing kraya - which was basically megabus. I felt bad that she
thought that kraya was the most important thing in my life - she was right - but
it still felt bad. kraya had other clients at the time, but my time wasn&rsquo;t
monopolised by other clients, or indeed by kraya - my heart still belonged to
megabus.</p>
<p>And it would all have been fine too, except for a grave miscalculation I made.</p>
<p>In 2010, after trying to rebuild the ticketing system for £500k, and making some
mistakes with people I trusted, kraya ended up in £150k in the hole. We needed
some money urgently.</p>
<p>I was desperate and naively, I reached out to stagecoach for help. I thought
they were my friend - that they would have my back.</p>
<p>They understandably lost a great deal of trust in my ability to manage and lead
my company. I trusted the wrong person - but that was still my mistake. They
were right.</p>
<p>I thought that I&rsquo;d built up enough goodwill that they would help me through
this. I&rsquo;d felt I would have way more than that &ldquo;in the bank&rdquo; in terms of
goodwill. I learned that professional relationships do not work that way that
dark afternoon, standing outside my office on the phone, in the rain.</p>
<p>They didn&rsquo;t make my life easier. Instead, I&rsquo;d ended up rattling the cage - they
were now panicked - realising their over-reliance on an organisation that could
disappear at any point.</p>
<p>Instead of support, I had further actions, renegotiating the contract and what
felt like punitive, and definitely invasive reporting obligations.</p>
<p>I was hurt and angry. I had poured my heart, my soul - hey, my very life into
this product that I loved.</p>
<p>Suffice it to say - I got no help - no loan, no offer of investment - though
they did suggest buying us outright - which I rejected.</p>
<p>I signed a contract under circumstances I would not wish on anyone.</p>
<p>The best I got from them was a challenge - if we were really spending more time
than we were charging for - prove it. I did! We documented every minute we were
spending - I wasted my time on spreadsheets, pointless meetings and work to try
and rebuild the broken trust.</p>
<p>We went from £300k in the hole to £200k profit within a year. We charged for a
whole year in support around 20% of what the system made in a day.</p>
<p>I was 28 years old.</p>
<p>Around the same time, I was also dealing with the operational aftermath of
trying to build a java EE ticketing system over six months for £500k. I thought
it would take a year and cost £1m. In hindsight, it needed two years and
probably three million pounds.</p>
<p>Over 18 months, I personally answered over 150 out of hours emergency calls. We
had a rota and others on call too - but I took the vast majority of these calls.
I felt bad putting others through what I knew was gruelling.</p>
<p>All of this led me down a narrower and narrower path to a serious breakdown -
though I didn&rsquo;t know enough to name it until many years later. All I knew - all
I felt was that something broke in me.</p>
<p>We managed to resolve all of the issues, but the deployment of that version kept
getting pushed.</p>
<p>Stagecoach cancelled the contract in 2012. They had started building a ticketing
system in-house two years prior - the cost of my grave mistake. I wasn&rsquo;t able to
make the meeting - I was in India, and at the same time as the meeting, I was
meeting for the first time the one who is now my wife.</p>
<p>I was 28 years old. I spent the next 15 years putting myself back together.</p>
<p>How much did it cost them to build it inhouse? If I had charged for my time from
the start, would we all have been better off?</p>
<p>I still feel something deep inside me every time I see a megabus - a sense of
pride mixed in with a deep sense of sadness - not for what I lost - but for what
could have been.</p>
<p>I am 44 years old, and I am starting again.</p>
]]></content:encoded></item><item><title>Priced In</title><link>https://icle.es/2026/04/07/priced-in/</link><pubDate>Tue, 07 Apr 2026 09:55:24 +0100</pubDate><guid>https://icle.es/2026/04/07/priced-in/</guid><description>&lt;p>Two ticketing systems. Same client. Same payment provider. We were moving fast —
that was the explicit choice, theirs and mine. The kind of fast where you know
something will go wrong eventually and you price it in rather than try to
prevent it.&lt;/p>
&lt;p>We&amp;rsquo;d done the sensible thing and shared the payment code between them — DRY,
less surface area for error, obvious call.&lt;/p>
&lt;p>Then the larger system needed PostAuth. We updated the code, added a scheduled
task to catch anything the non-deterministic bits missed, moved on.&lt;/p></description><content:encoded><![CDATA[<p>Two ticketing systems. Same client. Same payment provider. We were moving fast —
that was the explicit choice, theirs and mine. The kind of fast where you know
something will go wrong eventually and you price it in rather than try to
prevent it.</p>
<p>We&rsquo;d done the sensible thing and shared the payment code between them — DRY,
less surface area for error, obvious call.</p>
<p>Then the larger system needed PostAuth. We updated the code, added a scheduled
task to catch anything the non-deterministic bits missed, moved on.</p>
<p>A few months later: why has no money come through on the smaller system?</p>
<p>We&rsquo;d ported the PostAuth flow across when we updated the shared code. We hadn&rsquo;t
added the scheduled task. The payment provider, chosen for cheap and cheerful
rather than reliability, failed silently rather than erroring. The accounting
department, running at the same pace as everyone else, hadn&rsquo;t caught the gap.</p>
<p>Four separate things had to go wrong simultaneously. Any one of them holding
would have meant no loss at all.</p>
<p>The client lost money. Not a catastrophic amount, but real money. I braced for
the call.</p>
<blockquote>
<p>Try and let me know the next time you decide to run a sale.</p></blockquote>
<p>He already knew the cost. He&rsquo;d known before the mistake happened.</p>
]]></content:encoded></item><item><title>I Know People Like You</title><link>https://icle.es/2026/03/31/i-know-people-like-you/</link><pubDate>Tue, 31 Mar 2026 10:41:29 +0100</pubDate><guid>https://icle.es/2026/03/31/i-know-people-like-you/</guid><description>&lt;p>A few years ago, I was interviewed for a role. I was talking about a ticketing
system I&amp;rsquo;d built - originally in Spring, then rewritten to use EJB 3.2. The
interviewer didn&amp;rsquo;t look impressed.&lt;/p>
&lt;p>The team had already written a lot of stuff in Spring - but I really did not
like it. There was all this XML all over the place which was annoying, but what
I really didn&amp;rsquo;t like was that the code and configuration for each component was
spread out all over the place. It meant that to understand how something worked,
I had to go hunting. Eventually, I got sick of it, and ported it to EJB myself.&lt;/p></description><content:encoded><![CDATA[<p>A few years ago, I was interviewed for a role. I was talking about a ticketing
system I&rsquo;d built - originally in Spring, then rewritten to use EJB 3.2. The
interviewer didn&rsquo;t look impressed.</p>
<p>The team had already written a lot of stuff in Spring - but I really did not
like it. There was all this XML all over the place which was annoying, but what
I really didn&rsquo;t like was that the code and configuration for each component was
spread out all over the place. It meant that to understand how something worked,
I had to go hunting. Eventually, I got sick of it, and ported it to EJB myself.</p>
<p>Later in the same interview, he said: &ldquo;I know people like you - you come in,
shake things up and get things done - but that&rsquo;s not what I&rsquo;m looking for.&rdquo;</p>
<p>He was right. I understand that. But I&rsquo;ve been thinking about what he was
actually describing.</p>
<p>When megabus.com was still a PHP site, search was the problem. It returned
quickly when the database was healthy and crawled - and slowed down when the
database was struggling. The load came in spikes. Even within a minute, there
were peaks and troughs.</p>
<p>My fix was simple. Before the search query ran, I added a small SQL check: how
many queries are currently active on the database server? If too many, wait a
second and try again. A few retries, then send it anyway.</p>
<p>A rate limiter baked into a search algorithm, written live on a production
server.</p>
<p>There were edge cases to consider, not to mention the load the rate limiter
would add to the database server. I knew though that if it broke, I could fix
it - I could just remove it - live, if needed. Not having the rate limiter was
at the time, more expensive than having it.</p>
<p>It worked. It got us through more than one hump.</p>
<p>The database was still the ceiling. We were on PostgreSQL 7 - no replication
support. Getting a more powerful server was possible but disproportionately
expensive. So I built something.</p>
<p>Two database servers. All writes went to both. Reads were distributed randomly
between them. Everything funnelled through one section of code.</p>
<p>I didn&rsquo;t do this live. I tested it. I knew what failure looked like: if the
servers diverged badly enough, I&rsquo;d pick a primary and reset the other. That was
the contingency. It wasn&rsquo;t a safety net someone else would pull - it was mine.</p>
<p>The data integrity held better than I expected. Under very high load there were
edge cases - ticket IDs for the same customer could be in a different order
across the two servers on a return purchase - but because the IDs were
consistent within each server, it never caused a real problem. It held the fort
until I could replace it with something better.</p>
<p>I occasionally lie awake at night imagining the databases diverging and figuring
out how I would fix it.</p>
<p>I picked PostgreSQL over MySQL when MySQL was the obvious choice. Under heavy
load it stays up — it slows to a crawl, but it keeps going. And it had
transactions. I was building an ecommerce site; I needed transaction support.
MySQL was fast and popular. It also had a habit of giving up under sustained
load. I still pick PostgreSQL - but nowadays, so do most other people.</p>
<p>The thing these decisions had in common was that I was the person who&rsquo;d be
fixing them at 3am if they went wrong. When you&rsquo;re personally accountable for
the consequences, the risk calculus changes. You think harder about what failure
looks like. You build the contingency before you go live. You know which
direction to pull if it goes sideways.</p>
<p>Caution that&rsquo;s never personally tested isn&rsquo;t rigour. It&rsquo;s consequence-avoidance
dressed up as responsibility.</p>
<p>&ldquo;I know people like you - you come in, shake things up and get things done - but
that&rsquo;s not what I&rsquo;m looking for.&rdquo;</p>
]]></content:encoded></item><item><title>It Gets Everywhere</title><link>https://icle.es/2026/03/24/it-gets-everywhere/</link><pubDate>Tue, 24 Mar 2026 20:52:21 +0000</pubDate><guid>https://icle.es/2026/03/24/it-gets-everywhere/</guid><description>&lt;p>In 1999, I was building websites in ASP (before there was .NET) and MSSQL
Server. We had a Windows NT server that I had to restart every week — not
because of updates, because it would get slower and slower until a restart was
the only thing that would fix it.&lt;/p>
&lt;p>We had one ADSL connection coming into the office and three of us. I wanted to
share the internet. Windows NT didn&amp;rsquo;t support it cleanly — it had a way, but it
was clunky enough that no internet was arguably better. We&amp;rsquo;d paid hundreds of
pounds for it.&lt;/p></description><content:encoded><![CDATA[<p>In 1999, I was building websites in ASP (before there was .NET) and MSSQL
Server. We had a Windows NT server that I had to restart every week — not
because of updates, because it would get slower and slower until a restart was
the only thing that would fix it.</p>
<p>We had one ADSL connection coming into the office and three of us. I wanted to
share the internet. Windows NT didn&rsquo;t support it cleanly — it had a way, but it
was clunky enough that no internet was arguably better. We&rsquo;d paid hundreds of
pounds for it.</p>
<p>I&rsquo;d heard about Linux. Downloaded Red Hat, installed it, configured it for NAT.
It worked — it was like magic. I&rsquo;m pretty sure I had to recompile the kernel to
get some bits working, but there were instructions and they were honest. It did
what it said.</p>
<p>Here was software that was completely free — free enough that I could read the
source code, make changes, run it however I wanted. It did more than the
hundreds of pounds worth of garbage sitting on the desk. And once I set it up, I
never had to restart it. Never. Compared to once a week on the NT box.</p>
<p>The difference, in my mind, was simple. Linux was built responsibly. NT was
built as a money-making enterprise.</p>
<p>That held for a long time. I moved to Debian, then celebrated when Ubuntu
arrived and made things more accessible. I&rsquo;ve recently been able to abandon
Windows altogether — gaming on Linux is finally viable. I came back full time
and felt mostly at home.</p>
<p>But there were minor niggles. Things that felt slightly off but that I couldn&rsquo;t
quite name.</p>
<p>Then I started digging into systemd.</p>
<p>I remembered feeling odd about having to run specific commands to read logs. Odd
about one tool doing many different things — which ran contrary to the Unix
philosophy that had made Linux what it was. When I looked into the history of
the opposition to systemd, it was revelatory.</p>
<p>systemd becoming process 1 is, in a word, irresponsible. It makes everything
easier and more accessible, which is why it won. But unlike the Linux of old,
the tradeoff isn&rsquo;t visible upfront, and there&rsquo;s no real choice. The responsible
option isn&rsquo;t the default anymore — it&rsquo;s the thing you have to go looking for.</p>
<p>I thought I had already done the work. I thought I had found the alternative.</p>
<p>While I was celebrating Linux becoming mainstream, I hadn&rsquo;t considered what it
would cost.</p>
<p>The Linux ecosystem had started optimising for mainstream at the expense of
responsibility. It works now, for far more people. But it&rsquo;s a different thing
than it was. When linux was really taking off, there was a joke going around
(before memes were called memes) about Microsoft Linux. Turns out the joke was
on us!</p>
<p>It is always a tradeoff between security and convenience — something convenient
is rarely secure, and vice versa. I think something similar applies to
responsibility. The more accessible you make something, the harder it becomes to
hold the line on what it was built to do.</p>
<p>There was a time when software going wrong meant losing your work. Now it means
losing your money, your reputation, or — in a car, in a hospital — your life.</p>
<p>The context has changed. The attitudes haven&rsquo;t. And the places that once had
better attitudes — the ones built on responsibility, on craft, on caring about
the thing itself — are being pulled in the same direction. <em>It gets everywhere.</em></p>
<p>Do you want your car running Windows? What about systemd?</p>
]]></content:encoded></item><item><title>Even Light Gets Heavier</title><link>https://icle.es/2026/03/24/even-light-gets-heavier/</link><pubDate>Tue, 24 Mar 2026 10:56:05 +0000</pubDate><guid>https://icle.es/2026/03/24/even-light-gets-heavier/</guid><description>&lt;p>A dedicated input type is better than reusing your domain model at the API
boundary. Test layers matter. Writing log statements as you go saves the poor
soul (probably you) debugging blind at 10pm. You know all of this.&lt;/p>
&lt;p>This isn&amp;rsquo;t about any of that.&lt;/p>
&lt;p>It&amp;rsquo;s about the fact that none of those decisions show up in the metrics that
matter to the people making hiring and delivery calls. The cost is immediate and
visible. The return is delayed, quiet, and arrives in the form of things that
didn&amp;rsquo;t happen — the investigation that took two hours instead of two days, the
API change that didn&amp;rsquo;t bleed into the domain model, the bug that the structure
caught before it shipped.&lt;/p></description><content:encoded><![CDATA[<p>A dedicated input type is better than reusing your domain model at the API
boundary. Test layers matter. Writing log statements as you go saves the poor
soul (probably you) debugging blind at 10pm. You know all of this.</p>
<p>This isn&rsquo;t about any of that.</p>
<p>It&rsquo;s about the fact that none of those decisions show up in the metrics that
matter to the people making hiring and delivery calls. The cost is immediate and
visible. The return is delayed, quiet, and arrives in the form of things that
didn&rsquo;t happen — the investigation that took two hours instead of two days, the
API change that didn&rsquo;t bleed into the domain model, the bug that the structure
caught before it shipped.</p>
<p>Sprint velocity captures the extra day. It doesn&rsquo;t capture what that day bought.</p>
<p>This is not a new problem. Most engineers who&rsquo;ve been around long enough have
felt it from both sides - made the careful call and got measured on the
slowness, or inherited the codebase built entirely for speed and paid the tax.
The measurement system was already broken. It has been rewarding the appearance
of velocity over the thing velocity is supposed to serve.</p>
<p>This was true long before anyone was generating code with AI. The PR process in
a lot of teams was already largely theatrical — review comments on naming
conventions while the architectural decisions slipped through unquestioned,
approvals given because the diff was too large to meaningfully read. The gate
was already not doing much. We brushed it under the carpet and moved on.</p>
<p>AI tooling is changing the volume of code moving through that process by an
order of magnitude. The pressure to remove the gate entirely — to trust the
output, to ship faster - is only growing. The faster-is-better incentive that
was already making review ineffective is about to be handed a much larger
surface to work on.</p>
<p>Many years ago, I pitched full redevlopment of a ticketing system from a PHP
based system to a Java EE system because it was struggling to scale.</p>
<p>It probably needed a couple of years to build. They wanted it in six months. I
accepted the challenge.</p>
<p>We built and deployed the system in eight months. We spent the next year fixing
it.</p>
<p>The client then rebuilt it in-house.</p>
<p>When AI runs this experiment at scale, who takes it back?</p>
]]></content:encoded></item><item><title>We Optimised Ourselves to Death</title><link>https://icle.es/2026/02/11/we-optimised-ourselves-to-death/</link><pubDate>Wed, 11 Feb 2026 09:55:18 +0000</pubDate><guid>https://icle.es/2026/02/11/we-optimised-ourselves-to-death/</guid><description>&lt;p>I once worked on a gaming website.&lt;/p>
&lt;p>It collected structured metadata about games - tags for features, screenshots,
videos, reviews. Users contributed information. We gamified participation and
rewarded it with games and gifts.&lt;/p>
&lt;p>It started making money through “similar games” lists.&lt;/p>
&lt;p>All of our traffic came from Google.&lt;/p>
&lt;p>Then we needed more revenue.&lt;/p>
&lt;p>So we did what teams do.&lt;/p>
&lt;p>We added features.&lt;br>
Integrated Steam, Xbox and PSN.&lt;br>
Pulled in achievements.&lt;br>
Expanded recommendation lists.&lt;br>
Tweaked advertising.&lt;br>
Worked on SEO.&lt;/p></description><content:encoded><![CDATA[<p>I once worked on a gaming website.</p>
<p>It collected structured metadata about games - tags for features, screenshots,
videos, reviews. Users contributed information. We gamified participation and
rewarded it with games and gifts.</p>
<p>It started making money through “similar games” lists.</p>
<p>All of our traffic came from Google.</p>
<p>Then we needed more revenue.</p>
<p>So we did what teams do.</p>
<p>We added features.<br>
Integrated Steam, Xbox and PSN.<br>
Pulled in achievements.<br>
Expanded recommendation lists.<br>
Tweaked advertising.<br>
Worked on SEO.</p>
<p>Traffic crept upward.</p>
<p>Still not enough.</p>
<p>Eventually we decided the problem was perception.</p>
<p>The site looked too much like a community project. It needed to feel more
premium. More authoritative. More modern.</p>
<p>So we renamed it.<br>
Changed the domain.<br>
Redesigned it from the ground up.</p>
<p>Months of work.</p>
<p>We launched.</p>
<p>Traffic collapsed.</p>
<p>We never recovered.</p>
<p>In hindsight, the failure wasn’t technical.</p>
<p>It wasn’t branding.</p>
<p>It wasn’t SEO.</p>
<p>It was that we never made a hard decision about what the product actually was.</p>
<p>Was it:</p>
<ul>
<li>A participatory community?</li>
<li>A structured data engine?</li>
<li>A search destination?</li>
<li>A content property optimised for Google?</li>
<li>A recommendations platform?</li>
</ul>
<p>It was all of them.</p>
<p>Weakly.</p>
<p>What Google valued wasn’t polish. It valued volatility.</p>
<p>Our homepage changed many times a day because users were contributing.<br>
Those contributions created fresh internal links, fresh content, fresh signals.</p>
<p>Participation was the engine.</p>
<p>When we redesigned for the information consumer instead of the contributor, we
stabilised the surface.</p>
<p>We accidentally killed the engine.</p>
<p>We optimised the visible layer and ignored the system feeding it.</p>
<p>I first heard the phrase “we’ll fix it in post” from my filmmaker brother.</p>
<p>Something wasn’t quite right during filming, but they moved on anyway. It could
be corrected later.</p>
<p>In film, that’s sometimes true.</p>
<p>In product development, it’s usually self-deception.</p>
<p>Lean encourages delaying decisions to the last responsible moment.<br>
That’s discipline.</p>
<p>What most teams practice is delaying decisions until they become painful.<br>
That’s avoidance.</p>
<p>An MVP is not the smallest thing you can push out.<br>
It is the smallest thing that is coherent and viable.</p>
<p>Viable means it has a clear shape.<br>
It respects constraints.<br>
It closes more questions than it opens.</p>
<p>If you ship something that only works on the happy path, with undefined edges
and postponed trade-offs, you haven’t preserved optionality.</p>
<p>You’ve preserved ambiguity.</p>
<p>Ambiguity spreads.</p>
<p>In code, as defensive layers.<br>
In design, as half-committed patterns.<br>
In product, as multiple possible futures carried at once.</p>
<p>Teams don’t slow down because they’re weak.<br>
They slow down because no one chose.</p>
<p>Every postponed constraint becomes cognitive load.<br>
Every “temporary” rule becomes precedent.</p>
<p>Lean does not say “don’t decide.”</p>
<p>It says: decide at the point where delaying further increases cost.</p>
<p>Most teams drift past that point because deciding feels like loss.</p>
<p>Loss of flexibility.<br>
Loss of imagined futures.<br>
Loss of political safety.</p>
<p>But momentum comes from commitment.</p>
<p>Once something is decided, energy frees up.<br>
The system becomes legible.<br>
Subsequent decisions compound instead of conflict.</p>
<p>We didn’t fail because we built the wrong feature.</p>
<p>We failed because we never chose what we were.</p>
<p>Most startups don’t die from lack of effort.</p>
<p>They die from unmade decisions.</p>
<p>“We’ll fix it later” is not iteration.</p>
<p>It is hope disguised as strategy.</p>
]]></content:encoded></item><item><title>Journey</title><link>https://icle.es/2025/12/09/journey/</link><pubDate>Tue, 09 Dec 2025 16:14:22 +0000</pubDate><guid>https://icle.es/2025/12/09/journey/</guid><description>```

Reaching for the snow atop the everest,
the grey dust of the moon,
or the cold blackness of space.

However we measure the distance,
To that place, those milestones,
that which we convet so.

Be it measured in miles, hearts, accolades,
or pieces of coloured paper or plastic.

We must all pass two milestones,
The thresholds of these bookends,
It must be passed alone.

No matter the distance,
To the fabled top of the world,
Be it eight thousand kilometers,
three hundred thousand miles,
a billion beats,
or a trillion dollars,
all journeys must start with one step,
they must also end with a single step,
Whether your arrive, or you do not.

If all we have is the step,
The first, the last, and each in between,
Do we step on golden rungs of our ladders,
standing tall and proud,
leaning on nothing at all.

If all we have is the step,
perhaps each should be on the ground,
without shoes, socks or fear,
Without concrete, tarmac or things that man built.

If all we have is the step,
perhaps we should feel the ground underneath it,
for it is soft and yeilding,
gentle and loving,
not the way man built it.

If all we have is the step,
perhaps we should take it one step at a time,
As Aesop once tried to tell us,
but this time, perhaps we do not race,
let us too nap, whenever we are tired,
or simply because we want to.

Let us remember,
for in the end,
everything crumbles,
your accolades, your dreams and your legacy,
even your memories,
If not today, then in a year or a hundred.

All we had, and only in that moment,
was the step.

In each of these steps,
the first, last, or each in between,
let us take a beat,
out of the billion we might have.

Let us smell the roses,
Let us smell the manure,
Let me see the colours,
of rainbows after rain,
Feel the air on our face,
Tears streaming down cheeks.

Through joy, or through sorrow,
let us live life.

Take my hand, my friend,
Take my hand, my lover,
Take my hand, you whom I know not yet,
Show me the of the world as you see it,
In greys or the colours of the rainbow,
Hear my song, the one that makes me cry,
Help me walk when I cannot,
Let me carry you when you cannot.

For in my life,
I care not for the milestones,
the numbers, or that which lasts,
not any more.

For in my life,
I let the destination go,
and I let the journey dissolve,
I need no more steps,
For I have all of you.
```</description><content:encoded>```

Reaching for the snow atop the everest,
the grey dust of the moon,
or the cold blackness of space.

However we measure the distance,
To that place, those milestones,
that which we convet so.

Be it measured in miles, hearts, accolades,
or pieces of coloured paper or plastic.

We must all pass two milestones,
The thresholds of these bookends,
It must be passed alone.

No matter the distance,
To the fabled top of the world,
Be it eight thousand kilometers,
three hundred thousand miles,
a billion beats,
or a trillion dollars,
all journeys must start with one step,
they must also end with a single step,
Whether your arrive, or you do not.

If all we have is the step,
The first, the last, and each in between,
Do we step on golden rungs of our ladders,
standing tall and proud,
leaning on nothing at all.

If all we have is the step,
perhaps each should be on the ground,
without shoes, socks or fear,
Without concrete, tarmac or things that man built.

If all we have is the step,
perhaps we should feel the ground underneath it,
for it is soft and yeilding,
gentle and loving,
not the way man built it.

If all we have is the step,
perhaps we should take it one step at a time,
As Aesop once tried to tell us,
but this time, perhaps we do not race,
let us too nap, whenever we are tired,
or simply because we want to.

Let us remember,
for in the end,
everything crumbles,
your accolades, your dreams and your legacy,
even your memories,
If not today, then in a year or a hundred.

All we had, and only in that moment,
was the step.

In each of these steps,
the first, last, or each in between,
let us take a beat,
out of the billion we might have.

Let us smell the roses,
Let us smell the manure,
Let me see the colours,
of rainbows after rain,
Feel the air on our face,
Tears streaming down cheeks.

Through joy, or through sorrow,
let us live life.

Take my hand, my friend,
Take my hand, my lover,
Take my hand, you whom I know not yet,
Show me the of the world as you see it,
In greys or the colours of the rainbow,
Hear my song, the one that makes me cry,
Help me walk when I cannot,
Let me carry you when you cannot.

For in my life,
I care not for the milestones,
the numbers, or that which lasts,
not any more.

For in my life,
I let the destination go,
and I let the journey dissolve,
I need no more steps,
For I have all of you.
```
</content:encoded></item><item><title>Supa Supabase</title><link>https://icle.es/2025/09/23/supa-supabase/</link><pubDate>Tue, 23 Sep 2025 12:09:57 +0100</pubDate><guid>https://icle.es/2025/09/23/supa-supabase/</guid><description>&lt;p>I have always been a fan of &lt;a href="https://icle.es/tags/postgresql">PostgreSQL&lt;/a>. I picked it for
megabus.com and used it when the system grew from a few hundred orders a day to
tens of thousands each day.&lt;/p>
&lt;p>Back in the late noughties, when MySQL was getting popular, people would often
ask me why I was picking Postgres. MySQL was so popular and so fast and it had
cool things like query caching (with postgres did not have). My answer was
simple - postgresql was a &amp;ldquo;real database system.&amp;rdquo; I remember being shocked when
I trying to use transactions and it just ignored it. Postgresql was also really
good at failing gracefully under high load while MySQL had a bad habit of just
stopping processing any requests and becoming unresponsive.&lt;/p></description><content:encoded><![CDATA[<p>I have always been a fan of <a href="https://icle.es/tags/postgresql">PostgreSQL</a>. I picked it for
megabus.com and used it when the system grew from a few hundred orders a day to
tens of thousands each day.</p>
<p>Back in the late noughties, when MySQL was getting popular, people would often
ask me why I was picking Postgres. MySQL was so popular and so fast and it had
cool things like query caching (with postgres did not have). My answer was
simple - postgresql was a &ldquo;real database system.&rdquo; I remember being shocked when
I trying to use transactions and it just ignored it. Postgresql was also really
good at failing gracefully under high load while MySQL had a bad habit of just
stopping processing any requests and becoming unresponsive.</p>
<p>This was more than 15 years ago now so things may have changed with MySQL,
though its acquisition by Oracle was certainly a bad sign.</p>
<p>Years on, and it would seem that people are better informed as to the benefits
of postgresql.</p>
<p>I am currently working on a small game-like social experiment app and it
requires persistent storage. I was intially considering firebase but the costs
put me off. In the world of cloud, I didn&rsquo;t think that spinning up a postgresql
server was really an option - or is it?</p>
<h2 id="supabase">Supabase</h2>
<p>I ran into Supabase as a more cost-effective option and as a bonus it&rsquo;s open
source and based on PostgreSQL. I&rsquo;ll admit that I was skeptical that it would
embody the postgresql philosophy that I knew and loved. I expected another
capitalistic effort at monetising open source.</p>
<p>I was pleasantly surprised. I am in the very early stages of using it, but so
far, I love it. Not a huge fan its love of javascript, but the whole world seems
to be a big fan (I am not - but then I wasn&rsquo;t a fan of MySQL either ;))</p>
<p>I didn&rsquo;t end up going further with it, but the early impression was good enough
that I&rsquo;d reach for it again.</p>
]]></content:encoded></item><item><title>Publishing from hugo to dev.to</title><link>https://icle.es/2025/09/23/publishing-from-hugo-to-dev.to/</link><pubDate>Tue, 23 Sep 2025 09:30:54 +0100</pubDate><guid>https://icle.es/2025/09/23/publishing-from-hugo-to-dev.to/</guid><description>&lt;p>I have been pondering federating parts of my blog to &lt;a href="https://dev.to/">dev.to&lt;/a>
for a bit more visibility.&lt;/p>
&lt;p>However, I had a couple of issues:&lt;/p>
&lt;ul>
&lt;li>With html (instead of markdown), it would not pick up the code blocks
correctly&lt;/li>
&lt;li>With markdown, it would render the relative links incorrectly&lt;/li>
&lt;/ul>
&lt;p>What I really wanted was a way to render the hugo markdown into Jekyll style
(which is what forem wants) but with the links rendered.&lt;/p></description><content:encoded><![CDATA[<p>I have been pondering federating parts of my blog to <a href="https://dev.to/">dev.to</a>
for a bit more visibility.</p>
<p>However, I had a couple of issues:</p>
<ul>
<li>With html (instead of markdown), it would not pick up the code blocks
correctly</li>
<li>With markdown, it would render the relative links incorrectly</li>
</ul>
<p>What I really wanted was a way to render the hugo markdown into Jekyll style
(which is what forem wants) but with the links rendered.</p>
<p>This was a little more complicated than I would have liked.</p>
<h2 id="goals">Goals</h2>
<p>At a minimum, I wanted two main things:</p>
<ul>
<li>Render code blocks correctly</li>
<li>Relative URLs should be rendered as absolute (because they won&rsquo;t work on
dev.to)</li>
</ul>
<h2 id="pre-existing-solutions">Pre-existing Solutions</h2>
<p>I found <a href="https://github.com/maelvls/hudevto">hugodevto</a> which looked promising
except:</p>
<ul>
<li>Not a fan of having to manually update hundreds of posts with the devto id</li>
<li>Had a couple of fiddly bits to get it working (my plain text outputs had some
troubles for unknown reasons)</li>
<li>It rendered image urls,
<a href="https://github.com/maelvls/hudevto/issues/2#issuecomment-3302934120">but not regular urls.</a></li>
</ul>
<p>Ultimately though, it felt a bit bulkier than what I was looking for</p>
<h2 id="using-hugo">Using Hugo</h2>
<p>I had <a href="https://icle.es/tags/hugo">done enough work with hugo</a> and
<a href="https://icle.es/tags/inscribe">output formats</a> to have a vague idea of how to make it work.</p>
<h3 id="limitations">Limitations</h3>
<p>There are a few limitations to doing it this way though. Hugo
<a href="https://gohugo.io/render-hooks/introduction/">does not provide render hooks for everything</a>.
You will end up with html in the output. However, since Forem (and Jekyll) will
just render them, it fits my use case. It won&rsquo;t work as well if you try and use
this to generate like for like markdown usable in Jekyll.</p>
<h3 id="define-a-new-content-type">Define a new content type</h3>
<p>We want a new content type which will output markdown</p>
```toml
[outputFormats.jekyll]
    mediaType = "text/markdown"
    baseName = "index"
    isPlainText = true
    isHTML = false
    notAlternative = true
    path = 'jekyll'        # put the output in `public/jekyll` so it's easier to find

[outputs]
    page = ['html', 'jekyll'] # Output all pages in our jekyll format as well
```
<p>You also need a basic template before hugo will output our markdown files.</p>
<p><a href="https://icle.es/layouts/_default/single.jekyll.md"><code>layouts/_default/single.jekyll.md</code></a></p>
```gotemplate
---
title: {{ .Title }}
published: true
date: {{ .Date }}
{{- with .Params.tags }}
tags: [{{ delimit . ", " }}]
{{- end }}
canonical_url: {{ .Permalink }}
---

{{ .Content }}
```
<p>With this, if you <code>hugo build</code>, it&rsquo;ll render the <code>.md</code> files, but the content
will be html.</p>
<h3 id="render-code-blocks-as-markdown">Render code blocks as markdown</h3>
<p>We can use the
<a href="https://gohugo.io/render-hooks/code-blocks/">code block render hook</a> to
&ldquo;convert them&rdquo; back to markdown.</p>
<p><a href="https://icle.es/layouts/_default/_markup/render-codeblock.jekyll.md"><code>layouts/_default/_markup/render-codeblock.jekyll.md</code></a></p>
```gotemplate
```
{{ .Type }}
{{ .Inner }}
```
```
<h3 id="render-absolute-urls">Render absolute urls</h3>
<p>I already have a pretty extensive
<a href="https://icle.es/layouts/_default/_markup/render-link.html"><code>render-link</code></a> so updating
it was just a case of making a copy of it and replacing relative url references
with absolute ones.</p>
<p>It&rsquo;s fine for it to be in html because Forem will still render it correctly.
They could be rendered as markdown and it should work just as well.</p>
<p><a href="https://icle.es/layouts/_default/_markup/render-link.jekyll.md"><code>layouts/_default/_markup/render-link.jekyll.md</code></a></p>
```gotemplate
{{ /* other content */ }}
  <a href="{{ printf "%s#%s" .PageInner.Permalink $u.Fragment | safeURL }}" {{ with .Title }}title="{{ . }}"{{ end }}>{{ $text }}</a>
{{ /* other content */ }}
```
<h3 id="output">Output</h3>
<p>With these relatively minor changes, I was able to render markdown files I could
then pop into dev.to and it works for the handful I set up.</p>
<h2 id="next-steps">Next Steps</h2>
<h3 id="images">Images</h3>
<p>One big glaring omission is images - it&rsquo;s not as relevant for me because I
rarely use images.</p>
<p>I expect it to be easy enough to use the
<a href="https://gohugo.io/render-hooks/images/">image render hook</a> to achieve this.</p>
<h3 id="tags">Tags</h3>
<p>One of the issues I have is that dev.to has a strict tag limit of four.
Currently I manually edit that when I create the post on dev.to.</p>
<p>It would be better to have a <code>devto_tags</code> field because my local content tags
aren&rsquo;t always relevant for dev.to.</p>
<p>I could also write a script to automate the setting of the <code>devto_tags</code> field
automatically based on the first four tags, and mapping from my tags to dev.to
tags if necessary by building a small map data set somewhere.</p>
<h3 id="automation">Automation</h3>
<p>Once the above two are done, it would be good to automate it. I could</p>
<ul>
<li><a href="https://developers.forem.com/api/v0#tag/articles/operation/getUserAllArticles">get all the posts</a></li>
<li>get all the local posts</li>
<li>map them based on the canonical url (which is returned by the endpoint)</li>
<li>upload updated ones.</li>
</ul>
<p>Not all my posts are technical, so I&rsquo;d also want to add a field to the
frontmatter (<code>devto_published</code>) and figure out a way to push updates only if
there are changes.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I have a working solution for now - and if dev.to brings enough traffic / value,
then I&rsquo;ll consider spending a bit more time adding polish.</p>
<p>For the time being though, seems to work!</p>
<p>Feel free to use any of this code (such that it is) as you wish.</p>
]]></content:encoded></item><item><title>Using `locateFile` to have js and wasm in different locations with emscripten</title><link>https://icle.es/2025/09/18/using-locatefile-to-have-js-and-wasm-in-different-locations-with-emscripten/</link><pubDate>Thu, 18 Sep 2025 10:15:42 +0100</pubDate><guid>https://icle.es/2025/09/18/using-locatefile-to-have-js-and-wasm-in-different-locations-with-emscripten/</guid><description>&lt;p>As part of building &lt;a href="https://icle.es/excursions/shine.md">shine&lt;/a>, I am using
&lt;a href="https://lume.land">lume&lt;/a> and webassembly with zig.&lt;/p>
&lt;p>zig, through emscripten generates both a js and wasm file, which, by default are
expected to be in the same directory.&lt;/p>
&lt;p>I wanted to put them in different places, and struggled to get that working with
lume for a bit. I did eventually solve it though:&lt;/p>
&lt;h2 id="include-emscripten-js-file">Include emscripten js file&lt;/h2>
&lt;p>Firstly, the js file output from emscripten should be included in you page
manually. I had used &lt;code>site.add&lt;/code> which meant that it was loaded &lt;em>before&lt;/em> we could
put the override for &lt;code>locateFile&lt;/code> in &lt;code>window.Module&lt;/code>.&lt;/p></description><content:encoded><![CDATA[<p>As part of building <a href="https://icle.es/excursions/shine.md">shine</a>, I am using
<a href="https://lume.land">lume</a> and webassembly with zig.</p>
<p>zig, through emscripten generates both a js and wasm file, which, by default are
expected to be in the same directory.</p>
<p>I wanted to put them in different places, and struggled to get that working with
lume for a bit. I did eventually solve it though:</p>
<h2 id="include-emscripten-js-file">Include emscripten js file</h2>
<p>Firstly, the js file output from emscripten should be included in you page
manually. I had used <code>site.add</code> which meant that it was loaded <em>before</em> we could
put the override for <code>locateFile</code> in <code>window.Module</code>.</p>
<p>I am using the
<a href="https://github.com/lumeland/theme-simple-blog">simple-blog theme</a>, so I add the
following line to the top of <code>src/index.vto</code></p>
```html
<script defer src="/js/shine.js"></script>
```
<h2 id="override-module">Override <code>Module</code></h2>
<p>You can override how emscripten finds the wasm file in the
<a href="https://emscripten.org/docs/api_reference/module.html#Module.locateFile">Module Object</a>.</p>
<p>This can be done in the html file in a script block, or even better, in a js
file that is included automatically.</p>
<p><code>js/main.js</code> felt like a good place.</p>
```javascript
window.Module = {
  locateFile: function (path, scriptDirectory) {
    if (path === "shine.wasm") {
      return "/static/shine/shine.wasm";
    } else {
      return scriptDirectory + path;
    }
  },
};
```
<h2 id="closing">Closing</h2>
<p>With this setup, I can not only have the js and wasm files in different
locations, it&rsquo;s also easy to modify / augment the <code>Module</code> object.</p>
]]></content:encoded></item><item><title>Calling Javascript from Zig through WebAssembly</title><link>https://icle.es/2025/09/17/calling-javascript-from-zig-through-webassembly/</link><pubDate>Wed, 17 Sep 2025 15:52:20 +0100</pubDate><guid>https://icle.es/2025/09/17/calling-javascript-from-zig-through-webassembly/</guid><description>&lt;p>The next step for &lt;a href="https://icle.es/excursions/shine.md">shine&lt;/a> is to build a bridge
between &lt;a href="https://icle.es/tags/zig">zig&lt;/a> and &lt;a href="https://icle.es/tags/javascript">javascript&lt;/a>.&lt;/p>
&lt;p>I am currently planning to using &lt;a href="https://supabase.com/">supabase&lt;/a> for storage.
Unsurprisingly, it does not have a zig sdk. It does, however, have a javascript
sdk.&lt;/p>
&lt;p>If I can write basic CRUD operations in javascript and call that from zig
through webassembly, that could make that integration a lot easier.&lt;/p>
&lt;h2 id="goals">Goals&lt;/h2>
&lt;p>There are a few ideal restrictions for me - mainly because writing javascript is
not fun for me.&lt;/p></description><content:encoded><![CDATA[<p>The next step for <a href="https://icle.es/excursions/shine.md">shine</a> is to build a bridge
between <a href="https://icle.es/tags/zig">zig</a> and <a href="https://icle.es/tags/javascript">javascript</a>.</p>
<p>I am currently planning to using <a href="https://supabase.com/">supabase</a> for storage.
Unsurprisingly, it does not have a zig sdk. It does, however, have a javascript
sdk.</p>
<p>If I can write basic CRUD operations in javascript and call that from zig
through webassembly, that could make that integration a lot easier.</p>
<h2 id="goals">Goals</h2>
<p>There are a few ideal restrictions for me - mainly because writing javascript is
not fun for me.</p>
<ul>
<li>Use the Supabase js/ts library through zig</li>
<li>Use TypeScript as much as possible. (I don&rsquo;t love TypeScript, but at least
it&rsquo;s not javascript)</li>
<li>Keep as much of the supabase related code in the web part so that
<a href="https://icle.es/tags/deno">deno</a> and <a href="https://icle.es/tags/lume">lume</a> can handle any heavy lifting.</li>
</ul>
<h2 id="options">Options</h2>
<p>I can&rsquo;t use FFI(Foreign Function Interface):</p>
```zig
extern "env" fn jsLog(ptr: [*]const u8, len: usize) void;
```
```javascript
const wasm = await WebAssembly.instantiateStreaming(fetch("prog.wasm"), {
  env: {
    jsLog: (ptr, len) => {
      /* read from memory and console.log */
    },
  },
});
```
<p>because imgui pulls in emscripten, which means we don&rsquo;t have the ability to call
<code>instantiateStreaming</code>.</p>
<p>With emscripten, declaring an external function is easy enough.</p>
```zig
extern fn jsLog(ptr: [*]const u8, len: usize) void;
```
<p>There are a couple of options to wire them up to the javascript:</p>
<h3 id="libraryjs--mergeinto"><code>library.js</code> / <code>mergeInto</code></h3>
<p>This option requires javascript files on the zig side. If you want to start with
typescript, you&rsquo;ll need to integrate a transpiler into the build chain as well.</p>
<p>First, you want a javascript file - let&rsquo;s call it <code>libshine.js</code>, and pop it into
a <code>js</code> dir.</p>
```javascript
// js/libshine.js
// Emscripten will provide these globals at link/runtime
declare var mergeInto: (lib: any, funcs: Record<string, Function>) => void;
declare var LibraryManager: { library: any };
declare function UTF8ToString(ptr: number, len?: number): string;

mergeInto(LibraryManager.library, {
  jsLog: (ptr: number) => {
    const msg = UTF8ToString(ptr);
    console.log("🟢 Zig says:", msg);
  },
});
```
<p>We then need to pass this <code>js</code> file into the build step</p>
<p>as part of my sokol build step, I pass it in as <code>.extra_args</code>.</p>
```zig
// create a build step which invokes the Emscripten linker
const link_step = try sokol.emLinkStep(b, .{
    .lib_main = shine,
    .target = opts.mod_main.resolved_target.?,
    .optimize = opts.mod_main.optimize.?,
    .emsdk = dep_emsdk,
    .use_webgl2 = true,
    .use_emmalloc = true,
    .use_filesystem = false,
    .shell_file_path = opts.dep_sokol.path("src/sokol/web/shell.html"),
    // set the js file here
    .extra_args = &.{
        "--js-library", "js/libshine.js",
    },
});
```
<p>We can then call it from zig, with something like:</p>
```zig
pub fn main() void {
    jsLog("hello from zig");
}

extern fn jsLog(ptr: [*]const u8) void;
```
<p>From my firefox console:</p>
```
Lume live reloading is ready. Listening for changes...     localhost:3000:102:15
🟢 Zig says: hello from zig                                shine.js:3168:11
```
<h3 id="em_js--em_asm"><code>EM_JS</code> / <code>EM_ASM</code></h3>
<p>The other option is to use
<a href="https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code-call-javascript-from-native"><code>EM_JS</code></a>
which involves writing a wee bit of <code>C</code>, which can embed the <code>javascript</code>.</p>
<p>In theory, it&rsquo;s as simple as:</p>
```c
#include <emscripten.h>

EM_JS_DEPS(bla, "$UTF8ToString");

EM_JS(void, jsLog, (const char* s), {
  console.log(UTF8ToString(s));
});
```
<p>and adding it into the build file:</p>
```zig
// build the main file into a library, this is because the WASM 'exe'
// needs to be linked in a separate build step with the Emscripten linker
const shine = b.addLibrary(.{
    .name = "shine",
    .root_module = opts.mod_main,
});

// get the Emscripten SDK dependency from the sokol dependency
const dep_emsdk = opts.dep_sokol.builder.dependency("emsdk", .{});

// need to inject the Emscripten system header include path into
// the cimgui C library otherwise the C/C++ code won't find
// C stdlib headers
const emsdk_incl_path = dep_emsdk.path("upstream/emscripten/cache/sysroot/include");

shine.root_module.addCSourceFile(.{
    .file = b.path("src/libjs.c"),
    .flags = &.{}, // optional extra emcc flags
});
shine.addSystemIncludePath(emsdk_incl_path);
```
<p>The calling code in <code>main.zig</code> remains the same:</p>
```zig
pub fn main() void {
    jsLog("hello from zig");
}

extern fn jsLog(ptr: [*]const u8) void;
```
<p>However, this didn&rsquo;t work, and failed with:</p>
```
error: undefined symbol: jsLog (referenced by root reference (e.g. compiled C/C++ code))
warning: To disable errors for undefined symbols use `-sERROR_ON_UNDEFINED_SYMBOLS=0`
warning: _jsLog may need to be added to EXPORTED_FUNCTIONS if it arrives from a system library
Error: Aborting compilation due to previous errors
```
<p>Thanks to
<a href="https://ziggit.dev/t/help-with-getting-a-simple-call-to-js-through-emscripten-working/12090/3">some help</a>
from <a href="https://ziggit.dev/u/floooh">flooh</a> (who btw put together the
<a href="https://github.com/floooh/sokol">sokol</a> and
<a href="https://github.com/floooh/sokol-zig">sokol-zig</a> packages as well the
<a href="https://github.com/floooh/sokol-zig-imgui-sample">sokol-imgui-sample</a> template
which I used to kick start this project.), I was able to get it working.</p>
<p>Turns out the c file needs to have a function in it that is used in the zig
file - it doesn&rsquo;t need to do anything.</p>
<p>So, based on the suggestion, <code>libjs.c</code> changes to:</p>
```c
#include <emscripten.h>

EM_JS_DEPS(bla, "$UTF8ToString");

EM_JS(void, jsLog, (const char* s), {
  console.log(UTF8ToString(s));
});

void dummy(void) {};
```
<p>and in <code>main.zig</code>:</p>
```zig
pub fn main() void {
    dummy();
    jsLog("hello from zig");
}

extern fn jsLog(ptr: [*]const u8) void;

extern fn dummy() void;
```
<p>From my firefox console:</p>
```
Lume live reloading is ready. Listening for changes...     localhost:3000:102:15
🟢 Zig says: hello from zig                                shine.js:3168:11
```
<p>You can see a working example in [my forked repo](</p>
<h2 id="em_js-directly-through-zig-unsuccessful"><code>EM_JS</code> directly through <code>zig</code> [unsuccessful]</h2>
<p>Looking at the macro for <code>EM_JS</code> and with my good friend ChatGPT, I attempted
translating it to zig and made some progress, but ultimately failed to get it
working. I&rsquo;ll leave the work here in the hopes it might be helpful.</p>
```c
#define _EM_JS(ret, c_name, js_name, params, code)                             \
  _EM_BEGIN_CDECL                                                              \
  ret c_name params EM_IMPORT(js_name);                                        \
  __attribute__((visibility("hidden")))                                        \
  void* __em_js_ref_##c_name = (void*)&c_name;                                 \
  EMSCRIPTEN_KEEPALIVE                                                         \
  __attribute__((section("em_js"), aligned(1))) char __em_js__##js_name[] =    \
    #params "<::>" code;                                                       \
  _EM_END_CDECL
```
<p>The above macro translates to zig roughly (with help from ChatGPT) as:</p>
```zig
extern fn jsLog(ptr: [*]const u8) void;

/// 2. Keep a reference to avoid the linker removing the function.
///    Same role as __em_js_ref_* in the C macro.
pub export const __em_js_ref_jsLog = &jsLog;

/// 3. Embed the JS implementation in a special section called "em_js".
///    Emscripten will scan this and inject the code into the output JS.
export const __em_js__jsLog align(1) linksection("em_js") =
    "(const char* s)<::>{ console.log(UTF8ToString(s)); }\x00";

pub export fn dummy() void {}
```
<p>I added a <code>pub fn</code> and called it from main:</p>
```zig
pub fn log(ptr: [*]const u8) void {
    jsLog(ptr);
}
```
<p>Which gave me the familiar error about not being able to find <code>jsLog</code>.</p>
<p>comparing the linker sections gave some clues:</p>
```
❯ wasm-objdump --section=linking -x <path/to/libjs.o>

libjs.o:        file format wasm 0x1

Section Details:

Custom:
 - name: "linking"
  - symbol table [count=9]
   - 0: F <dummy> func=1 [ binding=global vis=hidden ]
   - 1: D <__em_js_ref_jsLog> segment=0 offset=0 size=4 [ binding=global vis=hidden ]
   - 2: F <jsLog> func=0 [ undefined explicit_name binding=global vis=default ]
   - 3: D <__em_js__jsLog> segment=1 offset=0 size=53 [ exported no_strip binding=global vis=hidden ]
   - 4: S <.debug_abbrev> section=7 [ binding=local vis=default ]
   - 5: G <env.__stack_pointer> global=0 [ undefined binding=global vis=default ]
   - 6: S <.debug_str> section=9 [ binding=local vis=default ]
   - 7: T <env.__indirect_function_table> table=0 [ undefined exported no_strip binding=global vis=default ]
   - 8: S <.debug_line> section=10 [ binding=local vis=default ]
  - segment info [count=2]
   - 0: .data.__em_js_ref_jsLog p2align=2 [ ]
   - 1: em_js p2align=0 [ RETAIN ]
```
<p>and the zig object:</p>
```
❯ wasm-objdump --section=linking -x js.o

js.o:   file format wasm 0x1

Section Details:

Custom:
 - name: "linking"
  - symbol table [count=6]
   - 0: F <dummy> func=1 [ binding=global vis=default ]
   - 1: D <__em_js_ref_jsLog> segment=0 offset=0 size=4 [ binding=global vis=default ]
   - 2: F <jsLog> func=0 [ undefined explicit_name binding=global vis=default ]
   - 3: D <__em_js__jsLog> segment=1 offset=0 size=4 [ binding=global vis=default ]
   - 4: D <__anon_946> segment=2 offset=0 size=54 [ binding=local vis=default ]
   - 5: T <env.__indirect_function_table> table=0 [ undefined exported no_strip binding=global vis=default ]
  - segment info [count=3]
   - 0: .rodata.__em_js_ref_jsLog p2align=2 [ ]
   - 1: em_js p2align=0 [ ]
   - 2: .rodata.__anon_946 p2align=0 [ ]`
```
<p>From what I could understand (which is little), it looks like <code>__em_js__jsLog</code>
in the zig obj is a pointer while from C, it&rsquo;s the full string.</p>
<p>hardcoding it as a static array helped:</p>
```zig
export const __em_js__jsLog align(1) linksection("em_js") = [_]u8{
    '(', 'c','o','n','s','t',' ','c','h','a','r','*',' ','s',')',
    '<',':',':','>','{',' ',
    'c','o','n','s','o','l','e','.','l','o','g','(',
    'U','T','F','8','T','o','S','t','r','i','n','g','(',
    's',')',')',';',' ','}','\x00',
};
```
<p>The output from this is a little more promising</p>
```
❯ wasm-objdump --section=linking -x js.o

js.o:   file format wasm 0x1

Section Details:

Custom:
 - name: "linking"
  - symbol table [count=5]
   - 0: F <dummy> func=1 [ binding=global vis=default ]
   - 1: D <__em_js_ref_jsLog> segment=0 offset=0 size=4 [ binding=global vis=default ]
   - 2: F <jsLog> func=0 [ undefined explicit_name binding=global vis=default ]
   - 3: D <__em_js__jsLog> segment=1 offset=0 size=53 [ binding=global vis=default ]
   - 4: T <env.__indirect_function_table> table=0 [ undefined exported no_strip binding=global vis=default ]
  - segment info [count=2]
   - 0: .rodata.__em_js_ref_jsLog p2align=2 [ ]
   - 1: em_js p2align=0 [ ]
```
<p>Let&rsquo;s look at the two side by side</p>
```
# From C
- 3: D <__em_js__jsLog> segment=1 offset=0 size=53 [ exported no_strip binding=global vis=hidden ]
# From zig
- 3: D <__em_js__jsLog> segment=1 offset=0 size=53 [ binding=global vis=default ]
```
<p>There are some clear differences in how the two are output and I am already
beyond my knowledge level here - so I&rsquo;ll leave it to someone who knows this
stuff better (or wait until I do)</p>
<p>You can
<a href="https://github.com/drone-ah/sokol-zig-imgui-sample/tree/zig_em_js">check out the code in the branch of my forked repo</a></p>
<h2 id="next-steps">Next steps</h2>
<p>My plan is to use <code>EM_JS</code> through <code>C</code> to implement glue JavaScript functions -
something like:</p>
```c
EM_JS(void, jsLog, (const char* s), {
	Module.jsLog(UTF8ToString(s));
});
```
<p>By doing this, I can have one-line js code in the <code>.c</code> file and all the
implementation can go into the web side (and can easily be TypeScript too).</p>
```javascript
window.Module = {
  jsLog: function (msg) {
    console.log("🟢 Zig says:", msg);
  },
};
```
]]></content:encoded></item><item><title>Auto reload WASM with zig+lume</title><link>https://icle.es/2025/09/16/auto-reload-wasm-with-zig-lume/</link><pubDate>Tue, 16 Sep 2025 15:56:42 +0100</pubDate><guid>https://icle.es/2025/09/16/auto-reload-wasm-with-zig-lume/</guid><description>&lt;p>I’ve been taking some time off to rest and recover from health issues that made
it hard to focus. To ease back in, I’ve started a small project:
&lt;a href="https://icle.es/excursions/shine.md">shine&lt;/a>&lt;/p>
&lt;p>The project would do well to be multiplatform - mobile and web. The obvious
choice was &lt;a href="https://icle.es/tags/flutter">flutter&lt;/a> and I have enjoyed working with it before.&lt;/p>
&lt;p>However, as I&amp;rsquo;m currently in love with &lt;a href="https://icle.es/tags/zig">zig&lt;/a>, I wanted to work with
that instead.&lt;/p>
&lt;h2 id="libraries">Libraries&lt;/h2>
&lt;h3 id="graphics">Graphics&lt;/h3>
&lt;p>I&amp;rsquo;ve been playing with &lt;a href="https://icle.es/tags/raylib">raylib&lt;/a> and that was my initial instinct.
However, raylib
&lt;a href="https://github.com/raysan5/raylib/discussions/2681">does not support iOs&lt;/a> and
&lt;a href="https://github.com/raysan5/raylib/discussions/3626">has issues with wasm&lt;/a>&lt;/p></description><content:encoded><![CDATA[<p>I’ve been taking some time off to rest and recover from health issues that made
it hard to focus. To ease back in, I’ve started a small project:
<a href="https://icle.es/excursions/shine.md">shine</a></p>
<p>The project would do well to be multiplatform - mobile and web. The obvious
choice was <a href="https://icle.es/tags/flutter">flutter</a> and I have enjoyed working with it before.</p>
<p>However, as I&rsquo;m currently in love with <a href="https://icle.es/tags/zig">zig</a>, I wanted to work with
that instead.</p>
<h2 id="libraries">Libraries</h2>
<h3 id="graphics">Graphics</h3>
<p>I&rsquo;ve been playing with <a href="https://icle.es/tags/raylib">raylib</a> and that was my initial instinct.
However, raylib
<a href="https://github.com/raysan5/raylib/discussions/2681">does not support iOs</a> and
<a href="https://github.com/raysan5/raylib/discussions/3626">has issues with wasm</a></p>
<p>I considered a few options, including</p>
<ul>
<li><a href="https://www.libsdl.org/">sdl</a>, which looked great but was perhaps a little
too low level for me.</li>
<li><a href="https://github.com/Jack-Ji/jok">jok</a> - does not support mobile and possibly
has a little more than I needed.</li>
</ul>
<p>In the end, I decided to go with <a href="https://github.com/floooh/sokol">sokol</a> and
<a href="https://github.com/floooh/sokol-zig">sokol-zig</a>.</p>
<p>While a little more lower level than raylib, it has:</p>
<ul>
<li>a modern clean api</li>
<li>first class mobile support</li>
<li>first class wasm support</li>
</ul>
<h3 id="ui">UI</h3>
<p>I&rsquo;ve been working with <a href="https://icle.es/tags/dvui">dvui</a> a lot recently. Unfortunately, it
doesn&rsquo;t support sokol. <a href="https://github.com/SpexGuy/Zig-ImGui">imgui</a> is a better
option.</p>
<p>There is even a
<a href="https://github.com/floooh/sokol-zig-imgui-sample">template project that I could start from</a>.</p>
<h2 id="wasm-first">WASM first</h2>
<p>To keep things straightforward, I decided to start with wasm. If I make the site
mobile friendly, I could see how it goes and see if it needs a mobile version.</p>
<p>I will need some shared data storage and have been considering
<a href="https://supabase.com/">supabase</a>, which has <a href="https://icle.es/tags/javascript">javascript</a>
libs. By using <a href="https://icle.es/tags/wasm">wasm</a>, I can effectively shim in js functions to
handle that instead of having to write bare rest calls from zig.</p>
<h3 id="structuring-the-project">Structuring the project</h3>
<p>Is this a zig project with a web component, vice versa or indeed two independent
parts that work together.</p>
<p>I did a fair amount of web searching to see if there was some guidance I could I
find for a good way to structure a relatively straightforward zig+js project.</p>
<p>I could not find one. In the end, I decided to keep it fairly straightforward.</p>
```
- shine/
  - src/ # zig code
  - web/ # all the web stuff
```
<h3 id="frontend">Frontend</h3>
<p>After a bit of research, and realising that I will probably need a little bit of
supporting content around <a href="https://icle.es/excursions/shine.md">shine</a>, I decided to go
with <a href="https://lume.land/">lume</a></p>
<p>I used the <a href="https://github.com/lumeland/theme-simple-blog">simple-blog theme</a> as
a template to start from. I could have just pulled the template in but I wanted
a custom homepage.</p>
<p>When I tried to add an <code>index.md</code>, it complained about two files wanting to
write <code>index.html</code>. From what I could find, the easiest way to override the
homepage was to just pick up the theme and edit it - which was easy enough.</p>
<h3 id="wasm--frontend">WASM =&gt; frontend</h3>
<p>I didn&rsquo;t want to copy over the wasm and the js file every time, so I added a
couple of steps to <code>build.zig</code> right after the <code>link_step</code> (also included below)</p>
```zig
// build.zig
// create a build step which invokes the Emscripten linker
const link_step = try sokol.emLinkStep(b, .{
    .lib_main = shine,
    .target = opts.mod_main.resolved_target.?,
    .optimize = opts.mod_main.optimize.?,
    .emsdk = dep_emsdk,
    .use_webgl2 = true,
    .use_emmalloc = true,
    .use_filesystem = false,
    .shell_file_path = opts.dep_sokol.path("src/sokol/web/shell.html"),
});
// attach to default target
b.getInstallStep().dependOn(&link_step.step);

// Copy shine.js from default emscripten output
const js_install = b.addInstallFileWithDir(
    b.path("zig-out/web/shine.js"),
    .{ .custom = "../web/src/static/shine" },
    "shine.js",
);
js_install.step.dependOn(&link_step.step);
b.getInstallStep().dependOn(&js_install.step);

// Copy shine.wasm from default emscripten output
const wasm_install = b.addInstallFileWithDir(
    b.path("zig-out/web/shine.wasm"),
    .{ .custom = "../web/src/static/shine" },
    "shine.wasm",
);
wasm_install.step.dependOn(&link_step.step);
```
<p>These steps will copy across the wasm and the js file across to <code>static/shine</code>.
I wanted to put the js in <code>src/js</code> and the wasm in the static dir. However, the
js file expects the wasm in the same dir. <del>I tried overriding <code>locateFile</code> but
it didn&rsquo;t
work.</del>(<a href="https://icle.es/lume/wasm.md">you can use a different location by overriding <code>locateFile</code></a>)</p>
```html
<!-- index.vto -->
<script>
  window.Module = {
    locateFile: (path, prefix) => {
      if (path.endsWith(".wasm")) {
        return "/static/shine/shine.wasm";
      }
      return prefix + path;
    },
  };
</script>
<script src="/js/shine.js"></script>
```
<p>I was able to get lume to process the javascript file by <code>add</code>ing it.</p>
```javascript
// _config.ts
site.add("static/shine/shine.js");
```
<h2 id="conclusion">Conclusion</h2>
<p>With all of these set up, I was able to run:</p>
```bash
zig build -Dtarget=wasm32-emscripten --watch
```
```

```
<p>in one window. This command will rebuild wasm and provide it to lume whenever
the zig code changes.</p>
```bash
deno task serve
```
<p>Running this in another window will mean that lume will rebuild on any changes,
including a new wasm file and redeploy. The redeploy will trigger an auto-reload
of the page as well if I have it in a browser.</p>
<p>I now effectively have automated reload with changes if I make changes in either
zig or the frontend.</p>
<p>I don&rsquo;t have <em>hot</em> reload - but this is pretty good for now.</p>
]]></content:encoded></item><item><title>Building Pong with Zig and Raylib #6: Font Size, Collision Bugs, and Refactors</title><link>https://icle.es/2025/07/19/building-pong-with-zig-and-raylib-%236-font-size-collision-bugs-and-refactors/</link><pubDate>Sat, 19 Jul 2025 08:00:00 +0100</pubDate><guid>https://icle.es/2025/07/19/building-pong-with-zig-and-raylib-%236-font-size-collision-bugs-and-refactors/</guid><description>&lt;p>In this one, we’ll tidy up our Pong implementation by addressing three key
issues:&lt;/p>
&lt;ol>
&lt;li>The score font is too small&lt;/li>
&lt;li>The ball can get stuck inside a paddle&lt;/li>
&lt;li>Trigger score on edge (not the middle) of the ball going past.&lt;/li>
&lt;/ol>
&lt;p>We’ll also refactor score tracking to better match game logic structure.&lt;/p>
&lt;h2 id="-make-the-score-font-more-readable">🖋️ Make the Score Font More Readable&lt;/h2>
&lt;p>The score text was a little too small. If you&amp;rsquo;re using &lt;code>dvui&lt;/code>, here&amp;rsquo;s how to
adjust it:&lt;/p></description><content:encoded><![CDATA[<p>In this one, we’ll tidy up our Pong implementation by addressing three key
issues:</p>
<ol>
<li>The score font is too small</li>
<li>The ball can get stuck inside a paddle</li>
<li>Trigger score on edge (not the middle) of the ball going past.</li>
</ol>
<p>We’ll also refactor score tracking to better match game logic structure.</p>
<h2 id="-make-the-score-font-more-readable">🖋️ Make the Score Font More Readable</h2>
<p>The score text was a little too small. If you&rsquo;re using <code>dvui</code>, here&rsquo;s how to
adjust it:</p>
```zig
const font_size: f32 = 64;
var label_options: dvui.Options = .{
    .color_text = .white,
    .font_style = .title,
};
label_options.font = label_options.fontGet().resize(font_size);
dvui.label(@src(), "{d}", .{score}, label_options);
```
<p>Thanks to
<a href="https://ziggit.dev/t/building-pong-in-zig-with-raylib-part-1-paddles-and-a-ball/10768/12">code sample from milogreg</a></p>
<p>You may also need to adjust the width and height of the container if the larger
font gets clipped. In our case, font size of 64 felt about right.</p>
<h2 id="-fix-the-ball-getting-stuck-in-the-paddle">🧱 Fix the Ball Getting Stuck in the Paddle</h2>
<p>When the ball moves too far in one frame, it can land <em>inside</em> the paddle and
bounce back and forth infinitely.</p>
<p>This trap happens because we just multiply the x-velocity with <code>-1</code> to reverse
direction.</p>
<p>One way to fix this is to only trigger the bounce if the ball is moving <em>toward</em>
the paddle. For example:</p>
```zig
const crossing_x = switch (self.which) {
    .right => ball.vel.x > 0 and
        ball.pos.x + ball.r >= self.pos.x,
    .left => ball.vel.x < 0 and
        ball.pos.x - ball.r <= self.pos.x + size.x,
};
```
<p>Thanks again to
<a href="https://ziggit.dev/t/building-pong-in-zig-with-raylib-part-1-paddles-and-a-ball/10768/12">code sample from milogreg</a></p>
<p>This ensures we don’t apply the bounce logic when the ball is already inside the
paddle.</p>
<p>Another (potential) way to fix this would be to change the collision logic to
fix the x direction based on whether it&rsquo;s the left or right paddle.</p>
<h2 id="-trigger-a-score-when-the-balls-edge-crosses-the-screen">🧮 Trigger a Score When the Ball’s Edge Crosses the Screen</h2>
<p>Previously, we checked whether the ball’s <strong>center</strong> (<code>ball.x</code>) crossed the
screen edge. Particularly with the ball being bigger than the paddle, this
caused issues when the top/bottom of the paddle hit the ball.</p>
<p><a href="https://icle.es/games/pong/src/Game.zig">Game.zig</a></p>
```zig
if (self.ball.pos.x + self.ball.r > self.screen_width) {
    self.left_score += 1;
    self.ball.reset();
}

if (self.ball.pos.x < self.ball.r) {
    self.right_score += 1;
    self.ball.reset();
}
```
<p>This triggers the score as soon as the edge of the ball crosses the screen
bounds.</p>
<h2 id="-refactor-move-scores-out-of-the-paddle-struct">🔄 Refactor: Move Scores Out of the Paddle Struct</h2>
<p>Storing the score inside the <code>Paddle</code> struct is convenient but semantically odd</p>
<ul>
<li>paddles shouldn’t own scores. Instead, let&rsquo;s move them into your <code>Game</code>
struct:</li>
</ul>
<p>Then, pass the score explicitly to the rendering function.</p>
<p>You can find the updated code in
<a href="https://icle.es/games/pong/src/Game.zig">Game.zig</a>.</p>
<h2 id="-bonus-fix-make-score-display-use-paddle-play-area">✅ Bonus Fix: Make Score Display Use Paddle Play Area</h2>
<p>We now compute each paddle’s <em>play area</em> (a <code>dvui.Rect</code>) and use it to position
the score label, keeping layout logic more re-usable.</p>
<p>We add a <code>play_area</code> field or method to our <code>Paddle</code> struct that returns its
side of the screen. This makes the rendering logic clearer and more flexible.</p>
<h2 id="-whats-next">⏭️ What’s Next?</h2>
<p>Next episode: adding a <strong>pause menu</strong> to Pong, based on what I just built for my
other game, <em>triangle</em>. We’ll add basic Resume/Quit options and freeze game
state mid-play.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/youtube/shri-codes/pong/pong-6.md">Watch Video</a></li>
<li><a href="https://icle.es/games/pong/">Source Code (at this point)</a></li>
<li>Prev: <a href="https://icle.es/5-ui.md">Smarter Collisions &amp; Cleaner Code</a></li>
<li>Next: Pause Menu</li>
</ul>
]]></content:encoded></item><item><title>Escape to Menu</title><link>https://icle.es/2025/07/15/escape-to-menu/</link><pubDate>Tue, 15 Jul 2025 17:12:46 +0100</pubDate><guid>https://icle.es/2025/07/15/escape-to-menu/</guid><description>&lt;p>One of the key bits of functionality I want in a &lt;a href="https://icle.es/sprout.md">sprout build&lt;/a> is
the pause menu.&lt;/p>
&lt;p>After a bit of working it out, I ended up with this plan:&lt;/p>
&lt;p>
 &lt;img src="./sketch.png" alt="Pause menu sketch">

&lt;/p>
&lt;p>Until now, hitting Escape would just exit the game immediately. Not ideal.
triangle needed a proper menu: something that pauses the game, gives players a
chance to resume or quit intentionally, and maybe even shows a bit of useful
info.&lt;/p></description><content:encoded><![CDATA[<p>One of the key bits of functionality I want in a <a href="https://icle.es/sprout.md">sprout build</a> is
the pause menu.</p>
<p>After a bit of working it out, I ended up with this plan:</p>
<p>
  <img src="./sketch.png" alt="Pause menu sketch">

</p>
<p>Until now, hitting Escape would just exit the game immediately. Not ideal.
triangle needed a proper menu: something that pauses the game, gives players a
chance to resume or quit intentionally, and maybe even shows a bit of useful
info.</p>
<p>So that’s what I’ve been building. The menu now:</p>
<ul>
<li>Pops up when Escape is pressed (as long as no panels are open).</li>
<li>Pauses the game,</li>
<li>Shows a few buttons: Resume, Quit, Contact, and Config Path</li>
</ul>
<h2 id="ordering">Ordering</h2>
<p>At first, I was a bit confused as to why I was not able to see the menu. I was
under the (mistaken) impression that dvui would always draw last.</p>
<p>Once I switched things around to always draw dvui at the end, I could see the
menu.</p>
<h2 id="pauseresume">Pause/Resume</h2>
<p>Since we use the frame time to determine how much time has passed, pausing was
simply a case of setting that time elapsed to zero.</p>
<p>In fact, it skips the update call altogether. One thing I noticed while I was
doing that was that the camera movement code happened in <code>render</code>. While there
is a particular logic to that, it&rsquo;ll need to be moved to <code>update</code> (later), at
which point, render will also not require the elapsed time.</p>
<p>Resume was simply a case of letting &ldquo;time flow&rdquo; again.</p>
<h2 id="logging">Logging</h2>
<p>I thought logging might be useful — but writing to file or supporting runtime
filtering requires a custom log handler. While it&rsquo;s probably doable, I didn’t
want to dive into it just yet. For now, I’ve left it out entirely. Maybe a
separate debug build is the better option anyway.</p>
<h2 id="contact">Contact</h2>
<p>I set up a form at <a href="https://tally.so">tally.so</a> and ChatGPT helped me write a
tiny bit of code that opens a browser to that form.</p>
<p>I&rsquo;ve not tested this on Mac/Windows and would appreciate any feedback (when a
<a href="https://icle.es/sprout.md">sprout</a> build is available.)</p>
<h2 id="quit">Quit</h2>
<p>We now have a quit button that will quit the game. The game will also currently
quit if you hit <code>q</code>. I&rsquo;ll unmap this later.</p>
<h2 id="config-path">Config path</h2>
<p>The config location is also displayed. The path can be selected and copied - so
that the player can navigate there easier to be able to override controls etc.</p>
<h2 id="final">Final</h2>
<p>
  <img src="./final.png" alt="Final menu">

</p>
<p>While perhaps not as pretty as the sketch (credit is more to tldraw than me), it
is functional.</p>
<p>Next steps are to get a dialog working with some basic information, including a
changelog and possibly a roadmap.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/youtube/triangle/menu.md">YouTube Video</a></li>
<li>Let&rsquo;s code devlogs
<ul>
<li><a href="https://icle.es/youtube/shri-codes/triangle/menus-start.md">#1.1 - Getting Started</a></li>
<li><a href="https://icle.es/youtube/shri-codes/triangle/menus-theming.md">#1.2 - Theming &amp; More Buttons </a></li>
<li><a href="https://icle.es/youtube/shri-codes/triangle/menus-finalising.md">#1.3 - Wiring &amp; Finalising</a></li>
</ul>
</li>
<li>Prev: <a href="https://icle.es/2025-06-08-config.md">Under the Hood of Triangle</a></li>
</ul>
]]></content:encoded></item><item><title>Setup Whisper</title><link>https://icle.es/2025/07/15/setup-whisper/</link><pubDate>Tue, 15 Jul 2025 08:24:28 +0100</pubDate><guid>https://icle.es/2025/07/15/setup-whisper/</guid><description>&lt;p>I wanted to generate chapter markers from a devlog audio recording using
OpenAI&amp;rsquo;s Whisper, and figured I&amp;rsquo;d run it locally. Whisper is Python-based, and
I&amp;rsquo;m on Arch. What could go wrong?&lt;/p>
&lt;p>Turns out… not much, but it still took a few hops.&lt;/p>
&lt;h2 id="choosing-the-right-setup">Choosing the Right Setup&lt;/h2>
&lt;p>I already had Python installed, but rather than littering system Python or
managing a bunch of ad hoc virtualenvs, I decided to do it properly — with
Poetry.&lt;/p></description><content:encoded><![CDATA[<p>I wanted to generate chapter markers from a devlog audio recording using
OpenAI&rsquo;s Whisper, and figured I&rsquo;d run it locally. Whisper is Python-based, and
I&rsquo;m on Arch. What could go wrong?</p>
<p>Turns out… not much, but it still took a few hops.</p>
<h2 id="choosing-the-right-setup">Choosing the Right Setup</h2>
<p>I already had Python installed, but rather than littering system Python or
managing a bunch of ad hoc virtualenvs, I decided to do it properly — with
Poetry.</p>
```bash
sudo pacman -S poetry
poetry new whisper-transcriber
cd whisper-transcriber
```
<p>So far so good.</p>
<h2 id="pytorch--cuda-the-pypy-pitfall">PyTorch + CUDA: the PyPy Pitfall</h2>
<p>My first attempt to install <code>torch</code>, <code>torchvision</code>, and <code>torchaudio</code> failed in a
confusing way — no versions found at all. The clue was in the command: I&rsquo;d
accidentally run it with <code>pip-pypy3</code>. PyTorch doesn&rsquo;t build wheels for PyPy.
CPython only.</p>
<h2 id="sorting-out-python-versions">Sorting Out Python Versions</h2>
<p>My system Python was 3.13. PyTorch had just released 3.13 wheels for <code>torch</code>,
but not yet for <code>torchaudio</code> — version mismatch. I used <code>pyenv</code> to install 3.12
instead:</p>
```bash
pyenv install 3.12.3
```
<p>Updated <code>pyproject.toml</code>:</p>
```toml
python = ">=3.12,<3.14"
```
<p>And re-pointed Poetry:</p>
```bash
poetry env use $(pyenv prefix 3.12.3)/bin/python
```
<p>Poetry ignored me the first time because 3.13 was still hardcoded. After
recreating the environment and verifying the version, I was ready.</p>
<h2 id="pep-668-the-externally-managed-false-alarm">PEP 668: the &ldquo;Externally Managed&rdquo; False Alarm</h2>
<p>Even inside the Poetry shell, Arch&rsquo;s patched Python threw a
<code>--break-system-packages</code> error. This check is meant to protect system Python —
but it was firing inside a fully isolated Poetry environment. Safe to ignore. I
added the flag:</p>
```bash
poetry run pip install torch torchvision torchaudio \
  --index-url https://download.pytorch.org/whl/cu121 \
  --break-system-packages
```
<p>Worked perfectly.</p>
<h2 id="the-result">The Result</h2>
```bash
poetry run whisper output000.mp3 --model base --output_format json
```
<p>Transcribed 2.5 hours of audio, timestamped segments ready for chapter
generation. All local, GPU-accelerated, isolated from system Python, and
repeatable.</p>
<hr>
<h2 id="in-summary">In Summary</h2>
<p>If you&rsquo;re on Arch and want Whisper with CUDA:</p>
<ol>
<li>Use <code>poetry</code> + <code>pyenv</code></li>
<li>Set Python to 3.12 (not 3.13)</li>
<li>Install torch with <code>--break-system-packages</code> and the <code>cu121</code> index</li>
<li>Whisper just works</li>
</ol>
<p>A bit of fiddling up front, but now it&rsquo;s a solid local tool — one less cloud
dependency to think about.</p>
]]></content:encoded></item><item><title>Building Pong with Zig and Raylib #5: Show Score with dvui</title><link>https://icle.es/2025/07/15/building-pong-with-zig-and-raylib-%235-show-score-with-dvui/</link><pubDate>Tue, 15 Jul 2025 08:00:00 +0100</pubDate><guid>https://icle.es/2025/07/15/building-pong-with-zig-and-raylib-%235-show-score-with-dvui/</guid><description>&lt;p>In this episode, I finally add a score display to Pong using DVUI, a native Zig
UI framework. The scoring logic was already in place - now it&amp;rsquo;s time to show it
on screen.&lt;/p>
&lt;h2 id="extract-out-game-struct">Extract out &lt;code>Game&lt;/code> struct&lt;/h2>
&lt;p>One of the structural changes I wanted to make to tidy up the code was to pull
out the game logic into its own struct. This change helps to declutter the
&lt;code>main.zig&lt;/code> file, leading the way to add in the dvui scaffolding.&lt;/p></description><content:encoded><![CDATA[<p>In this episode, I finally add a score display to Pong using DVUI, a native Zig
UI framework. The scoring logic was already in place - now it&rsquo;s time to show it
on screen.</p>
<h2 id="extract-out-game-struct">Extract out <code>Game</code> struct</h2>
<p>One of the structural changes I wanted to make to tidy up the code was to pull
out the game logic into its own struct. This change helps to declutter the
<code>main.zig</code> file, leading the way to add in the dvui scaffolding.</p>
<p>This refactor pulls the update and render logic into a <code>Game</code> struct, which
helps declutter <code>main.zig</code> and sets up a better foundation for UI work.</p>
<p><a href="https://icle.es/games/pong/src/Game.zig">Game.zig</a></p>
```zig
pub fn update(self: *Game, dt: f32) void {
    self.ball.checkEdgeCollisions(self.screen_height);
    self.ball.update(dt);
    self.ball.checkPaddleCollision(&self.left_paddle);
    self.ball.checkPaddleCollision(&self.right_paddle);
    if (self.ball.pos.x > self.screen_width) {
        self.left_paddle.score += 1;
        std.debug.print("scores: l: {d}, r: {d}\n", .{ self.left_paddle.score, self.right_paddle.score });
        self.ball.reset();
    }

    if (self.ball.pos.x < 0) {
        self.right_paddle.score += 1;
        std.debug.print("scores: l: {d}, r: {d}\n", .{ self.left_paddle.score, self.right_paddle.score });
        self.ball.reset();
    }

    if (rl.isKeyDown(.w)) {
        self.left_paddle.moveUp(dt);
    }

    if (rl.isKeyDown(.s)) {
        self.left_paddle.moveDown(dt);
    }

    if (rl.isKeyDown(.e)) {
        self.right_paddle.moveUp(dt);
    }

    if (rl.isKeyDown(.d)) {
        self.right_paddle.moveDown(dt);
    }
}

pub fn render(self: *const Game) void {
    self.left_paddle.render();
    self.right_paddle.render();
    self.ball.render();

    showScore(self.screen_width * 0.25, self.left_paddle.score);
    showScore(self.screen_width * 0.75, self.right_paddle.score);
}
```
<h2 id="add-dvui-dependency">Add dvui dependency</h2>
<p>Let&rsquo;s fetch the dependency, adding it to <code>build.zig.zon</code>:</p>
<p><code>zig fetch --save git+https://github.com/david-vanderson/dvui.git</code></p>
<p>Then, in <code>build.zig</code>, we also need to declare it:</p>
```zig
const dvui_dep = b.dependency("dvui", .{
    .target = target,
    .optimize = optimize,
});

const dvui = dvui_dep.module("dvui_raylib");
```
<p>and then add it as a dependency:</p>
```zig
exe.root_module.addImport("dvui", dvui);
```
<h2 id="add-dvui-to-game-loop">Add <code>dvui</code> to game loop</h2>
<p>We also need to initialise dvui in the main loop.</p>
<p><a href="https://icle.es/games/pong/src/main.zig">main.zig</a></p>
<h3 id="import">Import</h3>
```zig
const dvui = @import("dvui");

const RaylibBackend = dvui.backend;
comptime {
    std.debug.assert(@hasDecl(RaylibBackend, "RaylibBackend"));
}
const ray = RaylibBackend.c;
```
<h3 id="initialise">Initialise</h3>
```zig
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const allocator = gpa.allocator();
defer _ = gpa.deinit();

//--------------------------------------------------------------------------
// init Raylib backend
// init() means the app owns the window (and must call CloseWindow itself)
var backend = RaylibBackend.init(allocator);
defer backend.deinit();
backend.log_events = true;

// init dvui Window (maps onto a single OS window)
// OS window is managed by raylib, not dvui
var win = try dvui.Window.init(@src(), allocator, backend.backend(), .{});
defer win.deinit();
```
<h3 id="pre-render">Pre-render</h3>
```zig
// marks the beginning of a frame for dvui, can call dvui functions after this
try win.begin(std.time.nanoTimestamp());

// send all Raylib events to dvui for processing
_ = try backend.addAllEvents(&win);
```
<h3 id="post-render">Post-render</h3>
```zig
_ = try win.end(.{});

// cursor management
if (win.cursorRequestedFloating()) |cursor| {
    // cursor is over floating window, dvui sets it
    backend.setCursor(cursor);
} else {
    // cursor should be handled by application
    backend.setCursor(.arrow);
}
```
<h2 id="show-score">Show Score</h2>
<p>We can now show the score using <code>dvui.label</code> with positioning hardcoded to about
25% and 75% across the screen. There may be a better way to position it but it
works well enough for now.</p>
<p>I had to generate an id for the label so that they were unique. I generated it
the x-position using <code>@intFromFloat()</code>.</p>
<p><a href="https://icle.es/games/pong/src/Game.zig">Game.zig</a></p>
```zig
pub fn render(self: *const Game) void {
    self.left_paddle.render();
    self.right_paddle.render();
    self.ball.render();

    showScore(self.screen_width * 0.25, self.left_paddle.score);
    showScore(self.screen_width * 0.75, self.right_paddle.score);
}

fn showScore(xpos: f32, score: u8) void {
    const id: usize = @intFromFloat(xpos);
    var right = dvui.box(@src(), .horizontal, .{ .rect = .{ .x = xpos, .y = 50, .w = 50, .h = 50 }, .id_extra = id });
    defer right.deinit();

    dvui.label(@src(), "{d}", .{score}, .{ .color_text = .white, .font_style = .title });
}
```
<h2 id="closing">Closing</h2>
<p>As always, things took a bit longer than expected, but by the end:</p>
<ul>
<li>The game&rsquo;s structure is cleaner</li>
<li>DVUI is wired up properly</li>
<li>Scores now show up on screen</li>
</ul>
<p>Shoutout to <a href="https://ziggit.dev/u/milogreg/summary">milo_greg</a> on
<a href="https://ziggit.dev/">ziggit.dev</a> for loads of really valuable feedback and
tips. That kind of thoughtful review really helps.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/youtube/shri-codes/pong/pong-5.md">Watch Video</a></li>
<li><a href="https://icle.es/games/pong/">Source Code (at this point)</a></li>
<li>Prev: <a href="https://icle.es/4-refactor.md">Smarter Collisions &amp; Cleaner Code</a></li>
<li>Next: <a href="https://icle.es/6-refactor.md">Font Size, Collision Bugs, and Refactors</a></li>
</ul>
]]></content:encoded></item><item><title>Building Pong with Zig and Raylib - Part 4: Smarter Collisions, Cleaner Code</title><link>https://icle.es/2025/07/10/building-pong-with-zig-and-raylib-part-4-smarter-collisions-cleaner-code/</link><pubDate>Thu, 10 Jul 2025 15:33:34 +0100</pubDate><guid>https://icle.es/2025/07/10/building-pong-with-zig-and-raylib-part-4-smarter-collisions-cleaner-code/</guid><description>&lt;p>Change of Plans&lt;/p>
&lt;p>I was going to dive into menus and UI, but after sharing the early version of
Pong on &lt;a href="https://ziggit.dev/">ziggit.dev&lt;/a>, I got a bunch of helpful feedback.
The feedback was the kind that makes you stop and think, &lt;em>ah, right&amp;hellip; I should
probably fix that before carrying on.&lt;/em>&lt;/p>
&lt;p>So that&amp;rsquo;s what this episode became: a collection of fixes, tweaks, and small
refactors that clean up the code and align things more closely with how things
&lt;em>should&lt;/em> be done in Zig (or at least, better than I had them before).&lt;/p></description><content:encoded><![CDATA[<p>Change of Plans</p>
<p>I was going to dive into menus and UI, but after sharing the early version of
Pong on <a href="https://ziggit.dev/">ziggit.dev</a>, I got a bunch of helpful feedback.
The feedback was the kind that makes you stop and think, <em>ah, right&hellip; I should
probably fix that before carrying on.</em></p>
<p>So that&rsquo;s what this episode became: a collection of fixes, tweaks, and small
refactors that clean up the code and align things more closely with how things
<em>should</em> be done in Zig (or at least, better than I had them before).</p>
<h2 id="-naming-matters">🧼 Naming Matters</h2>
<p>I’d originally named my files <code>paddle.zig</code> and <code>ball.zig</code> - lowercase,
snake-case. I had tried to find the guidelines around this, but turns out I only
got half the story. If a file implicitly defines a struct via top-level fields,
it should be named in PascalCase. So, <code>Paddle.zig</code>, not <code>paddle.zig</code>.</p>
<p>It’s a small thing, but one that helps be a bit more idiomatic - and avoids
confusion when others are reading it.</p>
<p>(now to make the same change across many more files in
<a href="https://icle.es/endeavours/triangle.md">triangle</a>)</p>
<h2 id="-default-field-initializers-and-when-not-to-use-them">🛠️ Default Field Initializers (and When <em>Not</em> to Use Them)</h2>
<p>Another thing I learned: structs that aren’t used as config objects shouldn’t
use default field values. Instead, they should have an <code>init</code> constant that
represents their starting state.</p>
<p>I’d missed this distinction, and both of my types were using default values
incorrectly. So I cleaned that up and added the values into the <code>init</code> method.
It’s a subtle change, but it keeps config objects and plain data objects
conceptually separate - and makes it clearer which parts of a struct are
supposed to be overridden.</p>
<h2 id="-rls-please">✨ RLS, Please</h2>
<p>One of the comments suggested I lean more into Zig’s Result Location Syntax -
where you define the type on the left-hand side and let Zig figure out the rest.</p>
<p>I’d been using a mix of styles. Nothing broke, but consistency helps. So I swept
through the code and updated those as well.</p>
```zig
//main.zig
var left_paddle: Paddle = .init(Paddle.size.x * 0.5, .left, screen_height);
var right_paddle: Paddle = .init(screen_width - Paddle.size.x * 1.5, .right, screen_height);
var ball: Ball = .init(.{ .x = screen_width * 0.5, .y = screen_height * 0.5 });
```
<h2 id="-fixing-paddle-collisions-on-the-y-axis">🎯 Fixing Paddle Collisions on the Y Axis</h2>
<p>Now for something more visible: my collision detection logic only checked the
center point of the ball along the y-axis. That meant if the ball clipped the
paddle at the edge, it was sneaking through.</p>
<p>The fix was simple: add/subtract the ball’s radius in the y-axis check. Much
better.</p>
```zig
const colliding = ball.pos.y + ball.r >= self.pos.y and ball.pos.y - ball.r <= self.pos.y + size.y;
```
<p>You can actually see the difference in-game - that satisfying little <em>thock</em> now
triggers when it should, even on corner hits.</p>
<p>(now I just need add some sounds - I&rsquo;d forgotten about that)</p>
<h2 id="-iscolliding-should-only-collide">🧽 <code>isColliding</code> Should Only Collide</h2>
<p>Previously, <code>isColliding</code> also handled coloring the paddle red when it detected
a hit - a debug leftover that had no place in the final function.</p>
<p>I stripped that out and left <code>isColliding</code> to do just one thing: return whether
there was a collision. If I want debug visuals again later, I’ll wrap this in
another function.</p>
```zig
// Paddle.zig
pub fn isColliding(self: *const Paddle, ball: *const Ball) bool {
    // which edge do we need to check
    const crossing_x: bool = switch (self.which) {
        .right => ball.pos.x + ball.r >= self.pos.x,
        .left => ball.pos.x - ball.r <= self.pos.x + size.x,
    };

    if (!crossing_x) {
        return false;
    }

    const colliding = ball.pos.y + ball.r >= self.pos.y and ball.pos.y - ball.r <= self.pos.y + size.y;

    return colliding;
}
```
<h2 id="-movement-logic-encapsulation">🔀 Movement Logic Encapsulation</h2>
<p>Paddle movement was scattered, and the logic for moving up/down lived inside
<code>main.zig</code>. I pulled that out into proper <code>moveUp</code> and <code>moveDown</code> methods on
<code>Paddle</code>.</p>
<p>It reads cleaner now:</p>
```zig
//Paddle.zig
pub fn moveUp(self: *Paddle, dt: f32) void {
    self.move(-100, dt);
}

pub fn moveDown(self: *Paddle, dt: f32) void {
    self.move(100, dt);
}
```
```zig
// main.zig
if (rl.isKeyDown(.w)) {
    left_paddle.moveUp(dt);
}

if (rl.isKeyDown(.s)) {
    left_paddle.moveDown(dt);
}

if (rl.isKeyDown(.e)) {
    right_paddle.moveUp(dt);
}

if (rl.isKeyDown(.d)) {
    right_paddle.moveDown(dt);
}
```
<p>…and it keeps the input logic in <code>main</code>, but the movement logic in the paddle -
where it belongs.</p>
<h2 id="-resolution-independence">📐 Resolution Independence</h2>
<p>Some values were hardcoded (like setting <code>y = 200</code> for paddle start position),
while others used <code>getScreenHeight()</code> and <code>getScreenWidth()</code>. I refactored to
make everything use actual screen dimensions, converting <code>i32</code> screen height
values to <code>f32</code> where needed.</p>
<p>It was a bit fiddly, but worth it. Now Pong should behave properly regardless of
window size.</p>
<h2 id="closing">Closing</h2>
<p>So, yeah - not the flashiest episode, but a satisfying one. Lots of small things
that feel better now that they’re fixed. And it&rsquo;s a reminder that sharing early
(even rough work) is usually a good idea. You never know what you&rsquo;ll learn.</p>
<p>Next time, we <em>will</em> get into UI. I’m planning to bring in
<a href="https://github.com/david-vanderson/dvui">DVUI</a> and show a basic score display,
a pause menu, and maybe some options for reset and quit.</p>
<p>Until then, thanks for reading (and watching) - see you in the next one.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/youtube/shri-codes/pong/pong-4.md">Watch Video</a></li>
<li><a href="https://icle.es/games/pong/">Source Code (at this point)</a></li>
<li>Prev: <a href="https://icle.es/3-scoring.md">Ball Movement &amp; Paddle Collisions</a></li>
<li>Next: <a href="https://icle.es/5-ui.md">Integrate DVUI &amp; Show Score</a></li>
</ul>
]]></content:encoded></item><item><title>Automatically link to repo at current commit</title><link>https://icle.es/2025/07/08/automatically-link-to-repo-at-current-commit/</link><pubDate>Tue, 08 Jul 2025 15:39:14 +0100</pubDate><guid>https://icle.es/2025/07/08/automatically-link-to-repo-at-current-commit/</guid><description>&lt;p>I like writing blog posts, particularly about code, and I like to link to code
on my repo from my blog post. I do this a lot. Until now, I&amp;rsquo;ve just been copying
and pasting the full link to github. However, I ran into a problem today.&lt;/p>
&lt;p>I moved a file that was referenced in a blog post. I then had to go to that blog
post and update the links - this is fine if I remember the linked blog posts -
but that&amp;rsquo;s not scalable.&lt;/p></description><content:encoded><![CDATA[<p>I like writing blog posts, particularly about code, and I like to link to code
on my repo from my blog post. I do this a lot. Until now, I&rsquo;ve just been copying
and pasting the full link to github. However, I ran into a problem today.</p>
<p>I moved a file that was referenced in a blog post. I then had to go to that blog
post and update the links - this is fine if I remember the linked blog posts -
but that&rsquo;s not scalable.</p>
<p>Also:</p>
<ul>
<li>Finding the link on GitHub, then copying and pasting is annoying</li>
<li>It worries me a little that the version they are linked to could be vastly
different from what I mention on the post.</li>
</ul>
<p>I was already trying to create permalinks using tags, but that is laborious and
error prone.</p>
<p>What if I could get Hugo to:</p>
<ul>
<li>Automatically link to GitHub if the relative link is not within <code>blog/content</code></li>
<li>What if I could get it to link it to the file at the last commit of the post.</li>
</ul>
<p>The last one is something to bear in mind. If I update a blog post, I&rsquo;ll have to
ensure that the links are still relevant.</p>
<p>Alternatively, let&rsquo;s allow an override at the page level where you can provide a
specific commit to link to:</p>
<h2 id="step-1-enable-git-info">Step 1: Enable git info</h2>
<p>To be able to get the commit of the post, we need to enable
<a href="https://gohugo.io/methods/page/gitinfo/">git info</a></p>
<p><a href="https://icle.es/hugo.toml">hugo.toml</a></p>
```toml
enableGitInfo = true
```
<h2 id="step-2-update-rendering-of-link">Step 2: Update rendering of link</h2>
<p><a href="https://icle.es/layouts/_default/_markup/render-link.html">layout/_default/_markup/render-link.html</a></p>
```gotmpl
{{- $linkPath := .Destination -}}                           {{/* e.g. "../scripts/tool.sh" */}}
{{- $currentPath := .Page.File.Path -}}                     {{/* e.g. "posts/foo.md" */}}
{{- $currentDir := path.Dir $currentPath -}}                {{/* e.g. "posts" */}}

{{- $combined := path.Join $currentDir $linkPath -}}        {{/* e.g. "posts/../scripts/tool.sh" */}}
{{- $resolved := path.Clean $combined -}}                   {{/* e.g. "scripts/tool.sh" */}}

{{- $fullRepoPath := path.Join "blog/content" $resolved -}} {{/* e.g. "blog/content/scripts/tool.sh" */}}

{{- $isInContent := strings.HasPrefix $fullRepoPath "blog/content/" -}}

{{- if $isInContent -}}
    <span class="unpublished">{{ $text }}</span>
{{- else -}}
    {{- $commit := or .Page.Params.link_commit .Page.GitInfo.Hash -}}
    <a href="https://github.com/drone-ah/wordsonsand/blob/{{ $commit }}/{{ $fullRepoPath }}" {{ with .Title }}title="{{ . }}"{{ end }}>{{ $text }}</a>
```
<h2 id="bonus-allow-per-post-commit-override">Bonus: Allow per-post commit override</h2>
<p>In the above code:</p>
```gotmpl
{{- $commit := or .Page.Params.link_commit .Page.GitInfo.Hash -}}
```
<p>The commit id is picked up from the page parameter <code>link_commit</code>, or if that
doesn&rsquo;t exist, from the last commit of the page.</p>
<p>You can therefore, set the commit to use for a post with:</p>
```yaml
link_commit: <custom-commit-to-link-to>
```
<h2 id="conclusion">Conclusion</h2>
<p>Necessity might be the mother of invention, but sometimes it takes a fine-tuned
sense of frustration to detect minor needs.</p>
]]></content:encoded></item><item><title>Projector: Keep YouTube Descriptions synced</title><link>https://icle.es/2025/07/07/projector-keep-youtube-descriptions-synced/</link><pubDate>Mon, 07 Jul 2025 20:08:21 +0100</pubDate><guid>https://icle.es/2025/07/07/projector-keep-youtube-descriptions-synced/</guid><description>&lt;p>In my &lt;a href="https://icle.es/projector-Hugo.md">previous post&lt;/a>, I used &lt;a href="https://gohugo.io/">hugo&lt;/a> to
generate correctly linked, always up to date descriptions for my YouTube Videos.&lt;/p>
&lt;p>But if I&amp;rsquo;m generating the descriptions automatically&amp;hellip; I&amp;rsquo;m hardly going to be
excited about copying and pasting them into YouTube - right? right!&lt;/p>
&lt;p>Automating this process brings up a few design choices.&lt;/p>
&lt;h2 id="planning">Planning&lt;/h2>
&lt;h3 id="which-language">Which language&lt;/h3>
&lt;p>There were a few contenders, and here&amp;rsquo;s how I thought them through:&lt;/p>
&lt;h4 id="zig">Zig&lt;/h4>
&lt;p>I’m currently learning &lt;a href="https://ziglang.org/">Zig&lt;/a>, and I love using it for my
game development. But it doesn’t yet have mature libraries for working with the
YouTube Data API - and I don’t feel like writing one. So, sadly, Zig’s out for
this one.&lt;/p></description><content:encoded><![CDATA[<p>In my <a href="https://icle.es/projector-Hugo.md">previous post</a>, I used <a href="https://gohugo.io/">hugo</a> to
generate correctly linked, always up to date descriptions for my YouTube Videos.</p>
<p>But if I&rsquo;m generating the descriptions automatically&hellip; I&rsquo;m hardly going to be
excited about copying and pasting them into YouTube - right? right!</p>
<p>Automating this process brings up a few design choices.</p>
<h2 id="planning">Planning</h2>
<h3 id="which-language">Which language</h3>
<p>There were a few contenders, and here&rsquo;s how I thought them through:</p>
<h4 id="zig">Zig</h4>
<p>I’m currently learning <a href="https://ziglang.org/">Zig</a>, and I love using it for my
game development. But it doesn’t yet have mature libraries for working with the
YouTube Data API - and I don’t feel like writing one. So, sadly, Zig’s out for
this one.</p>
<h4 id="python">Python</h4>
<p>I used Python for <a href="https://icle.es/despatches.md">despatches</a> and it was the right fit there -
good libraries for BlueSky and Reddit.</p>
<p>However, I did not enjoy the experience:</p>
<ul>
<li>
<p><code>bazel</code> was a constant struggle</p>
</li>
<li>
<p><code>poetry</code> is nice… but still a bit of a nightmare. It just makes the pain more
structured</p>
</li>
<li>
<p>Worst of all: blusky failed after reddit succeeded caused a <em>partial success</em>,
which broke the Git commit and silently caused a post to be repeated
(embarrassing!)</p>
<p>That kind of problem <em>can</em> happen in Go (nil pointer), though it wouldn’t in
Zig. But at least with Go, most handleable errors <em>stay</em> errors — they don’t
crash the whole tool.</p>
</li>
</ul>
<h4 id="java">java</h4>
<p>Sure, I could do this in Java - but I really don’t want to mess with the JVM.
And more importantly, I’m doing this for fun. Java doesn’t feel like that
anymore.</p>
<h4 id="golang">golang</h4>
<p>Not quite my favourite any more, but still a close second. It&rsquo;s <em>fast</em>, has
YouTube libraries and it somehow seems fitting that Hugo is also a go baby.</p>
<p>Even though I’m not wiring the two directly, the ecosystem fit is nice.</p>
<h2 id="overall-plan">Overall Plan</h2>
<ul>
<li>
<p>Let <code>Hugo</code> render the YouTube description as plain text</p>
</li>
<li>
<p>Traverse the <code>youtube/*.md</code> files in the source directory</p>
<ul>
<li>Skip videos that are too old to update (maybe older than 30 days?)</li>
<li>Hash the rendered output (title, description, tags, etc.)</li>
<li>Compare that hash with the one stored in the frontmatter</li>
<li>If it doesn’t match,
<ul>
<li>Update the metadata on YouTube</li>
<li>Update the hash</li>
</ul>
</li>
<li>commit and push any updates (should be only hash changes)</li>
</ul>
</li>
</ul>
<h2 id="validation">Validation</h2>
<p>One thing worth being careful about is whether the metadata is valid. We do not
want the sync to fail during its scheduled run - when it won&rsquo;t have many choices
on how to resolve it.</p>
<p>In a bid to mitigate this, we&rsquo;ll add a command to validate the source and
rendered files.</p>
<p>The validation would expect the rendered files to be generated as well, which
seems reasonable since Hugo is probably running as <code>hugo serve</code> while the
content files are being updated.</p>
```go
func validate(sourcePath string, renderedPath string) error {
	targetSourceDir, err := getTargetDir(sourcePath)
	if err != nil {
		return err
	}

	targetRenderedDir, err := getTargetDir(renderedPath)
	if err != nil {
		return nil
	}

	videos, err := findRecentVideos(targetSourceDir)
	for _, video := range videos {
		_, err := video.getDescription(targetRenderedDir)
		if err != nil {
			slog.Warn("unable to find rendered file", "file", video.renderedPath)
		}
	}
	return nil
}
```
<p>The validate function will retrieve the relevant files and check that there is a
corresponding rendered description.</p>
<p>If it errors in that process, we know that it would error out in the sync.</p>
<p>We can&rsquo;t catch errors around the API though at this stage, and that&rsquo;s
unavoidable.</p>
<h2 id="sync">Sync</h2>
<h3 id="hashing-the-description">Hashing the Description</h3>
<p>This part was surprisingly easy:</p>
```go
bdesc, err := video.getDescription(targetRenderedDir)
if err != nil {
    slog.Warn("unable to find rendered file", "file", video.renderedPath)
}

// We want to hash the contents of description
// Check with the hash in the metadata to see if it matches
hash := md5.Sum(bdesc)
strHash := hex.EncodeToString(hash[:])
```
<p>The challenge was trying to write the updated yaml frontmatter back. I was using
the <code>adrg/frontmatter</code> library to read the frontmatter, but it does not support
writing it back.</p>
<h3 id="detour-write-a-small-frontmatter-library">Detour: Write a small frontmatter Library</h3>
<p>I took a little detour to build
<a href="https://icle.es/golang/inscribe.md">inscribe, a little frontmatter library that supports reading and writing back in yaml</a>.</p>
<h2 id="auth">Auth</h2>
<p>We need the YouTube Client to have an OAuth Token, which we can retrieve by:</p>
<ul>
<li><a href="https://console.cloud.google.com/auth/clients">Create a new OAuth Client</a> -
Type of desktop is probably the easiest</li>
<li>add your user account to
<a href="https://console.cloud.google.com/auth/audience">test users</a>:</li>
<li>go to
<a href="https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT_ID&amp;redirect_uri=urn:ietf:wg:oauth:2.0:oob&amp;response_type=code&amp;scope=https://www.googleapis.com/auth/youtube">https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT_ID&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=https://www.googleapis.com/auth/youtube</a>
<ul>
<li>Remember to substitute your actual client_id</li>
<li>Add any other scopes you might want</li>
<li>Go through the flow steps - it&rsquo;ll warn you that the app is unreleased, which
is expected</li>
</ul>
</li>
<li>Take the code that it provides</li>
<li>Call the following curl command</li>
</ul>
```bash
curl -X POST https://oauth2.googleapis.com/token \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d code=PASTE_THE_CODE_HERE \
  -d grant_type=authorization_code \
  -d redirect_uri=urn:ietf:wg:oauth:2.0:oob
```
<p>You should finally get something like:</p>
```json
{
  "access_token": "ya29...",
  "expires_in": 3599,
  "refresh_token": "1//0g...",
  "scope": "https://www.googleapis.com/auth/youtube",
  "token_type": "Bearer"
}
```
<p>The <code>refresh_token</code> is what you want to save / use as the <code>access_token</code> will
expire (after an hour in this example).</p>
<p>The authentication was a bit more involved with a refresh token, but the
<code>oauth2</code> library helps us out:</p>
```go
func NewYouTube(ClientId string, ClientSecret string, RefreshToken string) (YouTube, error) {

	conf := &oauth2.Config{
		ClientID:     ClientId,
		ClientSecret: ClientSecret,
		Endpoint:     google.Endpoint,
		Scopes:       []string{"https://www.googleapis.com/auth/youtube"},
	}

	// Construct a token from just the refresh token
	token := &oauth2.Token{RefreshToken: RefreshToken}

	ctx := context.Background()

	// Create an authenticated client
	httpClient := conf.Client(ctx, token)

	ytService, err := youtube.NewService(ctx, option.WithHTTPClient(httpClient))
	if err != nil {
		return YouTube{}, err
	}

	return YouTube{
		service: ytService,
	}, nil

}
```
<h2 id="updating-the-description">Updating the description</h2>
<p>Setting the description is a little more complicated because you can&rsquo;t set just
the description.</p>
<p>Everything defined in the <code>VideoSnippet</code> gets updated.</p>
<p>To support this, what we need to do is get the current snippet for the video,
then update it:</p>
```go
vListCall := ytService.Videos.List([]string{"snippet"})
vListCall = vListCall.Id(videoId)
res, err := vListCall.Do()
if err != nil {
    return err
}

if len(res.Items) != 1 {
    return fmt.Errorf("wrong number of videos returned: %d", len(res.Items))
}

ytVideo := res.Items[0]
ytVideo.Snippet.Description = desc

vUpdateCall := ytService.Videos.Update([]string{"snippet"}, ytVideo)
_, err = vUpdateCall.Do()
```
<h2 id="github-action">GitHub Action</h2>
<p>The GitHub Action is fairly straightforward, mostly a copy of the Hugo one,
then:</p>
<ul>
<li>Add Bazel</li>
<li>Run projector sync</li>
<li>Commit if changed</li>
</ul>
```yaml
- uses: bazel-contrib/setup-bazel@0.15.0
  with:
    # Avoid downloading Bazel every time.
    bazelisk-cache: true
    # Store build cache per workflow.
    disk-cache: ${{ github.workflow }}
    # Share repository cache between workflows.
    repository-cache: true
- name: Run projector sync
  env:
    GOOGLE_CLIENT_ID: ${{ secrets.PROJECTOR_GOOGLE_CLIENT_ID }}
    GOOGLE_CLIENT_SECRET: ${{ secrets.PROJECTOR_GOOGLE_CLIENT_SECRET }}
    GOOGLE_REFRESH_TOKEN: ${{ secrets.PROJECTOR_GOOGLE_REFRESH_TOKEN }}
  continue-on-error: true
  run:
    bazel run //tools/projector:projector -- sync -source blog/content/youtube
    -rendered blog/public/youtube
- name: Commit and push if changed
  run: |
    git config user.name "drone-ah bot"
    git config user.email "github.actions@drone-ah.com"

    if ! git diff --quiet; then
      git add -u
      git commit -m "auto: log youtube updates"
      git push
    else
      echo "No changes to commit"
    fi
```
<p>We also needed to upgrade one permission - <code>contents</code></p>
```yaml
permissions:
  contents: write
```
<h2 id="conclusion">Conclusion</h2>
<p>The Google/YouTube documentation was the hardest part here in that it was pretty
obtuse and hard to understand.</p>
<p>Writing a little frontmatter library was unexpected, but while it took a little
time, was straightforward.</p>
<p>Once I got a handle on that, the rest of it was pretty straightforward, partly
because I was reusing parts from before.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/projector-Hugo.md">Part 1: Outputting YouTube Descriptions from Hugo</a></li>
<li><a href="https://icle.es/golang/inscribe.md">inscribe: simple frontmatter yaml library</a></li>
</ul>
]]></content:encoded></item><item><title>Building Pong in Zig with Raylib – Part 3: Edge Collisions, Scoring &amp; Player Input</title><link>https://icle.es/2025/07/07/building-pong-in-zig-with-raylib-part-3-edge-collisions-scoring-player-input/</link><pubDate>Mon, 07 Jul 2025 12:01:00 +0100</pubDate><guid>https://icle.es/2025/07/07/building-pong-in-zig-with-raylib-part-3-edge-collisions-scoring-player-input/</guid><description>&lt;p>In this part, we round out the core gameplay of our Pong clone in Zig using
raylib. By the end, we&amp;rsquo;ve got a working game - with a bouncing ball, paddle
controls, and basic scoring.&lt;/p>
&lt;h2 id="-edge-collisions">🧱 Edge Collisions&lt;/h2>
&lt;p>We already had paddle collision working, but we needed the ball to bounce off
the top and bottom edges of the screen. That meant checking if the ball&amp;rsquo;s
y-position was outside the visible range and inverting its vertical velocity
accordingly:&lt;/p></description><content:encoded><![CDATA[<p>In this part, we round out the core gameplay of our Pong clone in Zig using
raylib. By the end, we&rsquo;ve got a working game - with a bouncing ball, paddle
controls, and basic scoring.</p>
<h2 id="-edge-collisions">🧱 Edge Collisions</h2>
<p>We already had paddle collision working, but we needed the ball to bounce off
the top and bottom edges of the screen. That meant checking if the ball&rsquo;s
y-position was outside the visible range and inverting its vertical velocity
accordingly:</p>
```zig
pub fn checkEdgeCollisions(self: *Ball, screen_height: f32) void {
    if (self.pos.y < self.r or self.pos.y > screen_height - self.r) {
        self.vel.y *= -1;
    }
}
```
<p>There was a small detour where I realised <code>screen_height</code> wasn’t giving the
expected value - using <code>GetScreenHeight()</code> directly from raylib helped resolve
that.</p>
<h2 id="-scoring-system">🏁 Scoring System</h2>
<p>Once edge collisions were in place, it was time to detect goals. If the ball
passed the left or right edge of the screen, the opposing player got a point. We
also need a <code>reset()</code> method to the <code>Ball</code> struct to return it to the centre
after each goal.</p>
```zig
pub fn reset(self: *Ball) void {
    self.pos = self.home;
    self.vel = .{ .x = 250, .y = -50 };
}
```
<p>For now, let&rsquo;s print the scores via <code>std.debug.print</code>, but this sets the stage
for UI integration.</p>
```zig
if (ball.pos.x > screenWidth) {
    left_paddle.score += 1;
    std.debug.print("scores: l: {d}, r: {d}", .{ left_paddle.score, right_paddle.score });
    ball.reset();
}

if (ball.pos.x < 0) {
    right_paddle.score += 1;
    std.debug.print("scores: l: {d}, r: {d}", .{ left_paddle.score, right_paddle.score });
    ball.reset();
}
```
<h2 id="-player-input">⌨️ Player Input</h2>
<p>Finally, we wired up keyboard input to allow paddle movement. We used
<code>IsKeyDown</code> from raylib, and made sure movement was frame-rate independent by
scaling it with <code>dt</code>:</p>
```zig
pub fn move(self: *Paddle, y: f32, dt: f32) void {
    self.pos.y += y * dt;
}
```
<p>Left paddle uses <code>W/S</code>, right paddle uses <code>E/D</code>. Simple and responsive.</p>
```zig
if (rl.isKeyDown(.w)) {
    left_paddle.move(-100, dt);
}

if (rl.isKeyDown(.s)) {
    left_paddle.move(100, dt);
}

if (rl.isKeyDown(.e)) {
    right_paddle.move(-100, dt);
}

if (rl.isKeyDown(.d)) {
    right_paddle.move(100, dt);
}
```
<h2 id="-whats-working">✅ What&rsquo;s Working</h2>
<p>By the end of this episode, we’ve got:</p>
<ul>
<li>Ball bounces off top and bottom edges</li>
<li>Scoring when the ball passes a paddle</li>
<li>Ball reset after each goal</li>
<li>Both paddles are fully controllable</li>
</ul>
<p>All that’s missing now is a visible score, a win condition, and maybe a simple
menu.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/youtube/shri-codes/pong/pong-3.md">Watch Video</a></li>
<li><a href="https://icle.es/games/pong/">Source Code (at this point)</a></li>
<li>Prev: <a href="https://icle.es/2-collisions.md">Ball Movement &amp; Paddle Collisions</a></li>
<li>Next: <a href="https://icle.es/4-refactor.md">Code Improvements from Review</a></li>
</ul>
]]></content:encoded></item><item><title>inscribe</title><link>https://icle.es/excursions/inscribe/</link><pubDate>Sat, 05 Jul 2025 10:44:02 +0100</pubDate><guid>https://icle.es/excursions/inscribe/</guid><description>&lt;p>When working on &lt;a href="https://icle.es/projector.md">projector&lt;/a>, I needed a frontmatter yaml library
that could read and write changes back. I couldn&amp;rsquo;t find one, so I made a small
one.&lt;/p>
&lt;h2 id="links">Links&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://icle.es/posts/wordsonsand/projector-hugo.md">Intro Post&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://icle.es/lib/inscribe/">Source Code&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://icle.es/tags/inscribe/_index.md">All posts&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>When working on <a href="https://icle.es/projector.md">projector</a>, I needed a frontmatter yaml library
that could read and write changes back. I couldn&rsquo;t find one, so I made a small
one.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/posts/wordsonsand/projector-hugo.md">Intro Post</a></li>
<li><a href="https://icle.es/lib/inscribe/">Source Code</a></li>
<li><a href="https://icle.es/tags/inscribe/_index.md">All posts</a></li>
</ul>
]]></content:encoded></item><item><title>Inscribe: Updating frontmatter in-place with Go and yaml.Node</title><link>https://icle.es/2025/07/05/inscribe-updating-frontmatter-in-place-with-go-and-yaml.node/</link><pubDate>Sat, 05 Jul 2025 10:44:02 +0100</pubDate><guid>https://icle.es/2025/07/05/inscribe-updating-frontmatter-in-place-with-go-and-yaml.node/</guid><description>&lt;p>While building
&lt;a href="https://icle.es/wordsonsand/projector-sync.md">a little tool to sync up YouTube video descriptions from hugo&lt;/a>,
I needed a library to read and write frontmatter in yaml.&lt;/p>
&lt;p>I started with &lt;a href="https://github.com/adrg/frontmatter">adrg/frontmatter&lt;/a> before I
realised that it didn&amp;rsquo;t have the ability to write back.&lt;/p>
&lt;p>I considered contributing to that one, but writing back is a little more complex
than reading - particularly because the &lt;code>frontmatter.Parse&lt;/code> in that one is built
to support partial reading.&lt;/p>
&lt;p>Because adrg/frontmatter only unmarshals into a struct, and doesn’t store the
original bytes, you can’t write back without losing untouched keys.&lt;/p></description><content:encoded><![CDATA[<p>While building
<a href="https://icle.es/wordsonsand/projector-sync.md">a little tool to sync up YouTube video descriptions from hugo</a>,
I needed a library to read and write frontmatter in yaml.</p>
<p>I started with <a href="https://github.com/adrg/frontmatter">adrg/frontmatter</a> before I
realised that it didn&rsquo;t have the ability to write back.</p>
<p>I considered contributing to that one, but writing back is a little more complex
than reading - particularly because the <code>frontmatter.Parse</code> in that one is built
to support partial reading.</p>
<p>Because adrg/frontmatter only unmarshals into a struct, and doesn’t store the
original bytes, you can’t write back without losing untouched keys.</p>
<p>Looking around, I could not find another frontmatter library for Go. I know that
python has a decent library which supports writing back to it (I used it in the
<a href="https://icle.es/wordsonsand/despatches.md">depatcher</a> but I don&rsquo;t want to write python.</p>
<h2 id="multiple-formats">Multiple Formats</h2>
<p>Let&rsquo;s make it extendable by defining a <code>Format</code> that will allow us to add in
other formats later:</p>
```go
// A Format knows how to (un)marshal a particular format of frontmatter.
// e.g. yaml, toml etc.
type Format struct {
	// TODO: We could add details like delimiter to support other formats
	// and auto detection of format
	Unmarshal UnmarshalFunc
	Marshal   MarshalFunc
}

var yamlFormat = Format{
	Unmarshal: yaml.Unmarshal,
	Marshal:   yaml.Marshal,
}
```
<h2 id="writing-back">Writing Back</h2>
<p>We could also do with a struct to hold the whole file contents so that we can
write it back easier.</p>
```go
// A Scribed is a representation of a file that contains frontmatter and markdown content
type Scribed struct {
	format      Format
	frontmatter []byte
	Content     string
}
```
<p>By storing the full frontmatter, we can later accept partial updates without
losing other keys.</p>
<h2 id="naive-merging-of-updates">Naive Merging of Updates</h2>
<p>We (the user) should be able to update just the keys we care about. All the
other keys should be preserved.</p>
<p>The easiest way I could find to do this was to Marshal, Unmarshall and then
merge with the raw Unmarshall:</p>
<h3 id="minimal-merge-strategy-loses-order-and-formatting">Minimal merge strategy (loses order and formatting)</h3>
```go
// Merge frontmatter
var raw map[string]any // full unmarshalled frontmatter
err := s.format.Unmarshal(s.frontmatter, &raw)

updatedBytes, _ := yaml.Marshal(fm) // convert updated to yaml

var updates map[string]any
yaml.Unmarshal(updatedBytes, &updates) // get updated keys as map

for k, v := range updates {
    raw[k] = v // overwrite only touched fields
}

// raw is now the preserved + updated keys
data, err := s.format.Marshal(raw)
if err != nil {
    return err
}
```
<blockquote>
<p>⚠️ <strong>Warning</strong>: Key ordering is lost</p>
<p>Due to the way maps work, the key ordering is lost More accurately, the keys
are sorted alphabetically during write.</p>
<p>It fully rewrites the frontmatter, which also means that double quotes might
disappear etc.</p></blockquote>
<h2 id="in-place-merging-of-updates">In Place Merging of Updates</h2>
<p>If it is important to keep the frontmatter formatting as much as possible, we
need to bigger sledgehammer.</p>
<p>I explored an approach using yaml.Node with ChatGPT&rsquo;s help.</p>
<p>I fitted it into the <code>Format</code> as well</p>
```go
type MergeFunc func(raw []byte, fm any) ([]byte, error)

type Format struct {
	// TODO: We could add details like delimiter to support other formats
	// and auto detection of format
	Unmarshal UnmarshalFunc
	Marshal   MarshalFunc
	Merge     MergeFunc
}

func MergeYaml(raw []byte, fm any) ([]byte, error) {
	var node yaml.Node
	if err := yaml.Unmarshal(raw, &node); err != nil {
		return nil, err
	}

	// Expecting a document with a single mapping node
	if len(node.Content) == 0 || node.Content[0].Kind != yaml.MappingNode {
		return nil, errors.New("invalid frontmatter")
	}

	b, err := yaml.Marshal(fm)
	if err != nil {
		return nil, err
	}

	var updates map[string]string
	yaml.Unmarshal(b, &updates)

	m := node.Content[0]
	for key, value := range updates {
		// Search for the key and update it, or append a new one
		found := false
		for i := 0; i < len(m.Content); i += 2 {
			k := m.Content[i]
			if k.Value == key {
				m.Content[i+1].Value = value
				found = true
				break
			}
		}
		if !found {
			m.Content = append(m.Content,
				&yaml.Node{Kind: yaml.ScalarNode, Value: key},
				&yaml.Node{Kind: yaml.ScalarNode, Value: value},
			)
		}
	}

	var buf bytes.Buffer
	enc := yaml.NewEncoder(&buf)
	if err := enc.Encode(&node); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}
```
<p>This preserves frontmatter formatting well - but it only handles flat YAML.
Nested maps require a bit more work.</p>
<h2 id="supporting-maps-etc-as-values">Supporting maps etc. as values</h2>
<p>We need to switch to <code>map[string]any</code> and tweak a bit of the loop</p>
```go
var updates map[string]any
yaml.Unmarshal(b, &updates)

m := node.Content[0]
for key, value := range updates {
    // Encode value to a yaml.Node
    valNode := &yaml.Node{}
    if err := valNode.Encode(value); err != nil {
        return nil, err
    }

    // Search and replace or append
    found := false
    for i := 0; i < len(m.Content); i += 2 {
        if m.Content[i].Value == key {
            m.Content[i+1] = valNode
            found = true
            break
        }
    }
    if !found {
        m.Content = append(m.Content,
            &yaml.Node{Kind: yaml.ScalarNode, Value: key},
            valNode,
        )
    }
}
```
<p>It&rsquo;s funny how things can be more complicated than it first seems.</p>
<h2 id="minor-tweaks">Minor tweaks</h2>
<p>I also wanted to add the delimiter into the format and use that instead of
hardcoding, which was easy enough.</p>
```go
// A Format knows how to (un)marshal a particular format of frontmatter.
// e.g. yaml, toml etc.
type Format struct {
	Delimiter string
	Unmarshal UnmarshalFunc
	Marshal   MarshalFunc
	Merge     MergeFunc
}

var yamlFormat = Format{
	Delimiter: "---",
	Unmarshal: yaml.Unmarshal,
	Marshal:   yaml.Marshal,
	Merge:     MergeYaml,
}

func (s *Scribed) Write(fm any, out io.Writer) error {
	// Merge frontmatter
	data, err := s.format.Merge(s.frontmatter, fm)
	if err != nil {
		return err
	}

	io.WriteString(out, s.format.Delimiter+"\n")
	out.Write(data)
	io.WriteString(out, s.format.Delimiter+"\n\n")
	io.WriteString(out, s.Content)

	return nil
}

// splitFrontmatter will split frontmatter from Content and store them
func (s *Scribed) splitFrontmatter(r io.Reader) error {
	data, err := io.ReadAll(r)
	if err != nil {
		return err
	}

	parts := bytes.SplitN(data, []byte("\n"+s.format.Delimiter+"\n"), 2)
	if len(parts) != 2 {
		return errors.New("invalid frontmatter format")
	}

	// Remove the opening '---\n' from the first part
	s.frontmatter = bytes.TrimPrefix(parts[0], []byte("---\n"))

	s.Content = string(bytes.TrimLeft(parts[1], "\r\n"))

	return nil
}
```
<h2 id="conclusion">Conclusion</h2>
<p>This is probably the main bits of functionality I&rsquo;ll need to continue with the
<a href="https://icle.es/wordsonsand/projector-sync.md">projector sync</a>.</p>
<p>I had considered <code>frontmatter</code> to be a blackbox with complicated functionality,
but cutting it up and working on it has demystified it and made it easier to
work with. ChatGPT helped.</p>
<p>It should be fairly straightforward to add other frontmatter formats like TOML,
and to autodetect the formats, but I don&rsquo;t need it right now.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://github.com/drone-ah/wordsonsand/tree/main/lib/inscribe">Source Code on GitHub</a></li>
</ul>
]]></content:encoded></item><item><title>Building Pong in Zig with Raylib – Part 2: Ball Movement &amp; Paddle Collisions</title><link>https://icle.es/2025/07/04/building-pong-in-zig-with-raylib-part-2-ball-movement-paddle-collisions/</link><pubDate>Fri, 04 Jul 2025 12:01:00 +0100</pubDate><guid>https://icle.es/2025/07/04/building-pong-in-zig-with-raylib-part-2-ball-movement-paddle-collisions/</guid><description>&lt;p>In &lt;a href="https://icle.es/1-setup.md">Part 1&lt;/a> we set up the basics: a window, paddles, and a ball.
In this episode, we go one step further and get the ball moving 😉, add paddle
collisions, and make everything frame-rate independent.&lt;/p>
&lt;h2 id="ball-movement">Ball Movement&lt;/h2>
&lt;p>The first step was to give the ball some velocity and update its position every
frame.&lt;/p>
```zig
self.pos = rl.math.vector2Add(self.pos, self.vel);
```
&lt;p>We also fixed a small oversight: the ball and paddles were being recreated every
frame inside the game loop. Moving their initialization outside meant we could
actually observe state changes between frames.&lt;/p></description><content:encoded><![CDATA[<p>In <a href="https://icle.es/1-setup.md">Part 1</a> we set up the basics: a window, paddles, and a ball.
In this episode, we go one step further and get the ball moving 😉, add paddle
collisions, and make everything frame-rate independent.</p>
<h2 id="ball-movement">Ball Movement</h2>
<p>The first step was to give the ball some velocity and update its position every
frame.</p>
```zig
self.pos = rl.math.vector2Add(self.pos, self.vel);
```
<p>We also fixed a small oversight: the ball and paddles were being recreated every
frame inside the game loop. Moving their initialization outside meant we could
actually observe state changes between frames.</p>
<h2 id="frame-rate-independence">Frame-Rate Independence</h2>
<p>Raylib provides <code>GetFrameTime()</code> which returns the time in seconds since the
last frame. Multiplying the velocity by this <code>dt</code> value ensures that the ball
movement stays consistent across different frame rates:</p>
```zig
const vel_this_frame = rl.math.vector2Scale(self.vel, dt);
self.pos = rl.math.vector2Add(self.pos, vel_this_frame);
```
<p>With that, the ball now moves at a steady speed, no matter the frame rate.</p>
<h2 id="paddle-collision-x-axis">Paddle Collision (X-Axis)</h2>
<p>Next up: detecting collisions between the ball and paddles. I considered writing
a standalone collision checker, but ended up keeping the logic within the
<code>Paddle</code> struct itself.</p>
<p>To simplify which edge to check (left or right), I added a <code>which</code> field to
paddles - an enum with values <code>left</code> and <code>right</code>. That made the conditional
logic much cleaner.</p>
<p>To debug collisions, I added color switching: when a paddle detects a collision
on the x-axis, it flashes red.</p>
```zig
pub fn isColliding(self: *Paddle, ball: *const Ball) bool {
    // which edge do we need to check
    const crossing_x: bool = switch (self.which) {
        .right => ball.pos.x + ball.r >= self.pos.x,
        .left => ball.pos.x - ball.r <= self.pos.x + size.x,
    };

    self.colour = if (crossing_x) .red else .white;
    return crossing_x;
}
```
<h2 id="paddle-collision-y-axis">Paddle Collision (Y-Axis)</h2>
<p>After confirming horizontal collision detection, I added vertical bounds
checking. This just involved verifying the ball&rsquo;s y-position is within the
paddle’s vertical range.</p>
```zig
pub fn isColliding(self: *Paddle, ball: *const Ball) bool {
    // which edge do we need to check
    const crossing_x: bool = switch (self.which) {
        .right => ball.pos.x + ball.r >= self.pos.x,
        .left => ball.pos.x - ball.r <= self.pos.x + size.x,
    };

    if (!crossing_x) {
        self.colour = .white;
        return false;
    }

    const colliding = ball.pos.y >= self.pos.y and ball.pos.y <= self.pos.y + size.y;

    self.colour = if (colliding) .red else .white;
    return colliding;
}
```
<h2 id="bounce-logic">Bounce Logic</h2>
<p>With detection in place, we added bounce logic to the ball. If a collision with
a paddle is detected, we flip the x-component of the velocity vector:</p>
```zig
pub fn checkPaddleCollision(self: *Ball, paddle: *Paddle) void {
    if (paddle.isColliding(self)) {
        self.vel = rl.math.vector2Scale(self.vel, -1);
    }
}
```
<h2 id="whats-next">What’s Next</h2>
<p>That wraps up part 2. In the next episode, we’ll handle edge collisions (top and
bottom), scoring, and input management.</p>
<p>Thanks for following along!</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://youtu.be/IoOLH1O_a7M">Watch Video</a></li>
<li><a href="https://github.com/drone-ah/wordsonsand/tree/shri-codes/pong/part-2/games/pong">Source Code (at this point)</a></li>
<li>Prev: <a href="https://icle.es/1-setup.md">Place Paddles &amp; Ball</a></li>
<li>Next: <a href="https://icle.es/3-scoring.md">Edge Collisions, Scoring &amp; Inputs</a></li>
</ul>
]]></content:encoded></item><item><title>Generate YouTube Descriptions from Hugo</title><link>https://icle.es/2025/07/03/generate-youtube-descriptions-from-hugo/</link><pubDate>Thu, 03 Jul 2025 13:18:15 +0100</pubDate><guid>https://icle.es/2025/07/03/generate-youtube-descriptions-from-hugo/</guid><description>&lt;p>Uploading and setting up YouTube videos is fiddly. There are a lot of things to
get right - title, description, chapters, links, tags - the list goes on.&lt;/p>
&lt;p>I also want to link to and from blog posts and social posts - and making sure
those links stay in sync is a hassle.&lt;/p>
&lt;p>It gets more complicated when scheduling multiple videos.&lt;/p>
&lt;p>I wanted to make it easier&lt;/p>
&lt;p>I (at the time of writing) use hugo for this blog site, and I regularly link
from the YouTube description to a page on here. Since I want to save having to
copy and paste that link into YouTube, leveraging the CMS felt sensible.&lt;/p></description><content:encoded><![CDATA[<p>Uploading and setting up YouTube videos is fiddly. There are a lot of things to
get right - title, description, chapters, links, tags - the list goes on.</p>
<p>I also want to link to and from blog posts and social posts - and making sure
those links stay in sync is a hassle.</p>
<p>It gets more complicated when scheduling multiple videos.</p>
<p>I wanted to make it easier</p>
<p>I (at the time of writing) use hugo for this blog site, and I regularly link
from the YouTube description to a page on here. Since I want to save having to
copy and paste that link into YouTube, leveraging the CMS felt sensible.</p>
<h2 id="a-youtube-content-type">A YouTube Content Type</h2>
<p><a href="https://icle.es/archetypes/youtube.md">archetypes/youtube.md</a></p>
```yaml
---
title: "{{ replace .Name "-" " " | title }}"
publishDate: {{ .Date }}
youtubeId: ""
playlist: ""
categoryId: 20
tags: []
chapters:
  - "0:00 Intro"
links:
  - title: <title>
    url: <url>
_build:
  list: never
  render: always
  publishResources: false
sitemap: false
---
```
<ul>
<li><code>title</code>: Title for the youtube video</li>
<li><code>publishDate</code>: When should the video go live</li>
<li><code>youtubeId</code>: The video id from YouTube, used to build links</li>
<li><code>playlist</code>: Which playlist is this a part of? Used to build links</li>
<li><code>categoryId</code>:
<a href="https://mixedanalytics.com/blog/list-of-youtube-video-category-ids/">YouTube category Id</a> -
e.g. gaming</li>
<li><code>tags</code>: Used to add hashtags at the end of the video</li>
<li><code>chapters</code>: Added to description to demarcate chapters</li>
<li><code>links</code>: adds each link to the description. Can be other youtube videos</li>
<li><code>outputs</code>: needed to output in plaintext format</li>
<li><code>_build</code> and <code>sitemap</code>: prevent this file getting linked/crawled</li>
</ul>
<h3 id="cascaded-properties">Cascaded Properties</h3>
<p>We also want to prevent these pages from showing up on:</p>
<ul>
<li>List Pages</li>
<li>Sitemap</li>
</ul>
<p>We also want to prevent them from being published. We could add <code>_build</code> to each
of the <code>md</code> files, or we can cascade it (thanks to
<a href="https://discourse.gohugo.io/u/jmooring">jmooring</a> from the gohugo discourse).</p>
<p><a href="https://icle.es/youtube/_index.md">content/youtube/_index.md</a></p>
```yaml
title: "YouTube"
cascade:
  _build:
    list: never
    render: always
    publishResources: false
  sitemap: false
```
<h3 id="layout-plain-text">Layout (plain text)</h3>
<p>We need a plaintext template to render it as text</p>
<p><a href="https://icle.es/layouts/_default/single.plain.txt">layouts/_default/single.plain.txt</a></p>
```gotmpl
{{ .Content | plainify | htmlUnescape }}

{{- if .Params.links }}
Links:
{{- range .Params.links }}
{{ .title }}: {{ .url | absURL }}
{{- end }}
{{ end }}

{{- if .Params.chapters }}
{{ range .Params.chapters }}
{{- . }}
{{ end -}}
{{ end }}

{{- if .Params.tags }}
{{ range .Params.tags }}#{{ . }} {{ end }}
{{ end }}
```
<h3 id="enable-plaintext-output">Enable plaintext output</h3>
<p>We also need to define plain as an output format.</p>
<p>As far as I could see, there is no way (currently) in hugo to specify a default
output type for a <code>type</code> (i.e. youtube) of content, only a <code>kind</code> (e.g. page) of
content.</p>
<p>However, we can add this to the cascade as well:</p>
```yaml
cascade:
  outputs: ["plain"]
```
<p>I also created a <code>layouts/_default/list.plain.txt</code> file to avoid the error:</p>
<p><code>WARN  found no layout file for &quot;plain&quot; for kind &quot;section&quot;: You should create a template file which matches Hugo Layouts Lookup Rules for this combination.</code></p>
<p>The contents of this file doesn&rsquo;t really matter as we shouldn&rsquo;t be rendering or
using it.</p>
<p><a href="https://icle.es/hugo.toml">hugo.toml</a></p>
```toml
[outputFormats.plain]
	mediaType = "text/plain"
	baseName = "index"
	isPlainText = true
	isHTML = false
	noUgly = true
```
<h2 id="auto-link-to-youtube">Auto link to YouTube</h2>
<p>I&rsquo;d like to be able to link to a local markdown file, and have that resolve to
the correct YouTube URL.</p>
<h3 id="from-posts">From Posts</h3>
<p><a href="https://icle.es/layouts/_default/_markup/render-link.html">layouts/_default/_markup/render-link.html</a></p>
```gotmpl
{{- if eq $page.Type "youtube" -}}
  {{- $href = printf "https://www.youtube.com/watch?v=%s" $page.Params.youtubeId -}}
{{- else -}}
  <a href="{{ $page.RelPermalink | safeURL }}" {{ with .Title }}title="{{ . }}"{{ end }}>{{ $text }}</a>
{{- end -}}

<a href="{{ $href | safeURL }}">{{ $text }}</a>
```
<p>You know what would be nicer? If it took the user to the video in the playlist -
if playlist is defined</p>
```gotmpl
{{- if eq $page.Type "youtube" -}}
  {{- $href = printf "https://www.youtube.com/watch?v=%s" $page.Params.youtubeId -}}
  {{- with $page.Params.playlist }}
    {{- $href = printf "%s&list=%s" $href . -}}
  {{- end }}
{{- else -}}
  <a href="{{ $page.RelPermalink | safeURL }}" {{ with .Title }}title="{{ . }}"{{ end }}>{{ $text }}</a>
{{- end -}}

<a href="{{ $href | safeURL }}">{{ $text }}</a>
```
<h3 id="from-youtube-description">From YouTube Description</h3>
<p>Let&rsquo;s render links to YouTube from the <code>links</code> property:</p>
<p><a href="https://icle.es/layouts/_default/single.plain.txt">layouts/_default/single.plain.txt</a></p>
```gotmpl
{{ with .Params.links }}
Links:
{{- $this := $.Page }}
{{ range . -}}
  {{- $target := $this.GetPage .url -}}
  {{- if and $target (eq $target.Type "youtube") -}}
    {{- $href := printf "https://www.youtube.com/watch?v=%s" $target.Params.youtubeId -}}
    {{- with $target.Params.playlist -}}
      {{- $href = printf "%s&list=%s" $href . -}}
    {{- end -}}
    {{ .title }}: {{ $href }}
  {{- else if $target -}}
    {{ .title }}: {{ $target.Permalink }}
  {{- else -}}
    {{ .title }}: {{ .url | absURL }}
  {{- end }}
{{ end }}
{{ end }}
```
<h3 id="future-links">Future Links</h3>
<p>While we&rsquo;re at it, let&rsquo;s skip rendering any links that go live in the future:</p>
```gotmpl
{{- $target := $this.GetPage .url -}}
{{- if and $target (eq $target.Type "youtube") (not ($target.PublishDate.After now)) -}}
  {{- $href := printf "https://www.youtube.com/watch?v=%s" $target.Params.youtubeId -}}
  {{- with $target.Params.playlist -}}
    {{- $href = printf "%s&list=%s" $href . -}}
  {{- end -}}
  {{ .title }}: {{ $href }}
{{- else if and $target (ne $target.Type "youtube") -}}
  {{ .title }}: {{ $target.Permalink }}
{{- else if not $target -}}
  {{ .title }}: {{ .url | absURL }}
{{- end }}
```
<h3 id="lets-also-skip-draft-posts">Let&rsquo;s also skip draft posts</h3>
```gotmpl
{{- else if not $target -}}
    {{- $url := .url -}}
    {{- $isExternal := or (strings.HasPrefix $url "http") (strings.HasPrefix $url "mailto:") (strings.HasPrefix $url "#") -}}
    {{- $isTag := strings.HasPrefix $url "/tags/" -}}

    {{- if or $isExternal $isTag -}}
        {{ .title }}: {{ $url | absURL }} {{ "\n" }}
    {{- else -}}
        {{- warnf "Unresolved internal link: %q in %q" $url $this.File.Path -}}
    {{- end -}}
{{- end }}
```
<h2 id="next">Next</h2>
<p>This covers the Hugo-side of things.</p>
<p>There are two more parts, that I&rsquo;d like to happen automatically:</p>
<ul>
<li><a href="https://icle.es/projector-upload.md">Uploading the video</a></li>
<li><a href="https://icle.es/projector-sync.md">Syncing metadata</a></li>
</ul>
<h2 id="links--references">Links / References</h2>
<ul>
<li><a href="https://discourse.gohugo.io/t/generating-youtube-descriptions-using-hugo/55233/2?u=drone.ah">Suggestions from <code>jmooring</code> on hugo discourse</a></li>
<li><a href="https://gohugo.io/configuration/cascade">cascade</a></li>
</ul>
<h2 id="updates">Updates</h2>
<ul>
<li>2025-07-15: add note about skipping draft posts</li>
<li>2025-07-15: permalink file references to this commit</li>
</ul>
]]></content:encoded></item><item><title>projector</title><link>https://icle.es/excursions/projector/</link><pubDate>Thu, 03 Jul 2025 13:18:15 +0100</pubDate><guid>https://icle.es/excursions/projector/</guid><description>&lt;p>When uploading videos for &lt;a href="https://icle.es/endeavours/shri-codes.md">shri codes&lt;/a>, I got a bit
tired of keeping links and all the other information in sync in the videos. With
the help of hugo, I decided to automate parts of it.&lt;/p>
&lt;h2 id="links">Links&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://icle.es/posts/wordsonsand/projector-hugo.md">Intro Post&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://icle.es/posts/wordsonsand/projector-sync.md">Sync Post&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://icle.es/tools/projector/">Source Code&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://icle.es/tags/projector/_index.md">All posts&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>When uploading videos for <a href="https://icle.es/endeavours/shri-codes.md">shri codes</a>, I got a bit
tired of keeping links and all the other information in sync in the videos. With
the help of hugo, I decided to automate parts of it.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/posts/wordsonsand/projector-hugo.md">Intro Post</a></li>
<li><a href="https://icle.es/posts/wordsonsand/projector-sync.md">Sync Post</a></li>
<li><a href="https://icle.es/tools/projector/">Source Code</a></li>
<li><a href="https://icle.es/tags/projector/_index.md">All posts</a></li>
</ul>
]]></content:encoded></item><item><title>The End</title><link>https://icle.es/2025/07/02/the-end/</link><pubDate>Wed, 02 Jul 2025 12:17:38 +0100</pubDate><guid>https://icle.es/2025/07/02/the-end/</guid><description>&lt;p>It&amp;rsquo;s funny how much can change in a moment. There was once a ship that was
happy. It had sharp edges and a soft soul. It loved feeling the cool sea breeze,
the mist as it flew close to the ocean. It loved the heat of the desert, and it
loved to watch the setting sun.&lt;/p>
&lt;p>It loved to watch the birds, to race galloping creatures below, often pretending
to lose.&lt;/p></description><content:encoded><![CDATA[<p>It&rsquo;s funny how much can change in a moment. There was once a ship that was
happy. It had sharp edges and a soft soul. It loved feeling the cool sea breeze,
the mist as it flew close to the ocean. It loved the heat of the desert, and it
loved to watch the setting sun.</p>
<p>It loved to watch the birds, to race galloping creatures below, often pretending
to lose.</p>
<p>One morning, not unlike many others, it was resting atop a mountain watching the
sun rise, basking in the warmth of the sun and the twittering of the birds.</p>
<p>It&rsquo;s funny how much can change in a moment.</p>
<p>It was now drifting in silence surrounded by what looked like asteroids. The
warmth of the sun felt like a distant memory. How many moments had passed in
between? It did not know.</p>
<p>Beyond the asteroids, far in the distance, it could make out vague shapes.
Amongst them, one it almost recognised - a segment of a shattered sphere. The
ship recognised the ocean, now spilling into the black of space. It had once
felt the mist from that ocean, many years ago. It would not skim that ocean
again.</p>
<p>It slowly became aware of the structure - gargantuan, black, looming in the
dark. It hung weightlessly above what was once the ship&rsquo;s home, barely visible,
quietly consuming the remains. Methodically, mechanically, it tore through the
wreckage, scattering crumbs into the void - rocks, dust, fragments of what once
was.</p>
<p>The ship found itself drifting in silence surrounded by what looked like
asteroids.</p>
<p>There was no asteroid belt here before.</p>
]]></content:encoded></item><item><title>Automated Posting to BlueSky &amp; Reddit</title><link>https://icle.es/2025/07/01/automated-posting-to-bluesky-reddit/</link><pubDate>Tue, 01 Jul 2025 10:09:47 +0100</pubDate><guid>https://icle.es/2025/07/01/automated-posting-to-bluesky-reddit/</guid><description>&lt;p>I tend to be pretty impatient and when I&amp;rsquo;m doing something, I want to just
finish it off. Unfortunately, the world works better for me when I work to its
schedule.&lt;/p>
&lt;p>Every time I finish a video for &lt;a href="https://icle.es/endeavours/shri-codes.md">shri codes&lt;/a>,
while I am still in the zone, I want to post to all the places (YouTube, BlueSky
and Reddit). However, this is usually the worst time to share these if I want to
get some decent traffic and raise awareness.&lt;/p></description><content:encoded><![CDATA[<p>I tend to be pretty impatient and when I&rsquo;m doing something, I want to just
finish it off. Unfortunately, the world works better for me when I work to its
schedule.</p>
<p>Every time I finish a video for <a href="https://icle.es/endeavours/shri-codes.md">shri codes</a>,
while I am still in the zone, I want to post to all the places (YouTube, BlueSky
and Reddit). However, this is usually the worst time to share these if I want to
get some decent traffic and raise awareness.</p>
<p>I&rsquo;ve been remembering to post on the relevant days at reasonable times, but this
process is annoying at best, interrupts flow and takes up cognitive load.</p>
<p>I wanted to automate it. I&rsquo;ve got to say that automating these two seemingly
simple tasks were rife with unexpected complexity.</p>
<p>My first challenge was trying to get <code>rule_python</code> to work, which, in the end, I
did not succeed and gave up.</p>
<p>Getting <code>pylyzer</code> to work in neovim was also a challenge - another one that I
gave up on.</p>
<p>I briefly gave up on python altogether and went with go, and I made stellar
progress until I got to the bit about actually posting to <code>BlueSky</code> - ChatGPT
had (once again) lied to me (shame on me not verifying their claims). The
library it wanted me to use was a hallucination, and did not exist. I then
realised that there was no real library for reddit integration either.</p>
<p>Back to python, and trusty <code>poetry</code> to see me through.</p>
<h2 id="scheduled-elements">Scheduled Elements</h2>
<p>There are four elements to getting scheduling to work</p>
<h3 id="youtube-scheduling">YouTube Scheduling</h3>
<p>This part was the easiest. The platform is kind enough to provide an option to
schedule release of videos, and we&rsquo;ll use that!</p>
<h3 id="scheduled-publish-for-blog">Scheduled Publish for Blog</h3>
<p><code>hugo</code> supports this out of the box. The bigger challenge was how to get GitHub
Actions to regenerate the site when relevant. In the end, I identified the
window during the week when I want to be publishing.</p>
<p>10am - 4pm Mon - Fri seemed like a decent slot. GitHub Actions though does not
support summer time. I opted for 10am - 3pm, which seemed the better option.</p>
<p>My GitHub action for publishing takes one minute to execute. If I run the action
every 30 minutes, for three hours five days a week:</p>
<p><code>2 * 5 * 5 * 4 = 200</code></p>
<p><a href="https://github.com/drone-ah/wordsonsand/blob/main/.github/workflows/hugo.yaml">.github/workflows/hugo.yaml</a></p>
```yaml
on:
  schedule:
    - cron: "*/30 12-15 * * 1-5"
  # Runs on pushes targeting the default branch
  push:
    branches:
      - main
```
<p>I will need to run a second one for the despatches (below) as well, which would
mean around 400 minutes each month - while there are no limits for public
repos - it felt a little abusive to run it every minute.</p>
<p>Once this has been running safely for a while, I&rsquo;ll consider bumping the
cadence.</p>
<h4 id="cron-is-unreliable-on-github-actions">Cron is Unreliable on GitHub Actions</h4>
<p>After I got this all ready with the two workflows set up to run on GitHub
Actions, I waited, and waited, and nothing happened.</p>
<p><a href="https://docs.github.com/en/actions/reference/events-that-trigger-workflows#schedule">GA schedule doc</a>
states:</p>
<blockquote>
<p>The schedule event can be delayed during periods of high loads of GitHub
Actions workflow runs. High load times include the start of every hour. If the
load is sufficiently high enough, some queued jobs may be dropped. To decrease
the chance of delay, schedule your workflow to run at a different time of the
hour.</p></blockquote>
<p>The
<a href="https://upptime.js.org/blog/2021/01/22/github-actions-schedule-not-working/">upptime post about GitHub Actions schedule not working</a>
includes some suggested workarounds, namely:</p>
<ul>
<li><a href="https://ifttt.com/">IFTTT</a> - seems to be limited to a maximum of hourly</li>
<li><a href="https://cloud.google.com/scheduler/docs/">Google Cloud Scheduler</a> - could be
a good solution but a bit of a sledgehammer</li>
<li><a href="https://cronhub.io/">Cronhub</a> - starts at $19/mo</li>
</ul>
<p>I also discovered:</p>
<ul>
<li><a href="https://cron-job.org/">cron-job.org</a> - haven&rsquo;t tried this yet, but looks
viable</li>
</ul>
<p>I was going to try out cron-job.org when ChatGPT suggested a simpler
alternative - a simple workflow that only triggered the relevant workflows.</p>
<p>According to ChatGPT, the more complex a workflow, the more likely it is to be
dropped. It makes sense, of course, and while I wasn&rsquo;t fully convinced, I
decided to
<a href="https://github.com/drone-ah/wordsonsand/blob/main/.github/workflows/cron.yaml">give it a go</a>.</p>
<p>It&rsquo;s only been 10 minutes, but it has completed one run already - which is
promising, but the original run also ran once.</p>
<p>I&rsquo;ll have to keep an eye on the reliability of this.</p>
<h4 id="switched-to-cron-joborg">Switched to <code>cron-job.org</code></h4>
<p>While the above strategy was OK, I wanted something more reliable, so I switched
to <a href="https://console.cron-job.org">cron-job.org</a></p>
<p>I created a new access token, restricted to the repo and with two additional
permissions:</p>
<ul>
<li>actions: read &amp; write</li>
<li>contents: read (to read the workflow file, ChatGPT suggests)</li>
</ul>
<p>I then set up a http call to:</p>
<p><code>https://api.github.com/repos/&lt;gh-username&gt;/&lt;repo-name&gt;/actions/workflows/&lt;workflow-filename&gt;/dispatches</code></p>
<ul>
<li><code>&lt;gh-username&gt;</code>: use your github username from the url</li>
<li><code>&lt;repo-name&gt;</code>: name of your repo, again from the url</li>
<li><code>&lt;workflow-filename&gt;</code>: The filename of the workflow you want to trigger</li>
</ul>
<p>To triger my hugo run, I used:
<code>https://api.github.com/repos/drone-ah/wordsonsand/actions/workflows/hugo.yaml/dispatches</code></p>
<p>Under advanced, I set the following Headers:</p>
<ul>
<li><code>Accept</code>: <code>application/vnd.github+json</code></li>
<li><code>Authorization</code>: <code>token &lt;personal-access-token&gt;</code></li>
<li><code>Content-Type</code>: <code>application/json</code></li>
<li><code>User-Agent</code>: <code>cronjob</code></li>
</ul>
<p>Set <code>Request method</code> to <code>POST</code></p>
<p><code>Request body</code>:</p>
```json
{
  "ref": "main"
}
```
<h3 id="bluesky">BlueSky</h3>
<p>This one - posting to BlueSky was far more complicated than I anticipated. All
the complexity was around its requirement to separate the post out into facets.
I recognise and value the semantic content such a process would output. However,
I could not find an algorithm or any details on how to extract the facets from
some text - e.g. markdown.</p>
<p>I referenced some code from a couple of sources for a stopgap solution to
address urls and hashtags.</p>
<p>And then, I found <a href="https://github.com/dmoggles/blueskysocial">blueskysocial</a></p>
<h3 id="reddit">Reddit</h3>
<p>You first need to <a href="https://www.reddit.com/prefs/apps/">register an app</a> on
reddit, from a page I don&rsquo;t seem to be able to get from anywhere except a direct
link.</p>
<p>Once I registered a <code>personal script</code>, which will let any of the developers
registered on that client to post, I got to try and login and was faced with:</p>
<p><code>prawcore.exceptions.OAuthException: invalid_grant error processing request</code></p>
<h4 id="red-herrings">Red Herrings</h4>
<p>I tried directly with curl:</p>
```bash
curl -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d "grant_type=password&username=$USERNAME&password=$PASSWORD" \
  -A "$APP_NAME" \
  https://www.reddit.com/api/v1/access_token
```
<p>and I got a similar error:</p>
<p><code>{&quot;error&quot;: &quot;invalid_grant&quot;}</code></p>
<p>After stumbling around for a while, verifying and re-verifying the credentials,
I also set up a brand new account using password auth (mine was originally
oauth). It also returned the same error.</p>
<p>Some resources that I followed:</p>
<ul>
<li><a href="https://www.reddit.com/r/redditdev">redditdev</a></li>
<li><a href="https://github.com/reddit/reddit/wiki/OAuth2-Quick-Start-Example">OAuth2 Quick Start Example</a></li>
</ul>
<p>While I was lookin around, I noticed in tiny little letters on the page to
<a href="https://www.reddit.com/prefs/apps/">register an app</a>, when you create a new
app:</p>
<blockquote>
<p>By creating an app, you agree to Reddit&rsquo;s Developer Terms and Data Api Terms.
<strong>You must also
<a href="https://www.reddit.com/r/reddit.com/wiki/api/#wiki_read_the_full_api_terms_and_sign_up_for_usage">register to use the API</a>.</strong></p></blockquote>
<p>(Emphasis mine)</p>
<p>I followed the instructions on that page, which felt more like red tape, but
easy enough for an app that is only intended to post on a schedule.</p>
<p>Alas, this too did not help!</p>
<h4 id="final-solution">Final Solution</h4>
<p>Perhaps not surprisingly, the final solution was to not use the password, but
get a refresh token instead.</p>
<p>You can do this manually on the browser. Start by going to the following URL:</p>
<p><code>https://www.reddit.com/api/v1/authorize?client_id=YOUR_CLIENT_ID&amp;response_type=code&amp;state=xyz&amp;redirect_uri=http://localhost&amp;duration=permanent&amp;scope=identity,submit,read</code></p>
<ul>
<li><code>YOUR_CLIENT_ID</code>: Replace this with the client id from your reddit app</li>
<li><code>redirect_uri</code>: This value has (<code>http://localhost</code> in the example) has to
match the <code>redirect_uri</code> setting in your app</li>
<li><code>scope</code>: Update to the scopes you are looking for.
<a href="https://www.reddit.com/api/v1/scopes">/api/vi/scopes</a> will return the list of
valid scopes and their descriptions.</li>
<li><code>state</code>: can be any value. It&rsquo;s supposed to match in the next step</li>
</ul>
<p>The browser will then ask your permission (of the scopes you defined). If you
approve, the browser will redirect to localhost (or whatever url you define for
the redirect above).</p>
<p>This redirect will likely fail, but that&rsquo;s ok. There is one parameter in the URL
that you are looking for - <code>code</code></p>
<p>In my case, I got something like:</p>
<p><code>http://localhost:8080/?state=xyz&amp;code=RilF7XDhRTr7o7B-iov2gpdDgum5pA#_</code></p>
<p>(don&rsquo;t worry - that code isn&rsquo;t the actual one)</p>
<p>You want to take the code, but without the <code>#_</code> at the end and substitute it in
the following:</p>
```bash
curl -X POST -A "despatcher" --user "$CLIENT_ID:$CLIENT_SECRET" \
  --data "grant_type=authorization_code&code=$CODE&redirect_uri=http://localhost:8080" \
  https://www.reddit.com/api/v1/access_token
```
<ul>
<li><code>CLIENT_ID</code>: The app id from your app settings page (again)</li>
<li><code>CLIENT_SECRET</code>: The secret from you app settings page</li>
<li><code>CODE</code>: The code that was in the URL above</li>
<li><code>redirect_uri</code>: exactly the same <code>redirect_uri</code> as above, and in the app
settings</li>
</ul>
```json
{
  "access_token": "<access-token>",
  "token_type": "bearer",
  "expires_in": 86400,
  "refresh_token": "<refresh_token>",
  "scope": "read submit identity"
}
```
<ul>
<li><code>access_token</code>: You can use this to auth, but not so useful for long term use
as it will expire</li>
<li><code>refresh_token</code>: more useful as it can be used to get a new access token. Pass
to <code>praw</code></li>
</ul>
<p><a href="https://github.com/drone-ah/wordsonsand/tree/main/tools/despatcher/despatch.py">tools/despatcher/despatch.py</a></p>
```python
client_id = os.environ.get("APP_REDDIT_CLIENT_ID")
client_secret = os.environ.get("APP_REDDIT_CLIENT_SECRET")
refresh_token = os.environ.get("APP_REDDIT_REFRESH_TOKEN")

reddit = praw.Reddit(
    client_id=client_id,
    client_secret=client_secret,
    refresh_token=refresh_token,
    user_agent="despatcher",
)

print(reddit.user.me())
```
<h2 id="posting--tracking">Posting &amp; Tracking</h2>
<p>Once a post has been submitted, it is important that we log it somehow.
Otherwise, we&rsquo;ll end up posting it again (and again (and again)).</p>
<p>The cleanest solution I could think of was to update the markdown file, then
commit and push the change. This will also help to keep a log of it.</p>
<p><a href="https://github.com/drone-ah/wordsonsand/blob/main/.github/workflows/despatcher.yaml">.github/workflows/despatcher.yaml</a></p>
```yaml
- name: Commit and push if changed
  run: |
    git config user.name "drone-ah bot"
    git config user.email "github.actions@drone-ah.com"

    if ! git diff --quiet; then
      git add -u
      git commit -m "auto: log post submissions"
      git push
    else
      echo "No changes to commit"
    fi
```
<h2 id="partial-successes">Partial Successes</h2>
<p>Now, I thought I&rsquo;d covered the worst offenders for risk of repeated posting, but
I&rsquo;d missed one case.</p>
<p>What happens when something gets posted, then the script errors?</p>
<p>Well, the git commit won&rsquo;t happen - and sadly this happened to my. My apologies
to the nice folks at <a href="https://www.reddit.com/r/selfhosted/">r/selfhosted</a> who
got a handful of my posts about automated posting - eek :(</p>
<p>Embarrassment aside, it identified at least one fix - probably two. Extra
embarrassing because something like this has happened to me before - many years
ago - but you live!</p>
<p>The first update is to get GitHub Actions to carry on even if there is an error:</p>
<p><a href="https://github.com/drone-ah/wordsonsand/blob/main/.github/workflows/despatcher.yaml">.github/workflows/despatcher.yaml</a></p>
```yaml
- name: Run despatcher script
  working-directory: tools/despatcher
  continue-on-error: true
  run: poetry run ./despatch.py ../../despatches/
```
<p>The second fix it to catch any errors from the dispatchers.</p>
<p><a href="https://github.com/drone-ah/wordsonsand/tree/main/tools/despatcher/despatch.py">tools/despatcher/despatch.py</a></p>
```python
try:
    ptype = p.get("type")
    if ptype == "bluesky":
        url = post_bluesky(p)

    if ptype == "reddit":
        url = post_reddit(p)
except Exception as e:
    print(f"[ERROR] Failed to post to {ptype} for {path}: {e}")
    continue  # Skip to the next file
```
<h2 id="wrap-up">Wrap Up</h2>
<p>In the end, what I thought was a two hour job took me two days, but such is the
life of a software engineer (probably everyone).</p>
<p>I am looking forward to see how it works, and a little scared if it&rsquo;ll go off
and do random things in my name - but we&rsquo;ll see</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/tools/despatcher/">Code Repo</a></li>
</ul>
<h2 id="updates">Updates</h2>
<ul>
<li>2025-07-08: Switch to <code>cron-job.org</code></li>
<li>2025-07-02: Add note about GA cron unreliability</li>
<li>2025-07-02: Add details of handling partial success</li>
</ul>
]]></content:encoded></item><item><title>despatches</title><link>https://icle.es/excursions/despatches/</link><pubDate>Tue, 01 Jul 2025 10:09:47 +0100</pubDate><guid>https://icle.es/excursions/despatches/</guid><description>&lt;p>I got tired of setting alarms do post into bluesky / reddit based on what was a
good time for each of the platforms.&lt;/p>
&lt;p>I build a small tool to automate the scheduled posting&lt;/p>
&lt;h2 id="links">Links&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://icle.es/posts/wordsonsand/despatches.md">Intro Post&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://icle.es/tools/despatcher/">Source Code&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://icle.es/tags/despatches/_index.md">All posts&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>I got tired of setting alarms do post into bluesky / reddit based on what was a
good time for each of the platforms.</p>
<p>I build a small tool to automate the scheduled posting</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/posts/wordsonsand/despatches.md">Intro Post</a></li>
<li><a href="https://icle.es/tools/despatcher/">Source Code</a></li>
<li><a href="https://icle.es/tags/despatches/_index.md">All posts</a></li>
</ul>
]]></content:encoded></item><item><title>Building Pong in Zig with Raylib – Part 1: Setup, Paddles, and Ball</title><link>https://icle.es/2025/06/28/building-pong-in-zig-with-raylib-part-1-setup-paddles-and-ball/</link><pubDate>Sat, 28 Jun 2025 20:38:29 +0100</pubDate><guid>https://icle.es/2025/06/28/building-pong-in-zig-with-raylib-part-1-setup-paddles-and-ball/</guid><description>&lt;p>Before continuing development on Triangle - my larger, arcade ARPG, factory
game, I wanted to take a step back and build something small and familiar. Pong
felt like the perfect choice: quick to prototype, easy to understand, and a good
warm-up before diving deeper into raylib again.&lt;/p>
&lt;p>This post goes alongside &lt;a href="https://youtu.be/ICq2D_na6zc">my video on YouTube&lt;/a>,
and walks through the early steps of the project: setting up the game, getting
something on screen, and implementing the paddles and the ball.&lt;/p></description><content:encoded><![CDATA[<p>Before continuing development on Triangle - my larger, arcade ARPG, factory
game, I wanted to take a step back and build something small and familiar. Pong
felt like the perfect choice: quick to prototype, easy to understand, and a good
warm-up before diving deeper into raylib again.</p>
<p>This post goes alongside <a href="https://youtu.be/ICq2D_na6zc">my video on YouTube</a>,
and walks through the early steps of the project: setting up the game, getting
something on screen, and implementing the paddles and the ball.</p>
<h2 id="why-pong">Why Pong?</h2>
<p>Sometimes, before getting deeper into a project, it helps to do something simple
just to get your hands moving again. I hadn’t written Zig in a couple of weeks,
and wanted a fast feedback loop to:</p>
<ul>
<li>Get back into using <code>raylib-zig</code></li>
<li>Have a bit of fun</li>
<li>Remind myself why I made certain decisions in the first place</li>
</ul>
<h2 id="project-setup">Project Setup</h2>
<p>I&rsquo;m using <a href="https://github.com/Not-Nik/raylib-zig"><code>raylib-zig</code></a> for this. You
can scaffold a new project with:</p>
```bash
./project_setup.sh <project-name>
```
<p>You could also use <code>zig init</code> and then add the dependency in manually if you
prefer.</p>
<h2 id="drawing-the-playground">Drawing the Playground</h2>
<p>The Pong &ldquo;arena&rdquo; is simple:</p>
<ul>
<li>Two paddles</li>
<li>A ball (currently static)</li>
<li>A center dividing line</li>
<li>Placeholder for scores</li>
</ul>
<h2 id="implementing-paddles">Implementing Paddles</h2>
<p>The paddles have:</p>
<ul>
<li>A position (top-left corner)</li>
<li>A fixed size</li>
<li>A simple render function</li>
</ul>
```zig
const std = @import("std");
const rl = @import("raylib");

const Paddle = @This();

pos: rl.Vector2,

pub fn init(x: f32) Paddle {
    return .{ .pos = .{
        .x = x,
        .y = 200,
    } };
}

pub const size = rl.Vector2{ .x = 25, .y = 100 };

pub fn render(self: Paddle) void {
    rl.drawRectangleV(self.pos, size, .white);
}
```
<p>It might have been nice to be able to have the paddle calculate more of its own
values, but the more I think about it, the more it makes sense to keep the logic
in paddle simpler.</p>
<h2 id="ball-placeholder">Ball Placeholder</h2>
<p>To wrap things up for this session, I added a static ball in the center of the
screen. It has a radius, position, and velocity fields ready to go. Rendering is
straightforward:</p>
```zig
const std = @import("std");
const rl = @import("raylib");

const Ball = @This();

pos: rl.Vector2,
r: f32 = 16,
vel: rl.Vector2 = .{ .x = 0, .y = 0 },

pub fn render(self: Ball) void {
    rl.drawCircleV(self.pos, self.r, .white);
}
```
<h2 id="whats-next">What&rsquo;s Next</h2>
<p>This was mostly about warming up, but the next episode will tackle:</p>
<ul>
<li>Adding velocity to the ball</li>
<li>Basic collision detection with paddles</li>
</ul>
<p>I&rsquo;ll try and think about simplifying paddle logic, especially the awkward
symmetry between left and right.</p>
<p>You can find the full source code
<a href="https://github.com/drone-ah/wordsonsand/tree/main/games/pong">on GitHub</a>.</p>
<p>See you in part 2!</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/youtube/shri-codes/pong/pong-1.md">YouTube Video</a></li>
<li><a href="https://github.com/drone-ah/wordsonsand/tree/shri-codes/pong/part-1/games/pong">Full Source Code (at this point)</a></li>
<li>Next: <a href="https://icle.es/2-collisions.md">Ball Movement &amp; Paddle Collisions</a></li>
</ul>
]]></content:encoded></item><item><title>Under the Hood of triangle</title><link>https://icle.es/2025/06/27/under-the-hood-of-triangle/</link><pubDate>Fri, 27 Jun 2025 08:01:28 +0100</pubDate><guid>https://icle.es/2025/06/27/under-the-hood-of-triangle/</guid><description>&lt;p>In &lt;a href="https://youtu.be/8nA-a5Z1IDc">this Let&amp;rsquo;s Code video&lt;/a> on
&lt;a href="http://www.youtube.com/@ShriCodesHere">&lt;strong>shri codes&lt;/strong>&lt;/a>, I walk through some of
the early systems in place for &lt;a href="https://icle.es/tags/triangle/">&lt;em>Triangle&lt;/em>&lt;/a> - my space
ARPG built in &lt;strong>Zig&lt;/strong> with &lt;strong>raylib&lt;/strong>.&lt;/p>
&lt;p>For the broader story behind the project - what &lt;em>Triangle&lt;/em> is, where it&amp;rsquo;s going,
and why I’m making it - check out the
&lt;a href="https://icle.es/endeavours/triangle.md">triangle endeavour&lt;/a> or the
&lt;a href="https://icle.es/tags/triangle/">devlog archive&lt;/a>. This post is about the &lt;em>how&lt;/em>.&lt;/p>
&lt;h3 id="-core-systems-so-far">🧩 Core Systems So Far&lt;/h3>
&lt;p>At this stage, Triangle supports:&lt;/p>
&lt;ul>
&lt;li>Basic player movement and shooting (Asteroids-style).&lt;/li>
&lt;li>Procedurally generated asteroid fields per &lt;em>sector&lt;/em>.&lt;/li>
&lt;li>Material drops (iron ore) from destroyed asteroids.&lt;/li>
&lt;li>Automatic recipe unlocking when new items are picked up.&lt;/li>
&lt;li>Smelting system that converts ore to ingots.&lt;/li>
&lt;li>Unlocking and queuing up &lt;em>constructors&lt;/em> using the crafting menu.&lt;/li>
&lt;li>A basic UI for crafting and inventory.&lt;/li>
&lt;li>Configurable keyboard mappings using TOML.&lt;/li>
&lt;/ul>
&lt;p>That’s the visible layer. Below the surface, the codebase has carved out some
space for more ambitious systems.&lt;/p></description><content:encoded><![CDATA[<p>In <a href="https://youtu.be/8nA-a5Z1IDc">this Let&rsquo;s Code video</a> on
<a href="http://www.youtube.com/@ShriCodesHere"><strong>shri codes</strong></a>, I walk through some of
the early systems in place for <a href="https://icle.es/tags/triangle/"><em>Triangle</em></a> - my space
ARPG built in <strong>Zig</strong> with <strong>raylib</strong>.</p>
<p>For the broader story behind the project - what <em>Triangle</em> is, where it&rsquo;s going,
and why I’m making it - check out the
<a href="https://icle.es/endeavours/triangle.md">triangle endeavour</a> or the
<a href="https://icle.es/tags/triangle/">devlog archive</a>. This post is about the <em>how</em>.</p>
<h3 id="-core-systems-so-far">🧩 Core Systems So Far</h3>
<p>At this stage, Triangle supports:</p>
<ul>
<li>Basic player movement and shooting (Asteroids-style).</li>
<li>Procedurally generated asteroid fields per <em>sector</em>.</li>
<li>Material drops (iron ore) from destroyed asteroids.</li>
<li>Automatic recipe unlocking when new items are picked up.</li>
<li>Smelting system that converts ore to ingots.</li>
<li>Unlocking and queuing up <em>constructors</em> using the crafting menu.</li>
<li>A basic UI for crafting and inventory.</li>
<li>Configurable keyboard mappings using TOML.</li>
</ul>
<p>That’s the visible layer. Below the surface, the codebase has carved out some
space for more ambitious systems.</p>
<h3 id="-code-structure-overview">🗂️ Code Structure Overview</h3>
<p>Here’s a quick tour of the key directories and their purpose:</p>
<h4 id="blit"><code>blit/</code></h4>
<p>Handles all the 2D rendering abstractions - bounding boxes, shapes (including
triangle fans 😉 and circles), and canvas drawing. Wraps <code>raylib</code> for better
namespacing and type control.</p>
<h4 id="phys"><code>phys/</code></h4>
<p>Physics system. <code>body.zig</code> implements basic movement, collisions, and inertia.
Still minimal, but usable.</p>
<h4 id="combat"><code>combat/</code></h4>
<p>Still early - only <code>bullet</code> is implemented, and is used to destroy asteroids.
Placeholder for upcoming systems like health, damage, and targeting.</p>
<h4 id="items"><code>items/</code></h4>
<p>This module forms the backbone of the crafting system. Items are tied to
recipes, and recipes are triggered via pickups or crafting actions. There&rsquo;s a
lot more to unpack here - likely its own devlog soon.</p>
<h4 id="notifier"><code>notifier/</code></h4>
<p>Manages in-game UI feedback. When you pick something up or unlock a new recipe,
this is what shows it. Notifiers are scoped - one for inventory, one for
crafting - and show up on either side of the screen.</p>
<h4 id="ship"><code>ship/</code></h4>
<p>This module contains the ship related logic, including movement, and the logic
for constructing smelters and other buildables. Eventually this will handle
assembly chains.</p>
<h4 id="asteroid"><code>asteroid</code></h4>
<p>These files should be refactored into its own module, and should contain the
asteroid itself, as well as sector, a chunk of space - asteroids, entities,
bullets, etc. This is the closest thing to a “level” in Triangle. It’s
procedurally generated, and you can eventually travel from one sector to the
next.</p>
<h4 id="diag"><code>diag/</code></h4>
<p>Code to support diagnostics &amp; logging</p>
<h4 id="game"><code>game/</code></h4>
<p>This module should get a bit of refactoring as well, moving some of the files
from the root into its own module</p>
<ul>
<li><code>config</code>: Takes care of user control bindings and input overrides via a TOML
config file.</li>
<li><code>notifier</code>: supports collecting notifications across multiple channels.</li>
<li><code>save</code>: Just scaffolding for now - no save/load logic yet, but it’s a
placeholder to group serializable components together.</li>
<li><code>context</code>: container struct to support dependency injection.</li>
</ul>
<h4 id="ui"><code>ui/</code></h4>
<p>Holds in-game panels like crafting and inventory. Needs cleanup and
consolidation.</p>
<h3 id="-whats-missing">🧪 What’s Missing</h3>
<p>A lot of the systems are stubbed out, but not yet wired in. For instance:</p>
<ul>
<li><code>power</code> is empty (future energy system)</li>
<li>The <em>canvas</em> currently wraps <code>raylib</code> without adding much - that will likely
change as UI complexity grows.</li>
</ul>
<h3 id="-threads-ill-pull-next">🧶 Threads I’ll Pull Next</h3>
<p>These are the next things I’ll either build or record:</p>
<ul>
<li>Improving the coding structure. I&rsquo;d like to improve organisation and am
considering extracting a re-usable library out of it, mainly so I can tinker
with other smaller game projects.</li>
<li>A <strong>rudimentary menu system</strong></li>
<li>A <strong>build+deploy system</strong> to generate early test builds</li>
<li>Possibly a <strong>contact/feedback</strong> form directly in-game or on the site</li>
</ul>
<p>I’ll be releasing this in what I’m calling a <a href="https://icle.es/sprout.md"><strong>&ldquo;Sprout&rdquo; build</strong></a> -
a playable seed, a form of extremely early access. Feedback and collaboration is
really important to me and I want to get this out there as soon as there is the
tiniest spark of &ldquo;life.&rdquo;</p>
<p>If you’d like to follow the journey, you can
<a href="https://youtu.be/8nA-a5Z1IDc">watch the video devlog</a></p>
]]></content:encoded></item><item><title>shri codes</title><link>https://icle.es/endeavours/shri-codes/</link><pubDate>Fri, 27 Jun 2025 08:00:28 +0100</pubDate><guid>https://icle.es/endeavours/shri-codes/</guid><description>&lt;p>I&amp;rsquo;ve mulled over doing coding videos for many years now, and with my recent
exploration (and love) of zig, now might be a good time to start&lt;/p>
&lt;h2 id="links">Links&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://www.youtube.com/@shriCodesHere">YouTube Channel&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://icle.es/tags/shri-codes/">Blog Posts&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>I&rsquo;ve mulled over doing coding videos for many years now, and with my recent
exploration (and love) of zig, now might be a good time to start</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="http://www.youtube.com/@shriCodesHere">YouTube Channel</a></li>
<li><a href="https://icle.es/tags/shri-codes/">Blog Posts</a></li>
</ul>
]]></content:encoded></item><item><title>Surviving to Create</title><link>https://icle.es/2025/06/26/surviving-to-create/</link><pubDate>Thu, 26 Jun 2025 11:06:47 +0100</pubDate><guid>https://icle.es/2025/06/26/surviving-to-create/</guid><description>&lt;p>&lt;a href="https://drone-ah.com/2025/04/26/a-lonely-triangle/">&lt;em>triangle&lt;/em>&lt;/a> is about
depression, recovery, and resilience - things I’ve lived through and am still
figuring out. It&amp;rsquo;s in the early stages, and far from making any money.&lt;/p>
&lt;h2 id="the-real-stuff-behind-making-a-game">The Real Stuff Behind Making a Game&lt;/h2>
&lt;p>Making games is hard! Making games independently is harder. When you&amp;rsquo;re doing it
solo, it&amp;rsquo;s harder still.&lt;br>
It’s juggling health, money, and energy - not to mention motivation.&lt;/p>
&lt;p>I’ve spent over 20 years in tech, running a company, building stuff like
megabus.com’s ticketing system.&lt;br>
After burning out and dealing with some tough health issues, I&amp;rsquo;m struggling with
even the idea of picking up freelance work or hustling like I used to. I have
about 3 months of money left, so I need to be smart.&lt;/p></description><content:encoded><![CDATA[<p><a href="https://drone-ah.com/2025/04/26/a-lonely-triangle/"><em>triangle</em></a> is about
depression, recovery, and resilience - things I’ve lived through and am still
figuring out. It&rsquo;s in the early stages, and far from making any money.</p>
<h2 id="the-real-stuff-behind-making-a-game">The Real Stuff Behind Making a Game</h2>
<p>Making games is hard! Making games independently is harder. When you&rsquo;re doing it
solo, it&rsquo;s harder still.<br>
It’s juggling health, money, and energy - not to mention motivation.</p>
<p>I’ve spent over 20 years in tech, running a company, building stuff like
megabus.com’s ticketing system.<br>
After burning out and dealing with some tough health issues, I&rsquo;m struggling with
even the idea of picking up freelance work or hustling like I used to. I have
about 3 months of money left, so I need to be smart.</p>
<p>On the positive side, I’ve got the experience - in dev and solo work - to see
this through,<br>
I just need to figure out a sustainable financial path.</p>
<h2 id="what-im-doing-now">What I’m Doing Now</h2>
<p>Here’s my current rough plan to keep <em>triangle</em> moving without burning out:</p>
<h3 id="1-building-community">1. Building Community</h3>
<p>I&rsquo;ve been posting devlogs and videos, but haven&rsquo;t really focused on building a
following or community. I loathe the &ldquo;like and subscribe&rdquo; crowd, so I don&rsquo;t want
to be doing that. I want to focus on adding value.</p>
<p>It&rsquo;ll take me longer, and that&rsquo;s ok - I have to figure out the finances, but
that&rsquo;s a separate problem.</p>
<p>I want to keep doing what I&rsquo;ve been doing. Devlogs, videos, sharing and
collaborating. I&rsquo;ll have more time for a bit as well since the main project I
was working on has now been canceled.</p>
<p>I also want to make some improvements to this site to link things up a bit
better and make it a bit easier to find things. That&rsquo;ll have to happen on a
&ldquo;little bit here and there&rdquo; kind of basis though.</p>
<h3 id="2-going-for-grants">2. Going for Grants</h3>
<p>I’m looking at funding from places like Creative Scotland that support mental
health and creative projects.<br>
<em>triangle</em> isn’t just a game - I&rsquo;m trying to express something real, and I’m
trying to communicate that clearly.</p>
<h3 id="3-maybe-a-kickstarter">3. Maybe a Kickstarter</h3>
<p>Further down the line, I might run a Kickstarter -<br>
but only if I can find the right piece of work that adds real value for everyone
involved: myself, the game, and potential contributors.</p>
<h3 id="4-patreon--ko-fi---but-no-paywalls">4. Patreon / Ko-fi - But No Paywalls</h3>
<p>Once I have enough of a following / community (5k - 10k subs), I could set up
Patreon (and/or maybe Ko-fi), but no paywalls - I don&rsquo;t like them. I want to
invite people who want to support and join in, and participate.<br>
Everything stays public - devlogs, updates, builds.<br>
Supporters get perks like name credits and priority access.</p>
<h3 id="4-loans-only-if-i-have-to">4. Loans Only If I Have To</h3>
<p>If I really need it, I’ll consider a small loan to get through a crunch -<br>
but only if it helps hit a real milestone, not just to stretch things out.</p>
<h2 id="other-stuff-im-doing">Other Stuff I’m Doing</h2>
<p>I’ve also started a coding video channel,
<a href="https://www.youtube.com/@ShriCodesHere"><em>Shri Codes</em></a>.<br>
It’s a way to share bits of the process behind building <em>triangle</em>, maybe other
excursions and the things I’m learning along the way.</p>
<p>This blog is where I write devlogs, ideas, and rambling thoughts -<br>
it’s turning into a bit of a central hub that connects everything else I’m
doing. I have a bunch of other things that I&rsquo;ve done in the past (and am working
on now) that I&rsquo;d like to put on here as well. Of course, that takes up time too,
and I&rsquo;m not sure how much real value that will add.</p>
<h2 id="if-you-want-to-follow-along">If You Want to Follow Along</h2>
<p>You&rsquo;re welcome to follow along on whatever platform works for you - no pressure,
no strings.<br>
I love collaboration and would really value your thoughts, whenever you feel
ready to join in.</p>
<p>If you have advice, experience, or just want to say hi, please do. It means a
lot. I&rsquo;ll take every bit of encouragement and support I can get.</p>
]]></content:encoded></item><item><title>Add zig dependency in bazel</title><link>https://icle.es/2025/06/11/add-zig-dependency-in-bazel/</link><pubDate>Wed, 11 Jun 2025 20:44:01 +0100</pubDate><guid>https://icle.es/2025/06/11/add-zig-dependency-in-bazel/</guid><description>&lt;p>&lt;code>[rules-zig][https://github.com/aherrmann/rules_zig]&lt;/code> doesn&amp;rsquo;t use &lt;code>zon&lt;/code>
files(yet) and addind dependencies involves adding it into &lt;code>MODULES.bazel&lt;/code>. If
you&amp;rsquo;re still using workspaces on bazel,
&lt;a href="https://github.com/aherrmann/rules_zig/issues/231#issuecomment-2723684385">this code sample mentioned in the related issue might help&lt;/a>.
I started with that code sample.&lt;/p>
&lt;p>I was adding in a dependency to &lt;a href="https://github.com/sam701/zig-toml">zig-toml&lt;/a>
and this is the
&lt;a href="https://github.com/drone-ah/wordsonsand/blob/9dd185e5f330403cad9afe2ad67cc511565e1325/MODULE.bazel#L43">rule that I used&lt;/a>:&lt;/p>
```starlark
zig_toml_archive(
 name = "zig_toml",
 urls = ["https://github.com/sam701/zig-toml/archive/refs/heads/main.zip"],
 sha256 = "b1f0846c3b5e9696892b0d96f8add4878c057c728623b03d6bfbd508e4af48d5",
 strip_prefix = "zig-toml-main",
 build_file_content = """
load("@rules_zig//zig:defs.bzl", "zig_module")
zig_module(
 name = "toml",
 main = "src/root.zig",
 srcs = glob(["src/**/*.zig"]),
 visibility = ["//visibility:public"],
)
""",
)
```
&lt;p>Let&amp;rsquo;s break it down:&lt;/p></description><content:encoded><![CDATA[<p><code>[rules-zig][https://github.com/aherrmann/rules_zig]</code> doesn&rsquo;t use <code>zon</code>
files(yet) and addind dependencies involves adding it into <code>MODULES.bazel</code>. If
you&rsquo;re still using workspaces on bazel,
<a href="https://github.com/aherrmann/rules_zig/issues/231#issuecomment-2723684385">this code sample mentioned in the related issue might help</a>.
I started with that code sample.</p>
<p>I was adding in a dependency to <a href="https://github.com/sam701/zig-toml">zig-toml</a>
and this is the
<a href="https://github.com/drone-ah/wordsonsand/blob/9dd185e5f330403cad9afe2ad67cc511565e1325/MODULE.bazel#L43">rule that I used</a>:</p>
```starlark
zig_toml_archive(
    name = "zig_toml",
    urls = ["https://github.com/sam701/zig-toml/archive/refs/heads/main.zip"],
    sha256 = "b1f0846c3b5e9696892b0d96f8add4878c057c728623b03d6bfbd508e4af48d5",
    strip_prefix = "zig-toml-main",
    build_file_content = """
load("@rules_zig//zig:defs.bzl", "zig_module")
zig_module(
    name = "toml",
    main = "src/root.zig",
    srcs = glob(["src/**/*.zig"]),
    visibility = ["//visibility:public"],
)
""",
)
```
<p>Let&rsquo;s break it down:</p>
<ul>
<li><code>name</code>: Pretty flexible, but you probably want to use the lib name, or the
name you would use in the zon file.</li>
<li><code>urls</code>: You need the url to the zip file. If you want main - you can click on
the button that says <code>Code</code> and at the use the link at the bottom that says
&ldquo;Download ZIP&rdquo;.
<a href="https://docs.github.com/en/repositories/working-with-files/using-files/downloading-source-code-archives">GitHub documentation on source code archives</a>
has more information as well as details on how to get he url for specific
commits etc.</li>
<li><code>sha256</code>: When running for the first time, bazel will tell you that
<code>a canonical reproducible form can be obtained by modifying arguments integrity = &quot;sha256-&lt;hash&gt;&quot;</code>.
However, for whatever reason, it uses a different format from what you need to
use in the <code>MODULES.bazel</code> file. To convert it, you can use:</li>
</ul>
```bash
echo '<hash>' | base64 -d | xxd -p -c 256
```
<ul>
<li><code>strip_prefix</code>: The zip file will extract the files out into a directory. This
parameter is that directory name. I just unzipped the zip file and put that
here. I&rsquo;m sure there are simpler ways to get at it (please let me know below
if you know of a way)</li>
<li><code>build_file_content</code>: This parameter lets you define what the contents of the
build file will be when building that library. It might be nice to be able to
reference a file rather than include it here - I think I&rsquo;ve done that before
but I can&rsquo;t remember how. Please let me know below if you know how it&rsquo;s done
and I&rsquo;ll update it here.</li>
</ul>
]]></content:encoded></item><item><title>Use `protoc` bin instead of building from source in `bazel`</title><link>https://icle.es/2025/06/09/use-protoc-bin-in-bazel/</link><pubDate>Mon, 09 Jun 2025 20:57:53 +0000</pubDate><guid>https://icle.es/2025/06/09/use-protoc-bin-in-bazel/</guid><description>&lt;p>I work on a project that uses &lt;code>pulumi&lt;/code> automation api, which in turn uses
&lt;code>protobuf&lt;/code>. I think there are other bits in &lt;code>bazel&lt;/code> that also uses it.&lt;/p>
&lt;p>For a while, it was fine - except that some &lt;code>CI&lt;/code> runs would take 15 minutes and
we couldn&amp;rsquo;t quite figure out why.&lt;/p>
&lt;p>I finally had to update &lt;code>bazel&lt;/code> from 7.x to 8 and in that process (which
honestly could have been easier, but oh well), I ran into a problem with
compiling protobuf.&lt;/p>
&lt;p>Namely, I kept running into this error:&lt;/p>
```
error while loading shared libraries: libstdc++.so.6: cannot open shared object file: No such file or directory
```
&lt;p>I tried many things but was not able to get past this error. In the end, I was
able to narrow it down to devbox/nix and how it messes with the environment that
doesn&amp;rsquo;t quite agree with &lt;code>bazel&lt;/code>.&lt;/p>
&lt;h2 id="what-didnt-work">What didn&amp;rsquo;t work&lt;/h2>
&lt;ul>
&lt;li>installing &lt;code>gcc&lt;/code> from devbox&lt;/li>
&lt;li>installing &lt;code>stdenv.cc.cc.lib&lt;/code>&lt;/li>
&lt;li>Setting &lt;code>LD_LIBRARY_PATH&lt;/code> (to
&lt;code>&amp;lt;workspace-dir&amp;gt;/.devbox/nix/profile/default/lib&lt;/code>, which is where the devbo
version of &lt;code>libstdc++.so.6&lt;/code> is installed by &lt;code>stdenv.cc.cc.lib&lt;/code>)&lt;/li>
&lt;li>Using the &lt;code>--action-env=&lt;/code>&lt;/li>
&lt;li>&lt;a href="https://github.com/tweag/rules_nixpkgs">rules-nixpkgs&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>None of which really worked.&lt;/p>
&lt;p>Disabling &lt;code>devbox&lt;/code> &lt;strong>did&lt;/strong> work, which was at least partly encouraging. I also
found a bunch of evidence online around this issue
(&lt;a href="https://github.com/bazelbuild/bazel/issues/12978">1&lt;/a>,
&lt;a href="https://github.com/jetify-com/devbox/issues/1100">2&lt;/a>,
&lt;a href="https://github.com/jetify-com/devbox/issues/1596">3&lt;/a>,
&lt;a href="https://github.com/jetify-com/devbox/issues/710">4&lt;/a>,
&lt;a href="https://github.com/tweag/rules_nixpkgs/issues/573">5&lt;/a>). Bazel seems to
&lt;a href="https://discuss.ray.io/t/bazel-protobuf-build-errors-libstdc-with-non-system-gcc/3329">generally dislike non-system gcc&lt;/a>.&lt;/p></description><content:encoded><![CDATA[<p>I work on a project that uses <code>pulumi</code> automation api, which in turn uses
<code>protobuf</code>. I think there are other bits in <code>bazel</code> that also uses it.</p>
<p>For a while, it was fine - except that some <code>CI</code> runs would take 15 minutes and
we couldn&rsquo;t quite figure out why.</p>
<p>I finally had to update <code>bazel</code> from 7.x to 8 and in that process (which
honestly could have been easier, but oh well), I ran into a problem with
compiling protobuf.</p>
<p>Namely, I kept running into this error:</p>
```
error while loading shared libraries: libstdc++.so.6: cannot open shared object file: No such file or directory
```
<p>I tried many things but was not able to get past this error. In the end, I was
able to narrow it down to devbox/nix and how it messes with the environment that
doesn&rsquo;t quite agree with <code>bazel</code>.</p>
<h2 id="what-didnt-work">What didn&rsquo;t work</h2>
<ul>
<li>installing <code>gcc</code> from devbox</li>
<li>installing <code>stdenv.cc.cc.lib</code></li>
<li>Setting <code>LD_LIBRARY_PATH</code> (to
<code>&lt;workspace-dir&gt;/.devbox/nix/profile/default/lib</code>, which is where the devbo
version of <code>libstdc++.so.6</code> is installed by <code>stdenv.cc.cc.lib</code>)</li>
<li>Using the <code>--action-env=</code></li>
<li><a href="https://github.com/tweag/rules_nixpkgs">rules-nixpkgs</a></li>
</ul>
<p>None of which really worked.</p>
<p>Disabling <code>devbox</code> <strong>did</strong> work, which was at least partly encouraging. I also
found a bunch of evidence online around this issue
(<a href="https://github.com/bazelbuild/bazel/issues/12978">1</a>,
<a href="https://github.com/jetify-com/devbox/issues/1100">2</a>,
<a href="https://github.com/jetify-com/devbox/issues/1596">3</a>,
<a href="https://github.com/jetify-com/devbox/issues/710">4</a>,
<a href="https://github.com/tweag/rules_nixpkgs/issues/573">5</a>). Bazel seems to
<a href="https://discuss.ray.io/t/bazel-protobuf-build-errors-libstdc-with-non-system-gcc/3329">generally dislike non-system gcc</a>.</p>
<h2 id="a-ray-of-hope">A ray of hope</h2>
<p>I was just about ready to throw in the towel when ChatGPT, in passing suggested
using the <code>protobuf</code> binary directly. I wasn&rsquo;t even using this binary - it was
being pulled in as a dependency and I just needed it to work. I did not need it
to be built from source.</p>
<p>Furthermore, I found, from my research into trying to solve this that building
<code>protobuf</code> was the likely culprit in the <code>CI</code> taking 15 minutes.</p>
<p>I also remembered that someone else on the team had issues trying to get it to
build on a mac at some point.</p>
<p>All in all, it was a problematic piece of software and I was seriously
considering switching to <code>opentofu</code> - but they might be using it too.</p>
<h2 id="bin-protoc">bin <code>protoc</code></h2>
<p>It was difficult to find decent documentation about how to achieve this though,
apart from a partially answered on:</p>
<ul>
<li><a href="https://stackoverflow.com/questions/68918369/is-it-possible-to-use-bazel-without-compiling-protobuf-compiler">stackoverflow question</a>,</li>
<li><a href="https://groups.google.com/g/bazel-discuss/c/3Q_GEqNZrC0">google groups for bazel-discuss</a>
<ul>
<li>which also led me to
<a href="https://gitlab.com/mvfwd/issue-bazel-protobuf-compile/-/tree/main">a gitlab repo with some code</a></li>
</ul>
</li>
</ul>
<p>These resources gave me just enough to be able to cobble together a working
solution, which needs:</p>
<h3 id="install-protoc">Install <code>protoc</code></h3>
<p>The first thing we need is a locally installed <code>protoc</code> bin (I&rsquo;ll used devbox to
install it for consistency across dev environments)</p>
```bash
devbox add protobuf
```
<p>You can add a version specifier for better reproducibility.</p>
<h3 id="runnable-target">Runnable Target</h3>
<p>We also need a shell target that will execute this correctly. The <code>bazel</code> flag
to override <code>protoc</code> takes a local target, not a bin.</p>
<p><code>third_party/tools/BUILD.bazel</code></p>
```python
package(default_visibility = ["//visibility:public"])

sh_binary(
    name = "protoc",
    srcs = ["protoc.sh"],
)

# https://github.com/protocolbuffers/protobuf/blob/b4b0e304be5a68de3d0ee1af9b286f958750f5e4/BUILD#L773
proto_lang_toolchain(
    name = "cc_toolchain",
    command_line = "--cpp_out=$(OUT)",
    runtime = ":protoc",
    visibility = ["//visibility:public"],
)
```
<p><code>third_party/tools/protoc.sh</code></p>
```bash
#!/bin/env bash
protoc "$@"
```
<h3 id="override-protoc">Override protoc</h3>
<p>You should now be able to override <code>protoc</code> with:</p>
```bash
bazel build --proto_compiler=//third_party/tools:protoc ...
```
<p>Having to pass in a flag each time is annoying though, and you can add it to
your <code>.bazelrc</code></p>
```
build --proto_compiler=//third_party/tools:protoc
```
<h3 id="sample-code">Sample code</h3>
<p>You can find the sample code in
<a href="https://github.com/drone-ah/wordsonsand">the wordsonsand repo</a> which uses this
exact solution to override <code>protoc</code> and get the <code>pulumi</code> sample code to work ;)</p>
<p>PS: It also includes a fully migrated <code>MODULES.bazel</code> with support for <code>golang</code>.
You will also want to check out <code>BUILD</code> in the root.</p>]]></content:encoded></item><item><title>Managing Configuration</title><link>https://icle.es/2025/06/08/managing-configuration/</link><pubDate>Sun, 08 Jun 2025 21:47:05 +0000</pubDate><guid>https://icle.es/2025/06/08/managing-configuration/</guid><description>&lt;p>Before making the game available for playtesting, I wanted the player to be able
to configure the game to some degree.&lt;/p>
&lt;p>As a starting point, my keyboard layout is &lt;code>colemak&lt;/code>, and I doubt that the
controls I use would suit the majority of players.&lt;/p>
&lt;p>I am putting off a UI based config management option down the road (did I
mention that I do not enjoy GUI work?). As such I&amp;rsquo;ve been pondering alternative
configuration options.&lt;/p></description><content:encoded><![CDATA[<p>Before making the game available for playtesting, I wanted the player to be able
to configure the game to some degree.</p>
<p>As a starting point, my keyboard layout is <code>colemak</code>, and I doubt that the
controls I use would suit the majority of players.</p>
<p>I am putting off a UI based config management option down the road (did I
mention that I do not enjoy GUI work?). As such I&rsquo;ve been pondering alternative
configuration options.</p>
<h2 id="platform-independence">Platform independence</h2>
<p>Before I even get to that, the first problem I need to solve is a way to
determine the location for the config files independent of the platform.</p>
<p>Fortunately, <a href="https://github.com/ziglibs/known-folders">known-folders</a> came to
the rescue and provided an easy to use framework that can be used to determine
the various relevant locations for multiple platforms.</p>
```zig
const known_folders = @import("known-folders");
const maybe_config = try known_folders.getPath(allocator, .roaming_configuration);
if (maybe_config) |config| {
    defer allocator.free(config);
    std.debug.print("roaming config path: {s}\n", .{config});
}
```
<h2 id="locations">Locations</h2>
<p>There are three real locations of relevance for triangle</p>
<ul>
<li>The binary / package</li>
<li>Config, technically, split into two
<ul>
<li>user (or in Windows parlance, the remote dir, and can be shared across
computers)</li>
<li>system (in windows parlance, local, and is specific to that system)</li>
</ul>
</li>
<li>Save Data</li>
</ul>
<h2 id="user--game-config-files">User &amp; Game Config Files</h2>
<p>With that sorted out, the next bit is to identify the relevant config files. I
expect that triangle will continue to use these, and will eventually just get a
UI config option as well.</p>
<h3 id="user-config">User Config</h3>
<p>There are two main bits of user configuration</p>
<ul>
<li>Preference like controls</li>
<li>System details like resolution</li>
</ul>
<p>I am currently unsure when it&rsquo;ll support system config.</p>
<h3 id="game-config">Game Config</h3>
<p>There are two bits of config that the game will store. One set of config is to
remember game choices the user has made.</p>
<h4 id="remember-player-actions">Remember Player Actions</h4>
<p>For example, it will be useful to show the user details of changes to the game
since they last played. To do this, we need to track the last set of changes
that the user saw.</p>
<p>The game will show a notice on startup about its extremely early access status,
and provide an option for the user to hide that in the future. We need to save
that somewhere too.</p>
<h4 id="telemetry">Telemetry</h4>
<p>The second bit of config is metrics. While a lot of games will simply send
telemetry information directly to the developer, player privacy is really
important to me. I recognise that I will get far less data because of this, and
that there will be a bit of survivorship bias with the data - but I feel that
privacy is more important.</p>
<p>The way I want telemetry to work is that it will all be saved in a human
readable telemetry file in the config file location.</p>
<p>The data is stored only locally, and is never automatically sent. The player is
welcome to use this data for themselves if they wish and also share at their
discretion. The information will be stored in a human readable format that
should be as easy to understand as possible - no data dumps.</p>
<p>The location will also store logs (if enabled).</p>
<h2 id="setting-config">Setting Config</h2>
<p>In terms of allowing the player to manage config, there are a couple of
challenges:</p>
<ul>
<li>Providing enough documentation that it is easy to do</li>
<li>Allowing for updates, particularly to the addition of new keys</li>
</ul>
<p>To tackle this, I am going to provide an annotated template file with all the
config options. The user can create a separate file based on this with <strong>only</strong>
the config they wish to override.</p>
<p>It will be tricky to change how particular parameters are configured. E.g. If a
single value key needs to switch to an array. I&rsquo;d be loathe to sprinkle the code
with checks for legacy keys/formats. We&rsquo;ll play it by ear.</p>
<p>I considered updating the config file automatically, but this would discard any
comments the user had added. While I could offer an option to merge changes in,
it’s not straightforward enough to implement just yet.</p>
<h2 id="format">Format</h2>
<p>I&rsquo;ve been considering <code>toml</code> and <code>yaml</code> for this, with
<a href="https://github.com/sam701/zig-toml/">zig-toml</a> and
<a href="https://github.com/kubkon/zig-yaml">zig-yaml</a> respectively.</p>
<p><code>zig-yaml</code> seems to be more active (more stars, forks, issues and pr&rsquo;s and
currently also the more recent commit).</p>
<p>I am also more familiar with and prefer yaml.</p>
<p>However, it does not currently support default values. I would like the user to
have to specify only the config they&rsquo;d like to override. <code>zig-yaml</code> currently
expects all the keys to be defined if you want to parse it into a struct.</p>
<p><a href="https://github.com/kubkon/zig-yaml/issues/85">#85</a> should bring it in, but I
<a href="https://github.com/kubkon/zig-yaml/issues/92">could not get it to work</a></p>
<p>So, I tried out <code>zig-toml</code> and the test worked the first time.</p>
<p><a href="https://github.com/drone-ah/wordsonsand/tree/main/code/zig/src/toml_with_defaults.zig">src/toml_with_defaults.zig</a></p>
```zig
const std = @import("std");

const Controls = struct {
    forward: []const u8 = "w",
    craft: []const u8 = "q",
    inventory: []const u8 = "e",
};

const User = struct {
    controls: Controls = .{},
};

test "load partial toml config" {
    const toml = @import("toml");
    const allocator = std.testing.allocator;
    var parser = toml.Parser(User).init(allocator);
    defer parser.deinit();

    const source =
        \\[controls]
        \\craft = "s"
    ;
    var result = try parser.parseString(source);
    defer result.deinit();

    const config = result.value;
    const default = User{};
    try std.testing.expectEqualStrings(default.controls.forward, config.controls.forward);
    try std.testing.expectEqualStrings("s", config.controls.craft);
}
```
<h1 id="using-the-config">Using the config</h1>
<p>The final part is to <em>use</em> the config.</p>
<h2 id="loading-config">Loading config</h2>
<p>All config is loaded at startup and attached to a <code>Config</code> struct, which is in
turn part of a <code>Context</code> struct that is passed around.</p>
<p><a href="https://github.com/drone-ah/wordsonsand/tree/main/code/zig/src/load_save_config.zig">src/load_save_config.zig</a></p>
```zig
user: User,

game_path: []const u8,
game: Game,

pub fn init(allocator: std.mem.Allocator) ConfigError!Self {
    const maybe_config = known_folders.getPath(allocator, .roaming_configuration) catch {
        return ConfigError.UnableToDetermineConfigLocation;
    };
    if (maybe_config) |config| {
        defer allocator.free(config);

        // user config path
        const full_path = std.fmt.allocPrint(allocator, "{s}/triangle/user.toml", .{config}) catch {
            std.debug.panic("oom", .{});
        };
        defer allocator.free(full_path);

        // game config path
        const game_path = std.fmt.allocPrint(allocator, "{s}/triangle/game.toml", .{config}) catch {
            std.debug.panic("oom", .{});
        };

        return .{
            .user = loadConfig(allocator, User, full_path),

            .game_path = game_path,
            .game = loadConfig(allocator, Game, game_path),
        };
    }

    return ConfigError.UnableToDetermineConfigLocation;
}

fn loadConfig(allocator: std.mem.Allocator, ConfigType: type, path: []const u8) ConfigType {
    var parser = toml.Parser(ConfigType).init(allocator);
    defer parser.deinit();

    var result = parser.parseFile(path) catch {
        log.warn("unable to read config file: {s}", .{path});
        return .{};
    };
    defer result.deinit();

    return result.value;
}
```
<h2 id="user-config-controls">User config (controls)</h2>
<p>This one involves a little translation as we need to know the <code>rl.KeyboardKey</code>
for each mapping to be able to detect it.</p>
<p>I use a <code>std.StringArrayHashMapUnmanaged(rl.KeyboardKey)</code> to map the string to
each key</p>
<p><a href="https://github.com/drone-ah/wordsonsand/tree/main/code/zig/src/load_save_config.zig">src/load_save_config.zig</a></p>
```zig
const Input = struct {
    keymap: KeyMaps,

    pub fn init(allocator: std.mem.Allocator) Self {
        var keymap = KeyMaps{};
        for (default_keybindings) |entry| {
            keymap.put(allocator, entry.name, entry.key) catch {
                std.debug.panic("oom", .{});
            };
        }

        return .{
            .keymap = keymap,
        };
    }

    const KeyMap = struct {
        name: []const u8,
        key: rl.KeyboardKey,
    };

    const default_keybindings = [_]KeyMap{
        .{ .name = "a", .key = .a },
        .{ .name = "b", .key = .b },
        .{ .name = "c", .key = .c },
    };
}
```
<h2 id="game-config-1">Game Config</h2>
<p>As a starting point, we&rsquo;ll probably only have one config entry here - the last
time the news was marked as read by the player.</p>
<p>We&rsquo;ll store this as an <code>i64</code> and loading it is exactly the same as above.</p>
<p>The main difference with the <code>Game</code> config class is that on writing any value,
it will also save it to disk.</p>
```zig
pub fn markNewsAsRead(self: *Self) void {
    self.game.news_read = std.time.timestamp();
    saveConfig(self.allocator, self.game, self.game_path);
}

fn saveConfig(allocator: std.mem.Allocator, Config: anytype, full_path: []const u8) void {
    const path = std.fs.path.dirname(full_path) orelse ".";
    std.fs.cwd().makePath(path) catch |err| { // creates parent dirs if needed
        log.warn("unable to save: {any}", .{err});
        return;
    };

    var file = std.fs.cwd().createFile(full_path, .{
        .read = false,
        .truncate = true,
    }) catch |err| {
        log.warn("unable to save: {any}", .{err});
        return;
    };
    defer file.close();

    var writer = file.writer().any();
    toml.serialize(allocator, Config, &writer) catch |err| {
        log.warn("unable to write to config file: {any}", .{err});
    };
}
```
<p><del>I ran into a bug where
<a href="https://github.com/sam701/zig-toml/issues/32">the api for serialization was broken</a>.
There is (currently)
<a href="https://github.com/sam701/zig-toml/pull/33">a pending pr #33 to resolve it</a></del></p>
<p>One of the challenges of using emerging language and ecosystem is that you&rsquo;re
more likely to run into bugs. One of the great joys of working with such
ecosystem is the greater opportunity to contribute and get involved!</p>
<h1 id="links">Links</h1>
<ul>
<li><a href="https://youtu.be/OVswrFoFNjM">YouTube Devlog</a></li>
<li>Prev: <a href="https://icle.es/2025-05-20-crafting-machines.md">Crafting Machines</a></li>
</ul>
]]></content:encoded></item><item><title>SVG To TVG</title><link>https://icle.es/2025/06/07/svg-to-tvg/</link><pubDate>Sat, 07 Jun 2025 17:02:22 +0000</pubDate><guid>https://icle.es/2025/06/07/svg-to-tvg/</guid><description>&lt;p>I am using &lt;a href="https://github.com/david-vanderson/dvui">dvui&lt;/a> in a project and it
uses &lt;a href="https://tinyvg.tech/">TinyVG&lt;/a> for its icon format.&lt;/p>
&lt;p>While it sounds swell, most icons I could find were still
&lt;a href="https://en.wikipedia.org/wiki/SVG">SVG&lt;/a>.&lt;/p>
&lt;p>When I tried to use a standard SVG, I got the following errors:&lt;/p>
```
warning(dvui): iconTexture Tinyvg error error.InvalidData rendering icon craft at height 16

warning(dvui): iconWidth Tinyvg error error.InvalidData parsing icon craft
```</description><content:encoded><![CDATA[<p>I am using <a href="https://github.com/david-vanderson/dvui">dvui</a> in a project and it
uses <a href="https://tinyvg.tech/">TinyVG</a> for its icon format.</p>
<p>While it sounds swell, most icons I could find were still
<a href="https://en.wikipedia.org/wiki/SVG">SVG</a>.</p>
<p>When I tried to use a standard SVG, I got the following errors:</p>
```
warning(dvui): iconTexture Tinyvg error error.InvalidData rendering icon craft at height 16

warning(dvui): iconWidth Tinyvg error error.InvalidData parsing icon craft
```
<p>I couldn&rsquo;t find a lot of resources online on how to convert an SVG file into
TinyVG.</p>
<p>On their <a href="https://tinyvg.tech/">website, under tooling</a>, there are links with
binaries for various operating systems.</p>
<p>I am not a fan of downloading binaries, but it seemed reasonable - maybe run it
in a <a href="https://wiki.archlinux.org/title/Chroot">chroot</a> or container to be safe.</p>
<p>It needed two steps:</p>
<h2 id="convert-from-svg-to-tinyvg-text">Convert from SVG to TinyVG Text</h2>
```bash
./svg2tvgt <path-to-svg-file>
```
<p>It will put the output in the same directory as the source file</p>
<h2 id="convert-from-tinyvg-text-to-binary">Convert from TinyVG Text to Binary</h2>
```bash
./tvg-text -I tvgt -O tvg <path-to-tvgt-file>
```
<p>It will output the binary tvg file in the same directory as the source</p>
<h2 id="repo--status">Repo &amp; Status</h2>
<p>The <a href="https://github.com/TinyVG/sdk">SDK Repo</a> is using Zig 0.11 and hasn&rsquo;t had
an update in almost a year. There is a PR pending to update it to Zig 0.14,
which has been waiting a month.</p>
<p>It doesn&rsquo;t build on Zig 0.14 as it stands.</p>
<p>I worry that it might already be abandoned, but am hopeful that it will come
back to life.</p>]]></content:encoded></item><item><title>Build and push container image using bazel</title><link>https://icle.es/2025/06/06/bazel-go-docker-gcloud/</link><pubDate>Fri, 06 Jun 2025 13:06:14 +0000</pubDate><guid>https://icle.es/2025/06/06/bazel-go-docker-gcloud/</guid><description>&lt;p>I am building a small &lt;a href="https://icle.es/tags/golang">golang&lt;/a> &lt;a href="https://icle.es/tags/webapp">webapp&lt;/a>, and I want
to push a &lt;a href="https://icle.es/tags/oci">container&lt;/a> up for it, which can eventually be used in
Google Cloud Run, or elsewhere.&lt;/p>
&lt;p>In this post, I want to describe how I got it to push images build locally up to
googles artifact repository. It will include creating the artifac repository
using infrastructure as code&lt;/p>
&lt;p>The project is in a &lt;a href="https://icle.es/tags/monorepo">monorepo&lt;/a> that uses &lt;a href="https://icle.es/tags/bazel">bazel&lt;/a>.&lt;/p>
&lt;h2 id="the-executable-to-be-packaged">The executable to be packaged&lt;/h2>
```starlark
# //products/example/cmd/server/BUILD.bazel
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")

go_library(
 name = "server_lib",
 srcs = ["main.go"],
 visibility = ["//visibility:private"],
)

go_binary(
 name = "server",
 embed = [":server_lib"],
 visibility = ["//visibility:public"],
)
```</description><content:encoded><![CDATA[<p>I am building a small <a href="https://icle.es/tags/golang">golang</a> <a href="https://icle.es/tags/webapp">webapp</a>, and I want
to push a <a href="https://icle.es/tags/oci">container</a> up for it, which can eventually be used in
Google Cloud Run, or elsewhere.</p>
<p>In this post, I want to describe how I got it to push images build locally up to
googles artifact repository. It will include creating the artifac repository
using infrastructure as code</p>
<p>The project is in a <a href="https://icle.es/tags/monorepo">monorepo</a> that uses <a href="https://icle.es/tags/bazel">bazel</a>.</p>
<h2 id="the-executable-to-be-packaged">The executable to be packaged</h2>
```starlark
# //products/example/cmd/server/BUILD.bazel
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")

go_library(
    name = "server_lib",
    srcs = ["main.go"],
    visibility = ["//visibility:private"],
)

go_binary(
    name = "server",
    embed = [":server_lib"],
    visibility = ["//visibility:public"],
)
```
<h2 id="build-container-image">Build Container Image</h2>
<h3 id="add-rules_oci">add <code>rules_oci</code></h3>
<p><a href="https://github.com/bazel-contrib/rules_oci">rules_oci</a> is a good place to start
to integrate container support into your bazel configuration. It&rsquo;s also worth
<a href="https://github.com/GoogleContainerTools/distroless">reading up a bit on distroless</a>
if you are not aware of it.</p>
<p>Adding <code>rules_oci</code> into bazel <code>MODULES</code> is straightforward:</p>
```starlark
bazel_dep(name = "rules_oci", version = "2.2.6")
# For testing, we also recommend https://registry.bazel.build/modules/container_structure_test

oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")

# Declare external images you need to pull, for example:
oci.pull(
    name = "distroless_base",
    # 'latest' is not reproducible, but it's convenient.
    # During the build we print a WARNING message that includes recommended 'digest' and 'platforms'
    # values which you can use here in place of 'tag' to pin for reproducibility.
    tag = "latest",
    image = "gcr.io/distroless/base",
    platforms = ["linux/amd64"],
)

# For each oci.pull call, repeat the "name" here to expose them as dependencies.
use_repo(oci, "distroless_base")
```
<p>If you use the <code>WORKSPACE</code>, or if you want the latest version, you
<a href="https://github.com/bazel-contrib/rules_oci/releases">can find details on their releases page</a>.</p>
<h3 id="tarbzl"><code>tar.bzl</code></h3>
<p><code>oci_rules</code> uses tar files to build the image. To build tar images, you will
want to use <a href="https://github.com/bazel-contrib/tar.bzl">tar.bzl</a>.</p>
<p>Add the following to you <code>MODULES</code></p>
```starlark
bazel_dep(name = "tar.bzl", version = "0.3.0")
```
<p>Latest version and instructions for <code>WORKSPACE</code>
<a href="https://github.com/bazel-contrib/tar.bzl/releases/tag/v0.3.0">can be found on their releases page</a></p>
<h3 id="buildbazel"><code>BUILD.bazel</code></h3>
<p>There are
<a href="https://github.com/bazel-contrib/rules_oci?tab=readme-ov-file#usage">language specific sample build files</a>
that you can start from.</p>
<p>Starting
<a href="https://github.com/aspect-build/bazel-examples/blob/main/oci_go_image/BUILD.bazel">from the example go <code>BUILD.bazel</code></a>,
and simplifying it, I have:</p>
```starlark
# //products/example/deploy/BUILD.bazel
load("@rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load", "oci_push")
load("@tar.bzl", "mutate", "tar")

# Put app go_binary into a tar layer.
tar(
    name = "app_layer",
    srcs = ["//products/muster/cmd/server:server"],
    out = "app_layer.tar",
    mutate = mutate(strip_prefix = package_name() + "/app_"),
)


oci_image(
    name = "image",
    # This is defined by an oci.pull() call in /MODULE.bazel
    base = "@distroless_base",
    entrypoint = ["/app"],
    # Link the resulting image back to the repository where the build is defined.
    labels = {
        "org.opencontainers.image.source": "https://github.com/aspect-build/bazel-examples",
    },
    tars = [":app_layer"],
)
```
<p>There is
<a href="https://github.com/bazel-contrib/rules_oci/blob/main/docs/go.md">more information at the go doc page</a>
including details of migrating from
<a href="https://github.com/bazelbuild/rules_docker"><code>rules_docker</code></a></p>
```bash
products/example/deploy $ bazel build ...
INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets...
INFO: Elapsed time: 0.444s, Critical Path: 0.07s
INFO: 2 processes: 1 internal, 1 linux-sandbox.
INFO: Build completed successfully, 2 total actions
```
<p>Building of the images now completes</p>
<h2 id="push-image">Push Image</h2>
<p>Pushing the image can be pretty straightforward:</p>
<ul>
<li>Enable the container repository in <a href="https://icle.es/tags/google-cloud">Google Cloud</a></li>
<li>Configure it in the <code>BUILD.bazel</code> file</li>
<li><code>bazel run</code> to push</li>
</ul>
<p>I would like to do the whole thing through <a href="https://icle.es/tags/iac">IaC</a> so that it is</p>
<ul>
<li>Repeatable, and</li>
<li>more importantly documented</li>
</ul>
<h3 id="iac-tool-options">IaC Tool Options</h3>
<p><a href="https://developer.hashicorp.com/terraform">terraform</a> has no bazel support, and
is a questionable choice from an ethics perspective. <a href="https://icle.es/">opentofu</a> has</p>
<p><a href="https://github.com/yanndegat/rules_tf">rules_tf</a>. However, that
<a href="https://github.com/yanndegat/rules_tf/issues/5">does not support <code>apply</code></a></p>
<p><a href="https://cloud.google.com/infrastructure-manager/docs">Google Cloud Infrastucture Manager</a>
is vendor lock in.</p>
<p>That leaves us with <a href="https://www.pulumi.com/">pulumi</a> (If there is another
alternative, please let me know).</p>
<p>I like the stacks concept in pulumi, and while it doesn&rsquo;t have bazel
integration, what it does have is an
<a href="https://github.com/pulumi/automation-api-examples">automation api</a>. While not
he best documented, there is enough documentation out there to be able to piece
it together.</p>
<p>With this, we can build a runnable unit, and then run it too. I&rsquo;ve experimented
with tagging specific runnables and then running them through the CI. I won&rsquo;t
get that far in this post. My focus here is to get it working locally.</p>
<h3 id="enable-artifact-repository">Enable Artifact Repository</h3>
<p>In pulumi, you can enable the artifact repository with:</p>
```go

func enableArtifactRepository(ctx *pulumi.Context) error {
	// Set your GCP project and region
	projectID := "<projectId>"
	region := "europe-west1"
	repoName := "<repo-name>"

	// Create Artifact Registry repository
	repo, err := artifactregistry.NewRepository(ctx, repoName, &artifactregistry.RepositoryArgs{
		Format:       pulumi.String("DOCKER"),
		Location:     pulumi.String(region),
		RepositoryId: pulumi.String(repoName),
		Description:  pulumi.String("Repository for OCI container images"),
		Project:      pulumi.String(projectID),
	})
	if err != nil {
		return err
	}

	// Export the repository URL (used for pushes)
	ctx.Export("repositoryURL", pulumi.Sprintf("%s-docker.pkg.dev/%s/%s", region, projectID, repo.Name))

	return nil

}
```
<h3 id="authenticating-against-the-repo">Authenticating against the repo</h3>
<p>You need to authenticate against the repository so that the <code>oci_rule</code> can pick
it up</p>
```bash
gcloud auth configure-docker europe-west1-docker.pkg.dev
```
<h3 id="define-the-push-target">Define the push target</h3>
```starlark
oci_push(
    name = "push",
    image = ":image",
    remote_tags = [
        "latest",
        "1h",
    ],
    repository = "europe-west1-docker.pkg.dev/<projectId>/<repo-name>/<image-name>",
)
```
<h2 id="pushing">Pushing</h2>
<p>Once all the above is set up, you can then push the image with one of the
following:</p>
```bash
bazel run :push # if you in the directory
bazel run //<target/path>:push # fron anywhere in the repo
```
<h2 id="side-note">Side note</h2>
<p>Please do not take my heavy usage of google and its products to be a fan letter
or an endorsement. They <em>might</em> at best, be the lesser evil.</p>]]></content:encoded></item><item><title>Crafting, Mods, and Machines</title><link>https://icle.es/2025/05/20/crafting-machines/</link><pubDate>Tue, 20 May 2025 14:27:00 +0000</pubDate><guid>https://icle.es/2025/05/20/crafting-machines/</guid><description>&lt;p>In this post, I want to explore the crafting system in triangle, especially how
mods, tiers, and machine interactions could create depth without overwhelming
complexity.&lt;/p>
&lt;p>Crafting in triangle should be a mix of random number generation (rng) and
deterministic. Crafted items will have a random set of mods. However, like in
&lt;a href="https://lastepoch.com/">Last Epoch&lt;/a>, you can extract the mods of the items.
These can then be placed into another item.&lt;/p>
&lt;p>It feels like mod crafting would be an interesting way to approach crafting in
general. Unlike in the Last Epoch, the extracted mods aren&amp;rsquo;t just combined back
together.&lt;/p></description><content:encoded><![CDATA[<p>In this post, I want to explore the crafting system in triangle, especially how
mods, tiers, and machine interactions could create depth without overwhelming
complexity.</p>
<p>Crafting in triangle should be a mix of random number generation (rng) and
deterministic. Crafted items will have a random set of mods. However, like in
<a href="https://lastepoch.com/">Last Epoch</a>, you can extract the mods of the items.
These can then be placed into another item.</p>
<p>It feels like mod crafting would be an interesting way to approach crafting in
general. Unlike in the Last Epoch, the extracted mods aren&rsquo;t just combined back
together.</p>
<p>What if each mod retains its values? What if you could combine two mods together
to upgrade or reroll them?</p>
<p>When you insert a mod into an item, what if it destroyed the mod that you were
replacing?</p>
<h2 id="tiers">Tiers</h2>
<p>All materials, items, and mods have tiers. Factories will only process items of
their tier or lower. A Tier 1 smelter cannot process a Tier 2 material and a
Tier 1 constructor cannot craft a Tier 2 item.</p>
<p>I am currently mulling over the idea of having nine tiers.</p>
<h2 id="items-aka-buildings-machines">Items (a.k.a Buildings? Machines?)</h2>
<p>I&rsquo;m toying with the idea of the tier determining how many mod slots it could
have. A Tier 2 item would have two mod slots and a Tier 5 item would have 5
slots.</p>
<p>This provides a natural power curve, even if the mods themselves do not have
tiers.</p>
<p>All items will also have up to one implicit mod. This mod will be item type
specific. e.g., for smelters, it will be <code>smelting rate</code></p>
<p>From a concept perspective, there are probably buildings, or some kind of
structure that can be slotted on to the ship.</p>
<p>Perhaps buildings are a better name for them than items.</p>
<h3 id="slots--hardpoints">Slots / Hardpoints</h3>
<p>I see two kinds of slots on the ship</p>
<ul>
<li><em>Interior slot</em>: Machines like the smelters will go in here</li>
<li><em>Exterior Slot</em>: Weapons, Armour, Hull Extensions etc. could go on to these.</li>
</ul>
<h2 id="mods">Mods</h2>
<p>Currently mods also support tiers, but I am wondering whether the power creep
could become exponential, combined with the additional mod slots. There may be
good ways to mitigate it though, which will only become apparent once some of
these mechanics have been implemented.</p>
<p>Another reason to skip tiers on mods is if that means that the player ends up
with far too many mods in their inventory.</p>
<p>There are a couple of key areas in which I am thinking about deviating from
other games (like the Diablo series, PoE 1/2, Last Epoch etc.)</p>
<h3 id="local-only">Local only</h3>
<p>Each mod applies only to the item it is on. A firing rate increase on your
smelters won&rsquo;t do anything - since they don&rsquo;t fire. A smelting speed increase on
your weapon will likewise have no effect. It would make sense to extract or
replace the ineffective mods.</p>
<p>If you have two weapons attached, and one of them has a mod that increases
firing rate, that will impact only that weapon.</p>
<h3 id="no-prefix--suffix-mods">No Prefix / Suffix mods</h3>
<p>There is no classification of mods as prefix/suffix or limits of each type. You
can have full attack mods on if you like. Considering the previous restriction,
it makes more sense to stack attack on weapons and defense on armour.</p>
<h2 id="machines">Machines</h2>
<p>There are a few factory based machines I have in mind so far.</p>
<p>All these machines will have a rate at which they will complete their work. I
have been pondering whether higher tier machines will complete lower tier work
faster.</p>
<h2 id="smelters">Smelters</h2>
<p>The standard refinery. It will convert ore and scrap (obtained when enemy ships
are destroyed, and from scrapping items) into refined materials.</p>
<h2 id="constructor">Constructor</h2>
<p>Converts refined materials into items of Tier 1. For Tier 2 onwards, I am
considering taking items from the previous tier plus a refined material of the
new tier. E.g. to craft a Tier 2 factory, it could take 2 x Factory Mk. I + 30
Tier 2 materials (whatever that turns out to be)</p>
<p>This could keep the recipes easy to understand and reason about. It also
provides one use for poorly rolled items.</p>
<h2 id="disassemblers">Disassemblers</h2>
<p>These function similar to the Foundry in Last Epoch, at least with regards to
extracting all the mods from the item.</p>
<p>I haven&rsquo;t decided how deterministic it will be. Will it extract all items or
could some be damaged in the process and maybe turned to scrap?</p>
<h2 id="scrappers">Scrappers</h2>
<p>These are a bit like the trash bin. If crafting requires lower tier items, these
might not be necessary. TBD!</p>
<h2 id="foundry">Foundry</h2>
<p>This would be the machine that could do mod crafting. I am still a little foggy
about how or if this machine could work.</p>
<p>Should mod crafting involve risk? Like combining two mods to maybe create a
higher tier one - or failing and getting scrap?</p>
<p>What about rerolling a mod by sacrificing another mod?</p>
<p>All ideas accepted :)</p>
<h2 id="augmentor">Augmentor</h2>
<p>The Augmentor could be the final step in the pipeline - letting you insert or
fine-tune mods once you’ve refined or crafted them.</p>
<p>This machine will work instantly so that it doesn&rsquo;t tie up the items currently
in use. It would allow you to modify items up to its own tier.</p>
<h2 id="triangle">triangle</h2>
<p>With machines slotted directly onto the ship, triangle becomes a living,
drifting factory - salvaging, upgrading, refining, evolving, fighting, and
surviving!</p>
<h2 id="other-posts">Other posts</h2>
<ul>
<li><a href="https://youtu.be/livphL9lOxo">Companion vlog for this post</a></li>
<li><a href="https://icle.es/2025-05-13-materials.md">Prev: Materials &amp; Pickups </a></li>
<li>Next: Coming Soon</li>
</ul>
]]></content:encoded></item><item><title>Journey or Destination</title><link>https://icle.es/2025/05/16/journey-or-destination/</link><pubDate>Fri, 16 May 2025 09:31:08 +0000</pubDate><guid>https://icle.es/2025/05/16/journey-or-destination/</guid><description>&lt;p>Everybody in India was seemingly learning to be a software engineer in the
2000&amp;rsquo;s. They were super expensive, made a lot of money and commanded respect.
Then the recession hit. Now, software engineers were a dime a dozen and begging
for jobs.&lt;/p>
&lt;p>Ring a bell?&lt;/p>
&lt;p>I remember having mixed feelings when coding bootcamps were springing up
everywhere. People were paying thousands to go on a six week bootcamp to learn
how to code. I was happy that coding was becoming accessible, but I was not
happy that people were picking this career path purely as a way of making money.&lt;/p></description><content:encoded><![CDATA[<p>Everybody in India was seemingly learning to be a software engineer in the
2000&rsquo;s. They were super expensive, made a lot of money and commanded respect.
Then the recession hit. Now, software engineers were a dime a dozen and begging
for jobs.</p>
<p>Ring a bell?</p>
<p>I remember having mixed feelings when coding bootcamps were springing up
everywhere. People were paying thousands to go on a six week bootcamp to learn
how to code. I was happy that coding was becoming accessible, but I was not
happy that people were picking this career path purely as a way of making money.</p>
<p>The same thing had happened in India - the popularity of software engineering
had stemmed from the massive amounts of outsourcing that was happening. It was a
lucrative career option. There was no love for the job, no curiosity about
learning. It was a job.</p>
<p>Trying to hire a software engineer in 2021/2022 was an absolute nightmare.
Salaries were skyrocketing and engineers were rare. Good engineers were rarer.
The bootcamps were going hell for leather at this time, and then the layoffs
hit. Tens of thousands of jobs lost, along with all the new graduates who
entered the market. On top of that, add the promise of A.I being able to write
code. It can probably write code as well as some of the bootcamp graduates.</p>
<p>There was a time that the majority of software was written by people who loved
the work. It was treated more as a craft and we were all figuring out how to do
it better. This craft then got commercialised, industrialised.</p>
<p>The focus is now on productivity, and how quickly we can get code out. The love
of the work has been lost. Let A.I have that joyless (thankless) job! I (We?)
don&rsquo;t want it.</p>
<p>I&rsquo;d rather be the last carpenter who still enjoys the job! What about you?</p>
]]></content:encoded></item><item><title>Materials</title><link>https://icle.es/2025/05/13/materials/</link><pubDate>Tue, 13 May 2025 12:07:00 +0000</pubDate><guid>https://icle.es/2025/05/13/materials/</guid><description>&lt;p>In triangle, everything should feel earned - so all items are crafted, not
found. I am also a big fan of factory / automation games. How much of a factory
sim can we squeeze into an ARPG (or on to a triangle for that matter)? Let&amp;rsquo;s
find out!&lt;/p>
&lt;h2 id="drops">Drops&lt;/h2>
&lt;p>As a starting point, when asteroids are destroyed, they will drop materials.
I&amp;rsquo;ve also been pondering whether materials could drop when they split. It makes
logical sense, but I want to get a better sense of pacing before diving into
that question.&lt;/p></description><content:encoded><![CDATA[<p>In triangle, everything should feel earned - so all items are crafted, not
found. I am also a big fan of factory / automation games. How much of a factory
sim can we squeeze into an ARPG (or on to a triangle for that matter)? Let&rsquo;s
find out!</p>
<h2 id="drops">Drops</h2>
<p>As a starting point, when asteroids are destroyed, they will drop materials.
I&rsquo;ve also been pondering whether materials could drop when they split. It makes
logical sense, but I want to get a better sense of pacing before diving into
that question.</p>
<p>Currently, only iron drops. I started off by finding a sprite sheet with
reasonable looking sprites and then started integrating them in before I decided
to just stick with shapes — so iron drops are little blue circles.</p>
```zig
var shape = Shape.initCircle(7);
shape.move(pos);
shape.colour = .blue;
```
<p>PS: you haven&rsquo;t seen the <code>Shape</code> code, but you get the gist.</p>
<p>These drops are managed by a <code>MaterialField</code> that keeps track of all the dropped
materials. They are given a low linear velocity, and no rotational velocity
(it&rsquo;s a circle, so you wouldn&rsquo;t see it anyway).</p>
<p>They do not interact with collisions to avoid them being bumped off the screen
or you having to chase them down.</p>
<p>At some point, they could also be optimised to only update if they are on
screen. Assuming the player will pick up most of them, this may not be
necessary.</p>
<h2 id="pickup">Pickup</h2>
<p>I also &ldquo;installed an attractor&rdquo; on the ship such that if it gets close enough to
a material, it&rsquo;ll pull it in. When it&rsquo;s close enough, it&rsquo;ll be &ldquo;picked up.&rdquo;</p>
```zig
pub fn pickup(self: *Ship, material: *Material.Drop) bool {
    const magnet_sq = self.magnet_range * self.magnet_range;
    const pickup_sq = self.pickup_range * self.pickup_range;

    const dist = self.pos().sub(material.body.pos());
    const dist_sq = dist.magSquared();

    if (dist_sq <= pickup_sq) {
        self.save.inventory.addMatDrop(material);
        return true;
    }

    if (dist_sq <= magnet_sq) {
        // pull it towards us
        material.body.applyForce(dist.scale(self.magnet_force).sub(material.body.lvel));
    }

    return false;
}
```
<p>It is a physics interaction, so it is possible to slingshot the material and
have it fly off the screen (it&rsquo;s happened to me). It feels like a sun
interaction and you are in control of it, so I am inclined to leave that in.</p>
<p>Once it&rsquo;s close enough, it&rsquo;s picked up, and added to an inventory.</p>
<p>The attractor also feels like an upgrade that can be crafted later.</p>
<h2 id="smelting">Smelting</h2>
<h3 id="crafting-panel">Crafting Panel</h3>
<p>For smelting, I started by sketching out a straightforward UI. I&rsquo;ll admit up
front that I don&rsquo;t enjoy UI work, but in earnest I started on it, and I got as
far as displaying the panel itself and I got tremendously bored.</p>
<p>
  <figure>
    <img src="/assets/2025/05/craft-ui-sketch.png" alt="Crafting Panel Sketch" class="figcaption-img">
    <figcaption>Crafting Panel Sketch</figcaption>
  </figure>

</p>
<p>I decided to make material refinement prioritisation automatic, at least for the
time being. It would always prioritise the most valuable material that is in the
inventory.</p>
<p>Actually, that&rsquo;ll remove some factory micromanagement without removing the
feeling of agency from the player.</p>
<p>It&rsquo;ll mainly be a problem if the player has run out of lower tier materials and
they have a large amount of higher tier items in the inventory. I figure let&rsquo;s
wait until we have some playtesting before we worry about it. The system
currently only drops one material and it can be refined automatically.</p>
<h3 id="refining">Refining</h3>
<p>All materials and items will have a tier, starting with iron at 1. To be able to
smelt a material, you&rsquo;ll need a smelter at a minimum of that tier. For example
to craft iron, you need a minimum tier 1 smelter (which is fine since 1 is the
minimum tier anyway). If you wanted to smelt a tier 3 material, you&rsquo;ll need a
minimum of tier 3 smelter.</p>
<p>The ship starts with a Tier 1 smelter and will always have a tier 1 smelter.
This smelter cannot be modified, removed, or destroyed. The same is true of some
other factories, which will be covered in the next post.</p>
<p>The first iteration of refining took the total capacity of all smelters at each
tier, and would convert a portion of the materials from <code>ore</code> to <code>ingot</code>. It
used floating points to track progress. If it would take four seconds to convert
one ore to ingot, over one second, it&rsquo;d transfer 0.25 from ore to ingot.</p>
<p>The display only showed integers, so there was a bit of <code>floor</code> and <code>ceil</code> to be
able to show consistent numbers.</p>
<p>I didn&rsquo;t like this way of doing things. It felt icky — hacky — but it was simple
enough and it works, for now.</p>
<h2 id="other-posts">Other posts</h2>
<ul>
<li><a href="https://youtu.be/8ct9aWNj3Zk">Companion vlog for this post</a></li>
<li><a href="https://icle.es/2025-05-10-asteroid-field.md">Prev: Procedural Asteroid Fields</a></li>
<li>Next:
<a href="https://icle.es/2025-05-20-crafting-machines.md">Refineries, Constructors and other other factory types in a world without conveyor belts.</a></li>
</ul>
]]></content:encoded></item><item><title>Procedural Asteroid Field Generation</title><link>https://icle.es/2025/05/10/asteroid-field/</link><pubDate>Sat, 10 May 2025 10:07:08 +0000</pubDate><guid>https://icle.es/2025/05/10/asteroid-field/</guid><description>&lt;p>In this post, I am going to cover procedural asteroid field generation. At a
high level, I wanted:&lt;/p>
&lt;ul>
&lt;li>An asteroid field that feels infinite&lt;/li>
&lt;li>Natural-looking distribution and density&lt;/li>
&lt;li>A safe starting area for the player&lt;/li>
&lt;/ul>
&lt;h2 id="spawning-asteroids">Spawning Asteroids&lt;/h2>
&lt;p>I explored strategies for spawning multiple asteroids on screen without overlaps
and avoiding the player’s starting zone. Initially, I considered a brute-force
collision check for each spawn candidate, but quickly realized it wouldn’t scale
well with many asteroids.&lt;/p>
&lt;p>I decided to split the screen into a grid. If the maximum radius of an asteroid
is &lt;code>r&lt;/code>, then each grid would be &lt;code>2r x 2r&lt;/code>, and would be able to accommodate
asteroids at their full size. The asteroid size is determined randomly, with a
minimum size. It also offsets the center of the x by a random amount, up to a
maximum of &lt;code>r&lt;/code>.&lt;/p></description><content:encoded><![CDATA[<p>In this post, I am going to cover procedural asteroid field generation. At a
high level, I wanted:</p>
<ul>
<li>An asteroid field that feels infinite</li>
<li>Natural-looking distribution and density</li>
<li>A safe starting area for the player</li>
</ul>
<h2 id="spawning-asteroids">Spawning Asteroids</h2>
<p>I explored strategies for spawning multiple asteroids on screen without overlaps
and avoiding the player’s starting zone. Initially, I considered a brute-force
collision check for each spawn candidate, but quickly realized it wouldn’t scale
well with many asteroids.</p>
<p>I decided to split the screen into a grid. If the maximum radius of an asteroid
is <code>r</code>, then each grid would be <code>2r x 2r</code>, and would be able to accommodate
asteroids at their full size. The asteroid size is determined randomly, with a
minimum size. It also offsets the center of the x by a random amount, up to a
maximum of <code>r</code>.</p>
<p>
  <figure>
    <img src="/assets/2025/05/asteroid-map.png" alt="The grid visualised" class="figcaption-img">
    <figcaption>The asteroid grid</figcaption>
  </figure>

</p>
<p>In theory, this means that if two adjacent cells spawn the full size asteroid
and offset in the &ldquo;wrong&rdquo; direction, they could start off overlapping. I&rsquo;ve not
seen this happen yet, likely because the full size asteroids are not that
common.</p>
<p>The grid has an additional offset in the x axis for each row. With this offset,
even if every asteroid was placed at the x center of each grid, they would not
look uniform.</p>
<p>Finally, for each grid cell, there is some further randomisation that determines
whether the asteroid will get spawned at all.</p>
<p>In hindsight - there might be more randomisation than we need. It works well
though.</p>
<p>The asteroids are also given a low starting velocity, both linearly and
rotationally.</p>
<p>Even if asteroids do overlap on spawn, the physics will correct it through the
collision detection.</p>
<h2 id="spawning-the-ship">Spawning the Ship</h2>
<p>The code for this part is a little icky right now. It checks the y position of
the ship and avoids spawning asteroids in any grid cell that overlaps the
starting location.</p>
<p>Of course, this should only matter for the asteroids spawned on the starting
screen. The rest of the asteroids don&rsquo;t need this check. It works just fine
though - for now :)</p>
<h2 id="making-it-infinite">Making it &ldquo;Infinite&rdquo;</h2>
<p>I pondered the best way to make it feel infinite without risk of slowdowns and
issues. A quadtree was an obvious answer. However, it felt too complicated since
I&rsquo;d never written one before, and I was worried about how I would move each
asteroid between each section as it moved. (Spoiler alert: I should have just
used quadtrees, and intend to move to it later.)</p>
<p>In the meantime, I wanted to build something which felt simpler, which was
basically to create chunks of asteroids, with each chunk being a little larger
than the size of the screen.</p>
<p>The system could then, based on the current y position, track just the current,
previous, and next chunks, and process only these asteroids.</p>
<p>When I got it working, it was pretty ok. The main problem I had now was the
asteroids leaving the screen and never coming back.</p>
<p>After I&rsquo;d implemented this, I ended up watching
<a href="https://www.youtube.com/watch?v=OJxEcs0w_kE">Coding Challenge #98: Quadtree</a>,
and boy did I feel silly about being scared about quadtrees. They were promptly
demystified and felt like a much simpler solution than what I have here - so
I&rsquo;ll add that to the list to change.</p>
<h2 id="asteroids-drifting-offscreen">Asteroids Drifting Offscreen</h2>
<p>One of the problems was that the asteroids would drift offscreen. Because I
wasn&rsquo;t updating all the asteroids, they&rsquo;d never come back on to the screen. In
fact, even if I updated <em>all</em> the asteroids, the screen would eventually clear.
I believe this was because there is a lot of offscreen space (mainly on the left
and right of the screen) that the asteroids could fly off to.</p>
<p>I tried various ways of wrapping the asteroids around after they hit a few
hundred pixels past the edge. I didn&rsquo;t want it to look like a wraparound, and
pretending like the screen was about twice the width helped.</p>
<p>However, the problem was now with the asteroids slinking out the bottom of the
screen (and the top too), and there wasn&rsquo;t an easy way to tackle those.</p>
<p>In the end, I ended up
<a href="https://www.youtube.com/watch?v=OAcXnzRNiCY">using an attractor</a>, which I
placed on the center of each chunk.</p>
<p>This worked surprisingly well that I didn&rsquo;t even need the horizontal wraparound.</p>
<p>I had also ended up making the <code>Chunk</code> struct a little heavy, and I ended up
refactoring it. While refactoring, I overoptimized it to only update asteroids
on the screen. This &ldquo;fix&rdquo; meant that asteroids off-screen aren&rsquo;t attracted to
the center of the chunk anymore, and the screen ends up clearing again.
Something to fix later - maybe when I implement the quadtree.</p>
<h2 id="debug-panel">Debug Panel</h2>
<p>There were various points when I was trying to track down issues that I reverted
to my habit of printing stuff out - but that was a nightmare because it was
printing the same thing in each frame.</p>
<p>Eventually, I got into the habit of displaying stuff on screen and drawing
different colours etc. I then wanted a way to see things easier and toggle some
of the debug features. To do this, I needed a GUI.</p>
<p>I looked at <a href="https://github.com/raysan5/raygui">raygui</a>, which looked good but
probably too basic for me. I don&rsquo;t enjoy GUI work and I figured something a
little more fleshed out would help.</p>
<p>I had already looked at <a href="https://github.com/david-vanderson/dvui">dvui</a> before
and it looked like it could be a good candidate.</p>
<p>Integrating it into <em>triangle</em> was pretty straightforward - at least once I
figured out how to add it to <code>build.zig</code>.</p>
<h2 id="next-steps">Next Steps</h2>
<p>Right now, the field feels big enough (around 15 chunks), but not truly
infinite. I&rsquo;m thinking of implementing a circular buffer - say, 8 or 10 chunks -
where old ones are recycled as the player moves forward. If a player backtracks,
most asteroids will have drifted off anyway, and replacements will blend in just
fine.</p>
<p>I’ll probably tackle that - and maybe finally add a quadtree - soon.</p>
<p><strong>Next up:</strong>
<a href="https://icle.es/2025-05-20-crafting-machines.md">crafting systems and how I’m handling material drops and recipes.</a></p>
<h2 id="other-posts">Other posts</h2>
<ul>
<li><a href="https://youtu.be/RXcBDC8Ki1w">Companion vlog for this post</a></li>
<li><a href="https://icle.es/2025-05-08-basic-gameplay.md">Basic Gameplay</a></li>
<li><a href="https://icle.es/2025-05-13-materials.md">Next: Materials &amp; Pickups</a></li>
</ul>]]></content:encoded></item><item><title>Basic Gameplay</title><link>https://icle.es/2025/05/08/basic-gameplay/</link><pubDate>Thu, 08 May 2025 10:07:08 +0000</pubDate><guid>https://icle.es/2025/05/08/basic-gameplay/</guid><description>&lt;p>The first goal was to get it working as far as the coding challenge itself.&lt;/p>
&lt;h2 id="the-ship">The Ship&lt;/h2>
&lt;p>Spawning the ship itself was relatively straightforward. I only needed three
points and &lt;code>drawTriangle&lt;/code> from &lt;code>raylib&lt;/code>.&lt;/p>
&lt;p>In the coding challenge, the ship was rotated with the keyboard, but I wanted
the ship to point to the mouse so that aiming was straightforward. Rotating to
the mouse was trickier — it involved &lt;code>atan2&lt;/code> and ChatGPT got me started.&lt;/p>
&lt;p>The coding challenge video did not worry about time lapsed between each frame,
but I wanted triangle to be framerate independent. That involved a bit of
jiggery pokery to get working, including determining how much the ship can move
within a time frame.&lt;/p>
&lt;p>Moving the ship was a bit easier, but based on the many videos of the Coding
Train that implemented force, velocity and dampening, it was pretty
straightforward. Dampening took a bit of trial and error. ChatGPT sent me down
the garden path initially with an over-complicated formula, but I was able to
simplify it later.&lt;/p>
&lt;p>Integrating the physics for linear momentum with the one for rotational momentum
was also quite nice — it meant that the ship could overshoot the aim when
rotating and will correct back.&lt;/p></description><content:encoded><![CDATA[<p>The first goal was to get it working as far as the coding challenge itself.</p>
<h2 id="the-ship">The Ship</h2>
<p>Spawning the ship itself was relatively straightforward. I only needed three
points and <code>drawTriangle</code> from <code>raylib</code>.</p>
<p>In the coding challenge, the ship was rotated with the keyboard, but I wanted
the ship to point to the mouse so that aiming was straightforward. Rotating to
the mouse was trickier — it involved <code>atan2</code> and ChatGPT got me started.</p>
<p>The coding challenge video did not worry about time lapsed between each frame,
but I wanted triangle to be framerate independent. That involved a bit of
jiggery pokery to get working, including determining how much the ship can move
within a time frame.</p>
<p>Moving the ship was a bit easier, but based on the many videos of the Coding
Train that implemented force, velocity and dampening, it was pretty
straightforward. Dampening took a bit of trial and error. ChatGPT sent me down
the garden path initially with an over-complicated formula, but I was able to
simplify it later.</p>
<p>Integrating the physics for linear momentum with the one for rotational momentum
was also quite nice — it meant that the ship could overshoot the aim when
rotating and will correct back.</p>
<h2 id="the-asteroids">The Asteroids</h2>
<p>The coding challenge worked with a fixed number of on screen asteroids and they
wrapped around. I needed to expand this to:</p>
<ul>
<li>A potentially infinite vertical scroller.</li>
<li>Collisions between the asteroids (currently the same as from the challenge,
only checks full circle collision)</li>
<li>Figure out how to handle asteroids moving out of the screen</li>
</ul>
<p>The above will be covered in a bit more depth in the
<a href="https://icle.es/2025-05-10-asteroid-field.md">next devlog</a>.</p>
<h2 id="the-camera">The Camera</h2>
<p>I also needed a follow camera. Unlike the original asteroids, the ship can move
up/down and the camera should follow it.</p>
<p>The current code looks something like this:</p>
```zig
/// ship_y: ship's current y position
/// ship_vy: ship's y velocity
/// dt: time elapsed since last update
/// margin: the zone out of which camera is moved
/// speed: maximum camera movement speed
fn updateCameraY(self: *Self, ship_y: f32, ship_vy: f32, dt: f32, margin: f32, speed: f32) void {
    const cam_y = self.camera.target.y;
    const inner_margin = margin * 0.8;

    // Predict future ship position
    // TODO: at high speeds, when the clamp goes off, the camera does a little shuffle
    // It would be nice to fix that
    const screen_height: f32 = @floatFromInt(self.screen.height);
    const predicted_y = ship_y + std.math.clamp(ship_vy * 30.0, -screen_height, screen_height);

    var target_y = self.camera.target.y;
    var target_clr: rl.Color = .red;

    const tmargin = cam_y - inner_margin;
    const bmargin = cam_y + inner_margin;
    if (predicted_y < tmargin) {
        target_y = predicted_y + margin;
        target_clr = .blue;
    } else if (predicted_y > bmargin) {
        target_y = predicted_y - margin;
        target_clr = .green;
    }

    if (target_y > screen_height / 2) target_y = screen_height / 2;
    const t = std.math.clamp(dt * speed, 0.0, 1.0);
    self.camera.target.y = lerp(cam_y, target_y, t);
}
```
<p>It predicts the position of the ship, based on its y velocity. If that&rsquo;s outside
the middle zone, it&rsquo;ll move the camera at up to the maximum speed. There is some
clamping to prevent things flying off at high speeds. It also prevents the
camera from overtaking the ship.</p>
<p>This code is mostly hacked together with some help from ChatGPT. It&rsquo;ll be
cleaned up later, once I have a better indication of possible ship speeds, and
how/if we&rsquo;ll zoom in and out as well.</p>
<p>Another important facet to consider with the camera was what to do with the
edges. Out of the four edges, only one is infinite.</p>
<p>The current solution is that the camera stops tracking when it gets to the
&ldquo;bottom.&rdquo; It also does not move to the left or right. The ship, on the other
hand is free to move out of the range of the camera. Currently, nothing changes
except that the camera does not follow.</p>
<p>Since the thrust works in the direction of the mouse, it&rsquo;s pretty easy to bring
the ship back on to the screen. This feels like the world is big and still out
there, we just don&rsquo;t track what happens out there.</p>
<p>I considered wrapping around on the left and right, but that felt more like the
ship was trapped in that zone. I want the feeling of
<a href="https://icle.es/2025-04-26-a-lonely-triangle.md#story">being trapped in vengeance</a> to be more
subtle ;)</p>
<h2 id="combat">Combat</h2>
<p>Combat is pretty much a mirror image of what happens in the coding challenge,
though I didn&rsquo;t have some of the helper functions. I learned some math :)</p>
<p>Initially, the update loop only handled asteroid collisions. I added bullets as
a separate field in the update loop. It then checks each bullet with every
asteroid in the active chunks (covered in
<a href="https://icle.es/2025-05-10-asteroid-field.md">devlog #2</a>).</p>
<p>If there is collision, the asteroid is split into two, moved apart a bit, and
given opposite linear momentum. The bullet also takes &ldquo;damage&rdquo; at this point,
and can be removed. The damage system is designed to support penetration, which
is currently not active.</p>
<p>If the spawned asteroids would be too small, nothing is spawned. Later on, this
would be the trigger to spawn a material drop.</p>
<h2 id="zig--raylib">Zig / Raylib</h2>
<p>Learning <a href="https://ziglang.org">zig</a> and <a href="https://www.raylib.com/">raylib</a> was a
big part of this. Fortunately, both were a lot of fun to learn and work with.</p>
<p>One thing I found annoying was that the vector functionality in raylib was
scattered around as individual functions instead of on the vector struct. While
this was understandable, with raylib being written in C, I found it a bit
frustrating.</p>
<p>I ended up writing my own Vector struct in zig and the functions that I used as
methods on that struct. It was an opportunity for me to learn some vector math
as well.</p>
<p>I also encapsulated raylib inside a <code>Canvas</code> struct. I probably still have some
<code>rl.</code> calls other places in the code, but the idea is that a canvas is passed
into any bits of code that needs it. The main help is that it&rsquo;ll convert our
version of <code>Vector2</code> to the one that raylib wants.</p>
<p>I am also thinking about how I want input handling to work. I would like to
encapsulate that into a separate struct. Right now, I mainly access raylib
directly.</p>
<h2 id="manual-testing">Manual testing</h2>
<p>While I am a big proponent and fan of Test Driven Development, I was happy
enough with manual testing for triangle. I found joy in seeing it work each time
I manage to get something working.</p>
<p>I do end up writing tests later, particularly when I got to bits of code that
was harder to test manually.</p>
<h2 id="feel">Feel</h2>
<p>triangle already feels fun and light. There are some clunky elements to be
ironed out, but so far, it feels good :)</p>
<h2 id="other-posts">Other posts</h2>
<ul>
<li><a href="https://youtu.be/F2ITT2-uKso">Companion vlog for this post</a></li>
<li><a href="https://icle.es/2025-04-26-a-lonely-triangle.md">Prev: A lone triangle vs the universe</a></li>
<li><a href="https://icle.es/2025-05-10-asteroid-field.md">Next: Procedural Asteroid Field</a></li>
</ul>]]></content:encoded></item><item><title>Going fast vs Going far</title><link>https://icle.es/2025/05/01/going-fast-or-far/</link><pubDate>Thu, 01 May 2025 13:17:57 +0000</pubDate><guid>https://icle.es/2025/05/01/going-fast-or-far/</guid><description>&lt;p>Aesop wrote us wonderful and valuable fables. Almost all of us know the one
about the tortoise and the hare - that slow and steady wins the race.&lt;/p>
&lt;p>There is a quote by Mario Andretti:&lt;/p>
&lt;blockquote>
&lt;p>If everything seems under control, you&amp;rsquo;re not going fast enough.&lt;/p>&lt;/blockquote>
&lt;p>This one clearly embodies the hare, but is it a good attitude to have in all
work, or in general life?&lt;/p>
&lt;p>We all seem to place so much emphasis on speed, it seems like we are trying race
through life. Aren&amp;rsquo;t we all going to the same place?&lt;/p></description><content:encoded><![CDATA[<p>Aesop wrote us wonderful and valuable fables. Almost all of us know the one
about the tortoise and the hare - that slow and steady wins the race.</p>
<p>There is a quote by Mario Andretti:</p>
<blockquote>
<p>If everything seems under control, you&rsquo;re not going fast enough.</p></blockquote>
<p>This one clearly embodies the hare, but is it a good attitude to have in all
work, or in general life?</p>
<p>We all seem to place so much emphasis on speed, it seems like we are trying race
through life. Aren&rsquo;t we all going to the same place?</p>
<p>I think the quote (of unclear origin)</p>
<blockquote>
<p>If you want to go fast, go alone; if you want to go far, go together?</p></blockquote>
<p>is a good comparison of our options, and while it might be nice(r) to go far, I
think there is a lot more value in traveling together.</p>
<p>We are born alone, and we die alone. We do not have choice in those, but why do
so many of us go alone, just to go fast?</p>
]]></content:encoded></item><item><title>init &amp; deinit in Zig</title><link>https://icle.es/2025/04/27/init-deinit-in-zig/</link><pubDate>Sun, 27 Apr 2025 17:01:57 +0100</pubDate><guid>https://icle.es/2025/04/27/init-deinit-in-zig/</guid><description>&lt;p>When I was ten, my grandmother passed away. As was custom where we lived, my
family moved into her house - a full household with uncles and aunts. I stayed
there for about nine months.&lt;/p>
&lt;p>My youngest uncle was a Computing Studies teacher at a local college. He also
taught private classes at home. I couldn’t get enough.&lt;/p>
&lt;p>I don’t know what most ten-year-olds dream about, but my dream was to learn C. I
saved ₹300 - a lot of money in 1994 - to buy a book called &lt;em>Encyclopedia C&lt;/em>. I
read it cover to cover, understood maybe 10% of it, and reread it years later to
pick up more.&lt;/p>
&lt;p>But I never actually programmed in C.&lt;/p></description><content:encoded><![CDATA[<p>When I was ten, my grandmother passed away. As was custom where we lived, my
family moved into her house - a full household with uncles and aunts. I stayed
there for about nine months.</p>
<p>My youngest uncle was a Computing Studies teacher at a local college. He also
taught private classes at home. I couldn’t get enough.</p>
<p>I don’t know what most ten-year-olds dream about, but my dream was to learn C. I
saved ₹300 - a lot of money in 1994 - to buy a book called <em>Encyclopedia C</em>. I
read it cover to cover, understood maybe 10% of it, and reread it years later to
pick up more.</p>
<p>But I never actually programmed in C.</p>
<h2 id="the-long-detour">The Long Detour</h2>
<p>Life took me through a range of other languages instead: Prolog (strangely, what
my school machines had), Visual Basic, ASP, PHP, Java, Python, JavaScript, Go,
Rust. I tinkered with C++ when messing with game engines, but never quite got
around to C.</p>
<p>Some of the early fears carried through - memory management was intimidating:
<code>malloc</code>, <code>free</code>, and in C++, <code>new</code> and <code>delete</code>.</p>
<p>Over time, I began to overcome the fear of systems languages - first through
Java, then Go. But I never revisited C, and I never quite got over the fear of
manual memory management.</p>
<p>In hindsight, these concepts weren’t really designed for a ten-year-old to
grasp. It makes sense that they felt out of reach.</p>
<h2 id="allocator-confusion">Allocator Confusion</h2>
<p>Zig had me curious for a while, and some recent health issues gave me the space
to explore it. I’ve been building a small game in Zig, and it’s been feeding
that original desire to learn C - just with a bit more clarity.</p>
<p>I was still intimidated by memory allocation though, and went as far as I could
without using an allocator.</p>
<p>Eventually, I had to use one. I understood the convention of <code>init</code> and
<code>deinit</code>, and the idea of allocators in general.</p>
<p>But I was confused about how <code>deinit</code> worked in the context of <code>ArenaAllocator</code>.</p>
<blockquote>
<p>A little learning is a dangerous thing</p>
<p>&ndash; Alexander Pope</p></blockquote>
<p>If the arena frees memory for you, wouldn’t calling <code>deinit</code> on individual
objects inside it risk double-freeing?</p>
<p>If I skip calling <code>deinit</code> on a struct that normally needs it, will I miss other
cleanup tasks?</p>
<p>And does this mean the arena allocator isn’t a true drop-in replacement if I
have to skip <code>deinit</code>?</p>
<p>At the time, the answer wasn’t clear - and I couldn’t find documentation that
resolved it.</p>
<h2 id="clarity">Clarity</h2>
<p>Thankfully, the
<a href="https://ziggit.dev/t/deinit-and-arena-allocator/9856">super friendly folk at Ziggit</a>
helped clarify things.</p>
<h3 id="arenaallocator-handles-it"><code>ArenaAllocator</code> handles it</h3>
<p><code>ArenaAllocator</code> <em>is</em> a drop-in replacement for other allocators. If you
<code>deinit</code> objects using memory from the arena, and those objects try to free that
memory, the operation is effectively (though not exactly) a no-op. There’s no
risk of double-free.</p>
<blockquote>
<p>It’s always safe to free / destroy memory from an arena, as long as you treat
it as freed of course. But it won’t actually be released until the arena is
deinit’ed.</p>
<p>&ndash; <a href="https://ziggit.dev/u/mnemnion/summary">mnemnion</a></p></blockquote>
<p>Even if some memory is freed manually before the arena is cleared, it’s handled
safely.</p>
<p>I was overthinking it.</p>
<h3 id="deinit-should-still-be-called"><code>deinit</code> should still be called</h3>
<p><code>deinit</code> exists for cleanup logic - not just memory. It should always be called
where appropriate, regardless of the allocator. That part doesn’t change.</p>
<h3 id="further-learning">Further learning</h3>
<p>Zig also encourages a light touch. One of the impacts of this is that the object
itself should not hold on to the allocator in the <code>init</code> and use it in <code>deinit</code>.
It could, but the better way would be to accept the <code>allocator</code> in both the
<code>init</code> and the <code>deinit</code>.</p>
<p>This convention is further reinforced by
<a href="https://ziglang.org/download/0.14.0/release-notes.html#Embracing-Unmanaged-Style-Containers">zig deprecating the <code>managed</code> variants of collections</a></p>
<h2 id="thanks">Thanks</h2>
<p>In many ways, this was about cleaning up more than just memory.</p>
<p>Special thanks to the helpful folks at <a href="https://ziggit.dev/">ziggit.dev</a>.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://ziggit.dev/t/deinit-and-arena-allocator/9856">Full discussion on ziggit.dev</a></li>
<li><a href="https://github.com/ziglang/zig/blob/d92649da80a526f2e2b2f220c05b81becf4fa627/lib/std/heap/arena_allocator.zig#L253-L267">Implementation of <code>ArenaAllocator</code></a></li>
</ul>]]></content:encoded></item><item><title>A lone triangle vs the universe</title><link>https://icle.es/2025/04/26/a-lonely-triangle/</link><pubDate>Sat, 26 Apr 2025 10:07:08 +0000</pubDate><guid>https://icle.es/2025/04/26/a-lonely-triangle/</guid><description>&lt;p>I&amp;rsquo;ve been unwell for a few months which has left me with a great deal of fatigue
and limited ability to be productive.&lt;/p>
&lt;p>One of the activities to pass time while healing was playing a lot of ARPG&amp;rsquo;s
recently, particularly &lt;a href="https://pathofexile2.com/home">PoE 2&lt;/a> and the
&lt;a href="https://lastepoch.com/">Last Epoch&lt;/a>. I&amp;rsquo;d even had a few ideas about how I might
do things a little differently. Since my brain capacity was pretty low, these
games with their repetitive nature and easy dopamine hits were really enjoyable.&lt;/p>
&lt;p>I found the gameplay a bit fragmented though, and having to follow guides was
annoying. I had been mulling over the idea of making something which flows, and
supports your decision making. It&amp;rsquo;s not about giving the player answers, but
about providing the player the most relevant information in an easy to digest
format. They still get to make the choices. I struggled to identify what
mattered - guides were easier.&lt;/p>
&lt;p>I did wonder if there was an easy way to just say - &amp;ldquo;use this build from this
guide&amp;rdquo; so that it would save me from all the tedious clicking :/&lt;/p>
&lt;p>All this left me wondering if I could build something that captured the fun
while keeping you in the flow. I even looked at how complicated it would be to
make an isometric game. Too complicated for me was the answer.&lt;/p>
&lt;p>Factory games, one of my other favourite genres felt a little too mentally
taxing. Otherwise, I would have dived into
&lt;a href="https://factorio.com/buy-space-age">Space Age&lt;/a>.&lt;/p>
&lt;p>I have also been bingeing on youtube videos and discovered a fantastic channel -
the &lt;a href="https://www.youtube.com/@TheCodingTrain">Coding Train&lt;/a>.&lt;/p>
&lt;p>I&amp;rsquo;ve been almost religiously watching the
&lt;a href="https://www.youtube.com/watch?v=17WoOqgXsRM&amp;amp;list=PLRqwX-V7Uu6ZiZxtDDRCi6uhfTH4FilpH">Coding Challenges&lt;/a>
playlist and I have been loving it. I love how he makes 2d graphics programming
seems so easy and approachable. I used to be so intimidated by all the math.&lt;/p>
&lt;p>There was a slow confidence building in me that maybe - just maybe I could build
a game.&lt;/p></description><content:encoded><![CDATA[<p>I&rsquo;ve been unwell for a few months which has left me with a great deal of fatigue
and limited ability to be productive.</p>
<p>One of the activities to pass time while healing was playing a lot of ARPG&rsquo;s
recently, particularly <a href="https://pathofexile2.com/home">PoE 2</a> and the
<a href="https://lastepoch.com/">Last Epoch</a>. I&rsquo;d even had a few ideas about how I might
do things a little differently. Since my brain capacity was pretty low, these
games with their repetitive nature and easy dopamine hits were really enjoyable.</p>
<p>I found the gameplay a bit fragmented though, and having to follow guides was
annoying. I had been mulling over the idea of making something which flows, and
supports your decision making. It&rsquo;s not about giving the player answers, but
about providing the player the most relevant information in an easy to digest
format. They still get to make the choices. I struggled to identify what
mattered - guides were easier.</p>
<p>I did wonder if there was an easy way to just say - &ldquo;use this build from this
guide&rdquo; so that it would save me from all the tedious clicking :/</p>
<p>All this left me wondering if I could build something that captured the fun
while keeping you in the flow. I even looked at how complicated it would be to
make an isometric game. Too complicated for me was the answer.</p>
<p>Factory games, one of my other favourite genres felt a little too mentally
taxing. Otherwise, I would have dived into
<a href="https://factorio.com/buy-space-age">Space Age</a>.</p>
<p>I have also been bingeing on youtube videos and discovered a fantastic channel -
the <a href="https://www.youtube.com/@TheCodingTrain">Coding Train</a>.</p>
<p>I&rsquo;ve been almost religiously watching the
<a href="https://www.youtube.com/watch?v=17WoOqgXsRM&amp;list=PLRqwX-V7Uu6ZiZxtDDRCi6uhfTH4FilpH">Coding Challenges</a>
playlist and I have been loving it. I love how he makes 2d graphics programming
seems so easy and approachable. I used to be so intimidated by all the math.</p>
<p>There was a slow confidence building in me that maybe - just maybe I could build
a game.</p>
<h2 id="coding-challenge-46">Coding Challenge 46</h2>
<p>Last night, I was watching the one about remaking
<a href="https://www.youtube.com/watch?v=hacZU523FyM">Asteroids</a>. He made it look so
easy and fun. How he puts those asteroids together felt so clever - neat! I
could do that. Actually, I could make that really interesting.</p>
<p>For a little while now, I&rsquo;ve been wanting to try out one of the challenges, tied
in with playing with <a href="https://ziglang.org/">zig</a>, which has also been on my
radar. I&rsquo;ve largely worked with higher level languages like Java (and golang
more recently). I&rsquo;ve always been fascinated by C, and C++, but never quite got
into it. Zig represented a more fun and modern version of C that I could enjoy.
I also really liked how explicit and simple zig feels.</p>
<p>It feels like a perfect opportunity to feed two birds with one scone. I could
try and replicate what he did in that video in zig.</p>
<p>A little bit of research on graphics/game libraries in zig revealed
<a href="https://www.raylib.com/">raylib</a> to be the best candidate for my needs. While a
C library, it has zig bindings, there is plenty of documentation, albeit not
necessarily in zig - but that was ok.</p>
<p>I could see it in my mind - a field of asteroids, <em>pew pew pew</em>, asteroids break
apart. Destroyed asteroids would drop resources. Oh you could have factories on
the ship and build your armoury - weapons, armour, scanners - everything. That
would be an interesting twist - you don&rsquo;t pick up loot - you build it.</p>
<p>Each item would still have randomised mods, but you could extract them like in
Last Epoch and use them in other ones - I have a lot of ideas.</p>
<h2 id="story">Story</h2>
<p>In terms of the story, I&rsquo;ve been pondering the destructive nature of capitalism
a lot lately and that felt like a good metaphor to bring in here - it just
connected.</p>
<p>The planet where the triangle belongs has been destroyed by a corporation - not
necessarily maliciously, but in a bid to extract it of all valuable resource.</p>
<p>I have this image in my mind of something called a &ldquo;Mill&rdquo; that the planet is put
through. The remnants create an asteroid field in which you find yourself.</p>
<p>The game essentially starts off with seeking vengeance. Victory - at least over
the corporation - is not possible. You can get stronger, much stronger, and you
can get to a place where you can beat the waves with relative ease - but the
waves never end!</p>
<p>I have been pondering a softer ending, a choice that the player can make, when
they&rsquo;ve had enough - but I&rsquo;m reluctant to give that away.</p>
<p>On pondering this. I realised this could also be a quiet metaphor for
depression. Having been through it in my life, I could feel a connection. What
if <strong>the triangle is indestructible</strong>?</p>
<p>Actually, that would improve the flow - no game over screen, and no respawn. All
the machinery and installations on the ship could be destroyed, but the core of
the ship - that always remains. You can always keep fighting.</p>
<h2 id="gameplay">Gameplay</h2>
<h3 id="scavenge">Scavenge</h3>
<p>Starting off in an asteroid field, you&rsquo;re able to scavenge materials. The ship
starts off with a smelter, a factory, and some basic power. You can refine
materials and construct buildings to get stronger.</p>
<p>Each sector will get more difficult. I thought about being able to travel
vertically and horizontally, but in the end, I decided to make it a vertical
scroller. A two dimensional map might be more interesting, but it also brings
annoying choices, particularly in terms of wondering whether you should have
gone another way.</p>
<p>By putting the game on some form of rails, you have a smaller context to think
about - and you can focus on fun. I am reminded of
<a href="https://en.wikipedia.org/wiki/Raptor:_Call_of_the_Shadows">Raptor: Call of the Shadows</a>,
a game that I loved in the 90&rsquo;s.</p>
<p>Raptor had shops, but I want to do a super simplified factory aspect. Every
upgrade should feel earned — constructed, not found. I want to feel that
progress is made entirely by the actions of the player.</p>
<p>There will still be randomisation, but it will be limited to the mods that are
spawned on the items that are constructed. The player gets to decide what items
to build.</p>
<p>I want the crafting to be as deterministic as possible, but while also keeping
it interesting.</p>
<h3 id="figuring-it-out">Figuring it out</h3>
<p>There are no (traditional) tutorials or help sections. This decision will put a
lot of pressure on getting the UI/UX intuitive and easy to use. However, it will
also help maintain flow and keep every bit of progress feeling earned.</p>
<p>Items will have descriptions and details so that the user can make choices, but
it is about experimentation and exploration. I would also like a component,
something like a computer that can be installed. The idea is that this can do
analysis of items and mods and give you details of clear and easy to understand
benefits and disadvantages. Not just what attributes change, but how it will
impact gameplay. I can&rsquo;t be perfect, but it must be accurate, and be able to
provide enough information to be able to decide whether it&rsquo;s worth a try.</p>
<p>The intention is that this computer can act a bit like the guide that you would
otherwise google.</p>
<p>Each sector increases difficulty, but is infinite in itself. After traveling for
a bit, you will be offered an exit. If you keep going, exits will be offered at
regular intervals - but you can just keep going.</p>
<p>You are dropped into a universe and every step is up to you to figure it out.</p>
<h2 id="aesthetic">Aesthetic</h2>
<p>I can hear the synthwave tracks that play in the background already (it could be
because I&rsquo;ve been listening to a lot of synthwave recently). It feels right and
thematic to the original Asteroids.</p>
<p>The graphics, at least initially is going to be simple and shape based, again
thematic. I&rsquo;ve been pondering whether and how the graphics would get updated as
you get through the sectors. The start of it is going to be black and white with
little hints of colour. As you progress, you&rsquo;ll get more and more colour and the
vibrancy increases, sometimes in leaps and bounds. It could be a metaphor for
the journey of recovery.</p>
<p>The sound effects should also have a strong 80s vibe. I want it to feel like
you&rsquo;ve been transported to the 80s but if it happened in 2025.</p>
<h2 id="triangle">triangle</h2>
<p>As a name, at least for the time being, I&rsquo;ve settled on <strong>triangle</strong>. It is a
passion project - I have a vision and a strong idea of how I want to feel
playing it.</p>
<p>At its heart, triangle is about (re)building - a game where every piece of
progress feels personal.</p>
<p>I would like to share it with the world and am looking for feedback, thoughts
ideas and supporters.</p>
<p>If the idea or any of the concepts resonate with you, I’d love to hear from you.</p>
<h2 id="other-posts">Other Posts</h2>
<ul>
<li><a href="https://icle.es/2025-05-08-basic-gameplay.md">Next: Basic Gameplay</a></li>
</ul>
<h2 id="updates">Updates</h2>
<p>2025-05-31: Added <a href="https://youtu.be/8pBPQbJtIJk">devlog #0 on youtube</a><br>
2025-05-21: Added an <a href="https://droneah.itch.io/triangle">itch.io page</a></p>]]></content:encoded></item><item><title>triangle</title><link>https://icle.es/endeavours/triangle/</link><pubDate>Sat, 26 Apr 2025 10:07:08 +0100</pubDate><guid>https://icle.es/endeavours/triangle/</guid><description>&lt;p>triangle is a minimalist arcade ARPG set in an endless asteroid field. You pilot
an indestructible ship, scavenging resources and building every upgrade from
scratch - no loot, no grind, just pure progression.&lt;/p>
&lt;p>Inspired by Asteroids, ARPGs like Last Epoch, and factory games, triangle blends
fast-paced combat with deterministic crafting and a focus on flow. There are no
tutorials - just intuitive design, thoughtful feedback, and space to explore.&lt;/p>
&lt;p>At its heart, it’s a quiet metaphor for recovery: from black-and-white
beginnings to vibrant growth, you always survive - and you always rebuild.&lt;/p></description><content:encoded><![CDATA[<p>triangle is a minimalist arcade ARPG set in an endless asteroid field. You pilot
an indestructible ship, scavenging resources and building every upgrade from
scratch - no loot, no grind, just pure progression.</p>
<p>Inspired by Asteroids, ARPGs like Last Epoch, and factory games, triangle blends
fast-paced combat with deterministic crafting and a focus on flow. There are no
tutorials - just intuitive design, thoughtful feedback, and space to explore.</p>
<p>At its heart, it’s a quiet metaphor for recovery: from black-and-white
beginnings to vibrant growth, you always survive - and you always rebuild.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/tags/triangle">Posts</a></li>
<li><a href="http://www.youtube.com/@lone-triangle">YouTube Channel</a></li>
<li><a href="https://droneah.itch.io/triangle">Itch.io</a></li>
<li><a href="https://forums.tigsource.com/index.php?topic=76406.0">TIGSource</a></li>
</ul>
]]></content:encoded></item><item><title>Starting Again</title><link>https://icle.es/2025/02/12/starting-again/</link><pubDate>Wed, 12 Feb 2025 13:17:57 +0000</pubDate><guid>https://icle.es/2025/02/12/starting-again/</guid><description>&lt;p>Most of you know that I ran my own company for fifteen years, and at one point
it had around 30 people in five different teams. It was without a doubt the most
difficult job I&amp;rsquo;ve ever done by a long long way. It was also the most rewarding
and fulfilling.&lt;/p>
&lt;p>Truth be told - it also very nearly killed me! I had a breakdown which left me
an emotional and mental wreck, as if I&amp;rsquo;d been hit by a bus (10 points if you
tell me which one!)&lt;/p></description><content:encoded><![CDATA[<p>Most of you know that I ran my own company for fifteen years, and at one point
it had around 30 people in five different teams. It was without a doubt the most
difficult job I&rsquo;ve ever done by a long long way. It was also the most rewarding
and fulfilling.</p>
<p>Truth be told - it also very nearly killed me! I had a breakdown which left me
an emotional and mental wreck, as if I&rsquo;d been hit by a bus (10 points if you
tell me which one!)</p>
<p>It has taken me the better part of 15 years to recover from that, and in the
meantime I tried working for others and I enjoyed it. In the end, though, I
think once you have run your own company for a while, it is incredibly difficult
to do anything else. It was inevitable that <em>if</em> I ever got well enough, that I
would want to start something of my own again.</p>
<p>It was a long and hard journey, but I am <em>finally</em> better, and I am ready to
start again. I am taking all the hard earned lessons into this venture, and I
want to do it even more differently this time.</p>
<p>I promised myself that this time around, I would:</p>
<ul>
<li>Surround myself with people who will challenge me and expand my perspective.</li>
<li>Accept the fact that I do not have all the answers</li>
<li>Do things the way that we are meant to:
<ul>
<li>Bring in expertise we do not have</li>
<li>Have someone in the team right from the start focused on marketing</li>
<li>Technically, BDD, TDD, Agile, Lean, &ldquo;&ldquo;The only way to go fast is to go
well&rdquo;&rdquo;</li>
</ul>
</li>
<li>Focus on people first</li>
<li>Focus on the journey, not the destination</li>
</ul>
<p>One thing I realised as part of this journey is that doing things differently
means that all the tools, technologies, and processes out there don&rsquo;t quite fit.</p>
<p>With #muster, we are starting with one of the easier parts, and streamlining the
developer experience. We are ending up building a lot of other things in the
process, but they all take longer.</p>
<p>With choosing to do things in a very different way, we don’t know exactly how
it’ll all pan out, but it will an interesting journey</p>
]]></content:encoded></item><item><title>Will A.I kill America?</title><link>https://icle.es/2025/01/22/will-ai-end-america/</link><pubDate>Wed, 22 Jan 2025 13:54:30 +0000</pubDate><guid>https://icle.es/2025/01/22/will-ai-end-america/</guid><description>&lt;p>Will $500,000,000,000 in AI investment kill America?&lt;/p>
&lt;p>Project Stargate: $500,000,000,000(i.e. $500b, the GDP of Singapore) in AI over
the next four years, starting with $100,000,000,000 (i.e. $100b, the GDP of
Bulgaria). &lt;a href="https://lnkd.in/getadcFN">https://lnkd.in/getadcFN&lt;/a>&lt;/p>
&lt;p>These numbers boggle the mind. The California fires have been estimated to cost
between $250b and $275b, which is just about half the total investment into
Project Stargate.&lt;/p>
&lt;p>How much of this money is likely to be spent on mitigating bias? What about on
exploring the risks of AI? I don&amp;rsquo;t mean AGI, Singularity, or about AI taking
jobs. I mean the day-to-day risks to ordinary people: the spreading of
misinformation and biases. We all saw the damage that Facebook and Cambridge
Analytica brought to the world. How much more damage could this cause?&lt;/p></description><content:encoded><![CDATA[<p>Will $500,000,000,000 in AI investment kill America?</p>
<p>Project Stargate: $500,000,000,000(i.e. $500b, the GDP of Singapore) in AI over
the next four years, starting with $100,000,000,000 (i.e. $100b, the GDP of
Bulgaria). <a href="https://lnkd.in/getadcFN">https://lnkd.in/getadcFN</a></p>
<p>These numbers boggle the mind. The California fires have been estimated to cost
between $250b and $275b, which is just about half the total investment into
Project Stargate.</p>
<p>How much of this money is likely to be spent on mitigating bias? What about on
exploring the risks of AI? I don&rsquo;t mean AGI, Singularity, or about AI taking
jobs. I mean the day-to-day risks to ordinary people: the spreading of
misinformation and biases. We all saw the damage that Facebook and Cambridge
Analytica brought to the world. How much more damage could this cause?</p>
<p>How might these new tools be used to manipulate, control and oppress? How might
these tools further marginalise those who are already disaffected?</p>
<p>Yes, there might be fantastic breakthroughs, but at what cost?</p>
<p>I am somehow reminded of the poem &ldquo;First they came&hellip;&rdquo;:</p>
<blockquote>
<p>First they came for the socialists, and I did not speak out—<br>
Because I was not a socialist.</p>
<p>Then they came for the trade unionists, and I did not speak out—<br>
Because I was not a trade unionist.</p>
<p>Then they came for the Jews, and I did not speak out—<br>
Because I was not a Jew.</p>
<p>Then they came for me—<br>
And there was no one left to speak for me.</p></blockquote>
<p>The Manhattan Project, which built the first Nuclear bomb, cost $2 billion,
which, adjusted for inflation, comes to $27 billion. America wants to spend
almost 20 times that amount over roughly the same time on Project Stargate.</p>
<p>I apologise for the doom &amp; gloom, but I have genuine concerns about the risks at
play here. How about you? Are you worried, excited or both?</p>
]]></content:encoded></item><item><title>There are no best practices</title><link>https://icle.es/2025/01/21/no-best-practices/</link><pubDate>Tue, 21 Jan 2025 11:12:57 +0000</pubDate><guid>https://icle.es/2025/01/21/no-best-practices/</guid><description>&lt;p>I was watching
&lt;a href="https://www.youtube.com/watch?v=V3yIKD6yMhA">a video by Dave Thomas&lt;/a> (one of
the authors of The Pragmatic Programmer) where he explains that the idea of best
practices is misleading. He argues that best practices are contextual.&lt;/p>
&lt;p>Dave uses the example of Agile, which is a sore topic for many. He talks about
how selling shrinkwrapped agile is not going to work, because it is different
for each company, and each team. It has to be custom fit.&lt;/p></description><content:encoded><![CDATA[<p>I was watching
<a href="https://www.youtube.com/watch?v=V3yIKD6yMhA">a video by Dave Thomas</a> (one of
the authors of The Pragmatic Programmer) where he explains that the idea of best
practices is misleading. He argues that best practices are contextual.</p>
<p>Dave uses the example of Agile, which is a sore topic for many. He talks about
how selling shrinkwrapped agile is not going to work, because it is different
for each company, and each team. It has to be custom fit.</p>
<p>It&rsquo;s hard to disagree. I do think though that a lot of the practices that are
talked about as being the &ldquo;best,&rdquo; are good practices. However, a challenge is
that a lot of these practices depend upon or are meant to encourage &ldquo;good
mindsets.&rdquo; The mindsets are far more valuable than the practice itself.</p>
<p>The question then becomes about how prepared an organisation or team is to shift
their mindset. As you probably know, it sometimes feels like it would be easier
to move the earth than some mindsets.</p>
<p>I encourage people to consider practices (good, bad and ugly) with a critical
perspective to understand the mindset behind it. Only then is the question of
relevancy capable of being answered. If the mindset that is encouraged can be
achieved in another way, that could be your version of that practice.</p>
<p>The problem, of course, is that communication is difficult, and our ability to
perceive possibilities is limited by our own perspectives. Sometimes, it takes a
period of trying out a practice before we can begin to understand its potential
value.</p>
<p>What do you think about best practices? Are there universal ones?</p>
]]></content:encoded></item><item><title>Agile, the hope killer!</title><link>https://icle.es/2024/12/12/agile-the-hope-killer/</link><pubDate>Thu, 12 Dec 2024 11:22:12 +0000</pubDate><guid>https://icle.es/2024/12/12/agile-the-hope-killer/</guid><description>&lt;p>Two weeks into mapping out a project, I realised that we&amp;rsquo;d underestimated the
lead-time. We&amp;rsquo;d have to push for overtime, pushing harder and longer to meet the
original deadline. You&amp;rsquo;ve probably been there before - I have! This time though,
I resisted. There must be a better way!&lt;/p>
&lt;p>Agile really shines here. In fact, it was agile that pointed out that the
original estimates (which we thought was pessimistic) was actually still
optimistic. The two weeks of user story mapping saved us a great deal of pain.&lt;/p></description><content:encoded><![CDATA[<p>Two weeks into mapping out a project, I realised that we&rsquo;d underestimated the
lead-time. We&rsquo;d have to push for overtime, pushing harder and longer to meet the
original deadline. You&rsquo;ve probably been there before - I have! This time though,
I resisted. There must be a better way!</p>
<p>Agile really shines here. In fact, it was agile that pointed out that the
original estimates (which we thought was pessimistic) was actually still
optimistic. The two weeks of user story mapping saved us a great deal of pain.</p>
<p>As Robert C. Martin puts it:</p>
<blockquote>
<p>Planning can destroy hope… and show us just how screwed we are.</p></blockquote>
<p>By constantly reassessing plans, Agile gives us an early warning system when
things are slipping.</p>
<p>I used to think that the point of Agile was to be faster. The book Clean Agile
argues that the point is to fail sooner.</p>
<p>In our case, Agile gave us an early warning system. It killed hope before hope
killed the product.</p>
<p>Instead of going into overdrive and being a workaholic (again), we simplified
scope, adjusted the deadline and found a better balance.</p>
<p>We like to think of this as the way of the tortoise.</p>
<p>How has Agile helped/impeded you and your team?</p>
]]></content:encoded></item><item><title>Not Afraid</title><link>https://icle.es/2024/08/02/not-afraid/</link><pubDate>Fri, 02 Aug 2024 16:12:59 +0000</pubDate><guid>https://icle.es/2024/08/02/not-afraid/</guid><description>&lt;p>There once was a boy,&lt;br>
And the boy was not afraid.&lt;/p>
&lt;p>He looked out at the world,&lt;br>
Everywhere he looked,&lt;br>
Everyone he knew,&lt;br>
Led their life carefully,&lt;br>
Moved through crowds cautiously,&lt;br>
Stayed far from the ledge.&lt;/p>
&lt;p>It took many years,&lt;br>
Jumping off many cliffs,&lt;br>
Chasing butterflies,&lt;br>
Breaking his little heart,&lt;br>
many, many times.&lt;/p>
&lt;p>He understood the fear,&lt;br>
He felt it as he lay on the floor,&lt;br>
cradling himself,&lt;br>
Afraid to open the door,&lt;br>
Afraid to get up,&lt;br>
Afraid to breathe,&lt;br>
Letting the darkness take him.&lt;/p></description><content:encoded><![CDATA[<p>There once was a boy,<br>
And the boy was not afraid.</p>
<p>He looked out at the world,<br>
Everywhere he looked,<br>
Everyone he knew,<br>
Led their life carefully,<br>
Moved through crowds cautiously,<br>
Stayed far from the ledge.</p>
<p>It took many years,<br>
Jumping off many cliffs,<br>
Chasing butterflies,<br>
Breaking his little heart,<br>
many, many times.</p>
<p>He understood the fear,<br>
He felt it as he lay on the floor,<br>
cradling himself,<br>
Afraid to open the door,<br>
Afraid to get up,<br>
Afraid to breathe,<br>
Letting the darkness take him.</p>
<p>The fear permeated him,<br>
Every pore, nook and cranny,<br>
A darkness with no beginning,<br>
A darkness with no end.</p>
<p>Forsaken!</p>
<p>Cold!</p>
<p>Afraid!</p>
<p>Alone!</p>
<p>Flicker,<br>
Flicker,<br>
Flicker,<br>
Hope.</p>
<p>Step...<br>
Step...<br>
By,<br>
Step...</p>
<p>Into...<br>
The light.</p>
<p>There once was a man,<br>
And he was brave!</p>
]]></content:encoded></item><item><title>Limit Java Memory Globally</title><link>https://icle.es/2024/07/26/limit-java-memory-globally/</link><pubDate>Fri, 26 Jul 2024 11:18:35 +0100</pubDate><guid>https://icle.es/2024/07/26/limit-java-memory-globally/</guid><description>&lt;p>Java can be greedy with RAM. By default, it grabs up to half your system memory,
which is fine for servers—but annoying on a dev machine.&lt;/p>
&lt;p>You can tame this by setting:&lt;/p>
```bash
export JAVA_TOOL_OPTIONS="-Xmx1G"
```
&lt;p>I dropped this into my .bashrc, and it instantly reduced background memory
pressure. One gig is plenty for most compile-and-run tasks during development.&lt;/p></description><content:encoded><![CDATA[<p>Java can be greedy with RAM. By default, it grabs up to half your system memory,
which is fine for servers—but annoying on a dev machine.</p>
<p>You can tame this by setting:</p>
```bash
export JAVA_TOOL_OPTIONS="-Xmx1G"
```
<p>I dropped this into my .bashrc, and it instantly reduced background memory
pressure. One gig is plenty for most compile-and-run tasks during development.</p>
]]></content:encoded></item><item><title>Microservices vs Monolith: Real World Tradeoffs</title><link>https://icle.es/2024/07/17/microservices-vs-monolith-real-world-tradeoffs/</link><pubDate>Wed, 17 Jul 2024 09:48:25 +0100</pubDate><guid>https://icle.es/2024/07/17/microservices-vs-monolith-real-world-tradeoffs/</guid><description>&lt;p>When starting a new backend system for a contract I was on, one of the early
decisions I had to make was whether to lean into a monolith or adopt a
microservices approach. While common wisdom offers strong opinions on both ends
of the spectrum, in reality, the choice often hinges on organizational
constraints as much as on technical purity.&lt;/p>
&lt;h3 id="reactive-vs-traditional-spring-web">Reactive vs Traditional Spring Web&lt;/h3>
&lt;p>I began by reviewing
&lt;a href="https://filia-aleks.medium.com/microservice-performance-battle-spring-mvc-vs-webflux-80d39fd81bf0">performance comparisons&lt;/a>
between Spring MVC and WebFlux. Reactive Web generally comes out ahead in
benchmarks, but that doesn’t tell the whole story.&lt;/p></description><content:encoded><![CDATA[<p>When starting a new backend system for a contract I was on, one of the early
decisions I had to make was whether to lean into a monolith or adopt a
microservices approach. While common wisdom offers strong opinions on both ends
of the spectrum, in reality, the choice often hinges on organizational
constraints as much as on technical purity.</p>
<h3 id="reactive-vs-traditional-spring-web">Reactive vs Traditional Spring Web</h3>
<p>I began by reviewing
<a href="https://filia-aleks.medium.com/microservice-performance-battle-spring-mvc-vs-webflux-80d39fd81bf0">performance comparisons</a>
between Spring MVC and WebFlux. Reactive Web generally comes out ahead in
benchmarks, but that doesn’t tell the whole story.</p>
<p>In our use case—web notifications—the benefit of reactive patterns depends
heavily on how data is delivered. If we were polling, the advantage would be
limited. However, with Server-Sent Events (SSE), Spring’s support aligns
directly with Reactive Web, making WebFlux the more appropriate choice for this
part of the system.</p>
<h3 id="the-deployment-constraint">The Deployment Constraint</h3>
<p>Ideally, I would have started with a monolith: a single deployable artifact
combining both the Kafka Streams logic and the API. This option would have
simplified initial development and allowed us to iterate quickly. But at the
client, the platform does not allow deploying a Kafka Streams app and an API
within the same Kubernetes deployment.</p>
<p>This effectively rules out a true monolith, even for a prototype.</p>
<h3 id="options-considered">Options Considered</h3>
<h4 id="shared-library-with-thin-deployments">Shared Library with Thin Deployments</h4>
<p>A middle ground was to build the core logic in a shared library and have
lightweight deployments wrap around it. This would allow the streams app and the
API to share code without needing to make HTTP calls between them.</p>
<p>The downside: these services are no longer independently deployable. But given
our team size and velocity goals, this compromise might be acceptable.</p>
<h4 id="full-microservices">Full Microservices</h4>
<p>Another option was to separate the services entirely:</p>
<ul>
<li><strong>Streams service</strong> (Kafka, plus domain-specific logic)</li>
<li><strong>Web API</strong> (for delivering notifications)</li>
<li><strong>Subscription API</strong> (managing notification subscriptions)</li>
</ul>
<p>This adheres more closely to the single responsibility principle, especially as
we move from PoC to MVP. However, it adds deployment and coordination overhead.</p>
<h4 id="application-profiles">Application Profiles</h4>
<p>A third hacky option was to control which parts of the app run using
environment-based profiles. For example, we could disable Kafka in dev or use
conditional beans to keep deployments clean. While not ideal long-term, it
offers flexibility for early stages.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Constraints matter. While I lean toward monoliths for rapid delivery in small
teams, platform limitations forced a hybrid approach. We intend to evolve into
microservices over time, but only when the benefits clearly outweigh the cost.</p>
<p>Have you faced similar deployment constraints that shaped your architecture? I&rsquo;d
love to hear how you navigated them.</p>
]]></content:encoded></item><item><title>A Half Written Poem</title><link>https://icle.es/2024/01/03/a-half-written-poem/</link><pubDate>Wed, 03 Jan 2024 13:26:07 +0000</pubDate><guid>https://icle.es/2024/01/03/a-half-written-poem/</guid><description>```
The middle of the night,
When the moon had eloped.

A cool breeze drifted,
sails fluttered,
leaves danced,
The sound of water lapping.

A boat inched its way to shore,
Its hold overflowed,
locked behind a heavy door,

On the deck,
a man sat against the black wall,
his charred chest rasped,
A crow watched from a mast.

In his half open hand,
A half written poem,
```</description><content:encoded>```
The middle of the night,
When the moon had eloped.

A cool breeze drifted,
sails fluttered,
leaves danced,
The sound of water lapping.

A boat inched its way to shore,
Its hold overflowed,
locked behind a heavy door,

On the deck,
a man sat against the black wall,
his charred chest rasped,
A crow watched from a mast.

In his half open hand,
A half written poem,
```
</content:encoded></item><item><title>Compassion</title><link>https://icle.es/2023/12/28/compassion/</link><pubDate>Thu, 28 Dec 2023 00:00:00 +0000</pubDate><guid>https://icle.es/2023/12/28/compassion/</guid><description>&lt;p>When you think about corporations and their culture, it&amp;rsquo;s unlikely that
compassion is at the top of the list. It&amp;rsquo;s likely the opposite. Corporations
focus on profit, shareholder value and market domination, none of which sound
very compassionate.&lt;/p>
&lt;p>Things are a little better in the non-profit sector. Sure, the currency may be
different, with a focus on &amp;ldquo;doing good&amp;rdquo; but in my experience, compassion is not
a high priority. Justice is often a key element, as is fairness, but compassion
is often forgotten.&lt;/p>
&lt;p>We human animals have been in survival mode for so long, for so many
generations, that we are all carrying around bags and bags of trauma. Few of us
do not have real childhood traumas, even if most are unaware of them. According
to the UK Trauma Council,
&lt;a href="https://www.annafreud.org/get-involved/networks/uk-trauma-council/">&amp;ldquo;One in three children and young people are exposed to at least one potentially traumatic event by the time they are 18.&amp;rdquo;&lt;/a>
Add to this the influence of your parents and family, how much
&lt;a href="https://www.amazon.co.uk/They-You-Up-Survive-Family/dp/0747584788/ref=sr_1_1?crid=2IRWT20P9S4NM&amp;amp;keywords=they&amp;#43;f***&amp;#43;you&amp;#43;up&amp;amp;qid=1702031285&amp;amp;s=books&amp;amp;sprefix=they&amp;#43;f&amp;#43;you&amp;#43;up%2Cstripbooks%2C121&amp;amp;sr=1-1">*They F*** You Up*&lt;/a>,
and we are all in a pretty dire state right out of the gate.&lt;/p></description><content:encoded><![CDATA[<p>When you think about corporations and their culture, it&rsquo;s unlikely that
compassion is at the top of the list. It&rsquo;s likely the opposite. Corporations
focus on profit, shareholder value and market domination, none of which sound
very compassionate.</p>
<p>Things are a little better in the non-profit sector. Sure, the currency may be
different, with a focus on &ldquo;doing good&rdquo; but in my experience, compassion is not
a high priority. Justice is often a key element, as is fairness, but compassion
is often forgotten.</p>
<p>We human animals have been in survival mode for so long, for so many
generations, that we are all carrying around bags and bags of trauma. Few of us
do not have real childhood traumas, even if most are unaware of them. According
to the UK Trauma Council,
<a href="https://www.annafreud.org/get-involved/networks/uk-trauma-council/">&ldquo;One in three children and young people are exposed to at least one potentially traumatic event by the time they are 18.&rdquo;</a>
Add to this the influence of your parents and family, how much
<a href="https://www.amazon.co.uk/They-You-Up-Survive-Family/dp/0747584788/ref=sr_1_1?crid=2IRWT20P9S4NM&amp;keywords=they&#43;f***&#43;you&#43;up&amp;qid=1702031285&amp;s=books&amp;sprefix=they&#43;f&#43;you&#43;up%2Cstripbooks%2C121&amp;sr=1-1">*They F*** You Up*</a>,
and we are all in a pretty dire state right out of the gate.</p>
<p>After an incredibly bad burnout and several years of being unable to work, I
started my therapeutic journey in mid-2016. It was only through this intense and
challenging but healing and learning journey over several years that I was
finally able to go back to work (in January 2019).</p>
<p>This journey helped me understand just how much trauma was in my life and how
trauma begets trauma.</p>
<p>Will Bowen said, &ldquo;Hurt people hurt people.&rdquo; We are all hurt, so we hurt others,
sometimes intentionally, often unintentionally. I think there is a corollary to
this — hurt people get hurt. The hurts we carry around leave us sensitive to
specific triggers. This can put us into a defensive mode that causes a repeat of
the pattern, leading to more hurt. You may be able to think of some examples of
this happening to you.</p>
<p><strong>Why does it matter?</strong> We are all professionals. Shouldn&rsquo;t we &ldquo;man up&rdquo; and
leave all this stuff at home? Toxic masculinity aside - this is not possible. We
are human, and no matter how much we try, it is impossible to separate our
traumas or hurts from our day-to-day existence.</p>
<p>Most of us spend most of our time at work (and recovering from it). We do this
believing that it will all eventually be worth it - perhaps when we retire or
finally make enough money to take a step back. Are we trapped in the promise of
the future? I was! Do we have the mental space to realise when we get to that
destination? Are we trapped in Stockholm, at the mercy of the prison we have
built for ourselves?</p>
<p>Mindfulness aside, how can we make our day-to-day life more enjoyable? How can
we make the journey more valuable than the destination? Ultimately, the
destination is the same for all of us. Should we be rushing to get there? (If
the idea sounded even remotely appealing, I would encourage you to <strong>consider</strong>
finding a good therapist - they do wonders!)</p>
<p>I think it all begins with compassion, starting with compassion for oneself.
What if we could create an environment where failure is not an option — not
because we must succeed at any cost, but because nothing that happens is
considered a failure? What if mistakes are not chastised but used as
opportunities to learn from?</p>
<blockquote>
<p>I have not failed. I&rsquo;ve just found 10,000 ways that won&rsquo;t work.<br>
— Thomas A. Edison</p></blockquote>
<p>How do we build an environment where innovation, imagination, and creativity
thrive? I believe the key here is a sea change to the fundamental attitude and
purpose of the organisation. We can create this environment by making the
organisation&rsquo;s primary focus collaboration (with everyone who wants to
collaborate) and making compassion the most vital tool of that collaboration.</p>
<p>I don&rsquo;t know about you, but I don&rsquo;t have deep pockets, so I still need to worry
about paying the bills. We also do not (yet) live in a &ldquo;Star Trek&rdquo;-like universe
where money is no longer critical to society. How will we pay the bills if we
focus on something other than commercial viability?</p>]]></content:encoded></item><item><title>Slack Channels for ClickUp Tickets</title><link>https://icle.es/2023/12/14/slack-channels-for-clickup/</link><pubDate>Thu, 14 Dec 2023 00:00:00 +0000</pubDate><guid>https://icle.es/2023/12/14/slack-channels-for-clickup/</guid><description>&lt;p>I mentioned GiToLink in the &lt;a href="https://icle.es/2023-11-21-muster.md">post about collaboration&lt;/a>
as the tool I am building to link GitHub and Slack together, creating Slack
channels as the communication platform for PRs. I have been working on this for
a few weeks and have gotten as far as linking up the Auth for Slack and GitHub.&lt;/p>
&lt;p>Today, while co-working, someone (thanks,
&lt;a href="https://www.linkedin.com/in/michael-pike-154616a2/">Mike&lt;/a>) suggested an
alternative integration, currently not available but potentially equally (or
more) valuable. What if we link ClickUp (or other productivity platforms like
JIRA) to Slack? The idea would be to create a Slack channel for any tickets that
are in the &lt;code>OPEN&lt;/code> state.&lt;/p></description><content:encoded><![CDATA[<p>I mentioned GiToLink in the <a href="https://icle.es/2023-11-21-muster.md">post about collaboration</a>
as the tool I am building to link GitHub and Slack together, creating Slack
channels as the communication platform for PRs. I have been working on this for
a few weeks and have gotten as far as linking up the Auth for Slack and GitHub.</p>
<p>Today, while co-working, someone (thanks,
<a href="https://www.linkedin.com/in/michael-pike-154616a2/">Mike</a>) suggested an
alternative integration, currently not available but potentially equally (or
more) valuable. What if we link ClickUp (or other productivity platforms like
JIRA) to Slack? The idea would be to create a Slack channel for any tickets that
are in the <code>OPEN</code> state.</p>
<h2 id="clickup--slack">ClickUp &lt;&ndash;&gt; Slack</h2>
<p>The user would start by linking up and authenticating with Slack. They would
then need to authenticate with ClickUp (more integrations in the future). Once
this is done, GiToLink can link the user&rsquo;s ClickUp account with their Slack
account.</p>
<p>When the user opens a new ticket (moving from TODO to any open state), GiToLink
automatically creates a new Slack channel and adds the assignees. The channel
description will be the ticket description, and any files will also be
uploaded/linked to the channel. There will also be links back to the ClickUp
ticket.</p>
<p>Any comments already on the ticket would be created in the Slack channel as
messages attributed to the relevant user, where possible.</p>
<p>Once the channel has been created, any additional comments in ClickUp will be
added as a message into Slack and vice versa.</p>
<p>All the messages in the Slack channel would be synchronised with the ticket in
ClickUp. It could ease communication around the relevant tickets, increase
visibility and improve productivity.</p>
<h2 id="challenges">Challenges</h2>
<h3 id="channel-proliferation">Channel Proliferation</h3>
<p>The biggest challenge is that everyone would end up with several more Slack
channels. In an agile environment, each individual within a team should only
have a handful of tickets that are relevant to them, and the team should be
limited to 9 people. There should be one ticket each person is working on, and
perhaps one or two they have handed off to another team member. The product
owner might want visibility on all the tickets the team is working on, which in
a large team could be (theoretically, at least) around nine.</p>
<p>Most people likely have around 15 channels (or fewer), and doubling that is
problematic.</p>
<h4 id="invite-only-owner--assignee">Invite only Owner + Assignee</h4>
<p>One way to mitigate this is to invite only the Owner and the assignees of the
ticket to the channel, and when assignees change, any new assignees are added to
the channel.</p>
<p>The product owner will likely still get inundated with channels, assuming they
create the majority of tickets.</p>
<h4 id="disable-automatic-channel-inclusion">Disable automatic channel inclusion</h4>
<p>An option that lets them opt out of automatic channel inclusion may be enough.
They can be manually invited into the channel if and when required. In this
case, though, the person assigned to a ticket could end up in a channel alone.</p>
<h4 id="channel-sections">Channel Sections</h4>
<p><a href="https://slack.com/intl/en-gb/help/articles/360043207674-Organise-your-sidebar-with-customised-sections">Slack allows you to have channel sections</a>,
mitigating the larger list of channels by separating the ClickUp channels into a
specific section.</p>
<h3 id="value">Value</h3>
<p>Another challenge we have to address is whether such an integration adds enough
value. Can we streamline communication enough to save time, money, and effort?
Can we capture and store more relevant information in each ticket?</p>
<h4 id="streamline-interactions">Streamline Interactions</h4>
<p>If we can load up all the information from the ticket into the Slack channel,
the person working on the ticket may no longer need to open up ClickUp in
another window/tab/workspace. Fewer windows open mean fewer distractions and
better focus.</p>
<p>If more information is needed or there is a question, you can pull the person
into the channel to ask the question, making the process of chatting about a
ticket easier.</p>
<p>We can provide some additional interaction buttons in the channel for changing
the ticket status or re-assigning it to someone else, further reducing the
requirement for having the ClickUp app open during the lifetime of a ticket.</p>
<p>If this tool proves its value, we could have an additional channel to track the
current sprint, showing the highest priority tickets so that a user can pick up
a new ticket on Slack. This channel could provide high-level updates from the
sprint tickets.</p>
<p>Alternatively, when the user closes (or re-assigns) their only open ticket, it
could automatically open and assign the next relevant ticket to the user. This
automation might be one too far.</p>
<h4 id="communication-trail">Communication Trail</h4>
<p>Since a Slack channel is dedicated to each ticket/issue/task, this encourages
each user to have conversations related to that ticket in its Slack channel
rather than in direct messages. These interactions are now captured within the
channel and in the ClickUp ticket for reference later. Having a single source of
all communication related to that ticket is easier.</p>
<h2 id="whats-next">What&rsquo;s Next</h2>
<p>Overall, I think the value added by integrating ClickUp tickets into Slack
channels is much higher than the risks posed by the challenges.</p>
<p>I still have questions about commercial viability which I will continue to
explore alongside the exploration of the technical implementation.</p>
<p>In the meantime, I would also like to collect all feedback on issues,
challenges, benefits and potential additional functionality. If you have any
thoughts on this, please reach out to me — either on site or
<a href="https://www.linkedin.com/in/shriramshrishrikumar/">on LinkedIn</a></p>
<h2 id="updates">Updates</h2>
<ul>
<li>2025-03-01: GiToLink is now <a href="https://muster.chat">#muster</a></li>
</ul>
]]></content:encoded></item><item><title>A Longing for the Sea</title><link>https://icle.es/2023/12/06/a-longing-for-the-sea/</link><pubDate>Wed, 06 Dec 2023 00:00:00 +0000</pubDate><guid>https://icle.es/2023/12/06/a-longing-for-the-sea/</guid><description>&lt;p>One of my recently favourite quotes is:&lt;/p>
&lt;blockquote>
&lt;p>&lt;em>If you want to build a ship, don&amp;rsquo;t drum up people to collect wood and don&amp;rsquo;t
assign them tasks and work, but rather teach them to long for the endless
immensity of the sea.&lt;/em>&lt;br>
— &lt;a href="https://www.brainyquote.com/authors/antoine-de-saint-exupery-quotes">Antoine de Saint-Exupery&lt;/a>&lt;/p>&lt;/blockquote>
&lt;p>In my mind, a good agile team works in this fashion. The team has a shared
vision, a goal and everyone is empowered to make choices they feel contributes
to progress to the shared goal.&lt;/p>
&lt;p>With &lt;a href="https://icle.es/muster/2023-11-21-muster.md">my recent post on collaboration&lt;/a>, I have
been thinking about how a concept like this applies within a truly collaborative
environment. In the quote, there is clearly a group of people and one of them
wants to build a ship. We do not know what the rest of the group desires. In the
context of traditional leadership, what the rest of the group wants is
importance mainly in the context of understanding how their wants and needs
align with those of the ones in leadership.&lt;/p></description><content:encoded><![CDATA[<p>One of my recently favourite quotes is:</p>
<blockquote>
<p><em>If you want to build a ship, don&rsquo;t drum up people to collect wood and don&rsquo;t
assign them tasks and work, but rather teach them to long for the endless
immensity of the sea.</em><br>
— <a href="https://www.brainyquote.com/authors/antoine-de-saint-exupery-quotes">Antoine de Saint-Exupery</a></p></blockquote>
<p>In my mind, a good agile team works in this fashion. The team has a shared
vision, a goal and everyone is empowered to make choices they feel contributes
to progress to the shared goal.</p>
<p>With <a href="https://icle.es/muster/2023-11-21-muster.md">my recent post on collaboration</a>, I have
been thinking about how a concept like this applies within a truly collaborative
environment. In the quote, there is clearly a group of people and one of them
wants to build a ship. We do not know what the rest of the group desires. In the
context of traditional leadership, what the rest of the group wants is
importance mainly in the context of understanding how their wants and needs
align with those of the ones in leadership.</p>
<p>Within this context, collaboration is often seen as something the team does for
the purpose of achieving the goals or the vision set forth by leadership.</p>
<p>Having been through the gauntlet of various levels of company ownership and
levels of leadership, the only thing I feel I have ascertained is that I don&rsquo;t
really like any of it. In positions of leadership, the amount of responsibility
and pressure is immense and the higher up the ladder you go, the lonelier the
job is.</p>
<p>Being a team member with no leadership responsibilities is liberating. There is
a great deal of joy in being able to physically and metaphorically shut the
computer down at the end of the day and be done with the job. The rest of your
life is yours. The downside, on the other hand, is that I disagreed with (at
least) half the decisions made. With little power to improve the quality of life
of myself or my fellow team members — mainly because ultimately what was
important was the bottom line, not quality of life.</p>
<p>Which brings me to the question: <strong>how do you build a collaborative environment
where the traditional boundaries of leaders and members are dissolved?</strong></p>
<p>I find the first challenge here is finding like minded people. Having the
motivation to build a highly collaborative team is only half the challenge if
the people in it are trapped in traditional team roles of member or leader — or
even worse, manager!</p>
<p>How easy is it to find someone able to see the possibility of a different way of
working when we have all been stuck in the rut of us vs them, infinite growth
and shareholder value? How easy is it to then learn to put aside our
differences, our childhood traumas and our baggage to be able to express our
authentic thoughts and desires without triggering our defence mechanisms or
letting our emotional baggage leak out? How much does it matter?</p>
<p>Hurt people hurt people, and humanity struggles to cooperate and collaborate
mainly because of our hurts — the ones we are unable to heal and recover from.
Because our hurts, particularly from childhood, are so strongly seared in us, we
see danger when anything close to our hurts presents itself. Like a soldier who
ducks and covers when they hear a car backfire — we all go into defensive mode
when something familiarly scary comes into our vicinity. Unfortunately for a lot
of us, we live in a world full of little triggers — some of which may be so
small that they are undetectable — but it impacts our mood and our ability to
reason.</p>]]></content:encoded></item><item><title>A new way</title><link>https://icle.es/2023/11/29/a-new-way/</link><pubDate>Wed, 29 Nov 2023 00:00:00 +0000</pubDate><guid>https://icle.es/2023/11/29/a-new-way/</guid><description>&lt;p>Having been involved in the rat race for decades, it is time to try something
different.&lt;/p>
&lt;p>We live in the belief that what is good for “the machine,” is good for the
individual - be it capitalism, the government, or society. What if we worked on
the basis of the opposite.&lt;/p>
&lt;blockquote>
&lt;p>Not all those who wander are lost.&lt;br>
— J. R. R. Tolkien&lt;/p>&lt;/blockquote></description><content:encoded><![CDATA[<p>Having been involved in the rat race for decades, it is time to try something
different.</p>
<p>We live in the belief that what is good for “the machine,” is good for the
individual - be it capitalism, the government, or society. What if we worked on
the basis of the opposite.</p>
<blockquote>
<p>Not all those who wander are lost.<br>
— J. R. R. Tolkien</p></blockquote>
]]></content:encoded></item><item><title>Collaboration</title><link>https://icle.es/2023/11/21/muster/</link><pubDate>Tue, 21 Nov 2023 00:00:00 +0000</pubDate><guid>https://icle.es/2023/11/21/muster/</guid><description>&lt;p>Collaboration is one of the most powerful things that anyone can do. In a lot of
media, where there is “good” vs “evil,” a common theme is that the “evil” side
is dictatorial or working largely alone (e.g. Thanos), with one person making
all the calls, while the “good side” (e.g. The Avengers) tend to be
collaborative. The more people that are involved in a collaboration, the more
powerful the outcome can be.&lt;/p></description><content:encoded><![CDATA[<p>Collaboration is one of the most powerful things that anyone can do. In a lot of
media, where there is “good” vs “evil,” a common theme is that the “evil” side
is dictatorial or working largely alone (e.g. Thanos), with one person making
all the calls, while the “good side” (e.g. The Avengers) tend to be
collaborative. The more people that are involved in a collaboration, the more
powerful the outcome can be.</p>
<p>You don’t have to take my word for it.
<a href="https://www.forbes.com/sites/katevitasek/">Kate Vitasek</a> on Forbes talks about
<a href="https://www.forbes.com/sites/katevitasek/2022/08/29/widespread-collaboration-will-make-the-world-a-better-place/">how widespread collaboration will make the world a better place</a>
including some excellent examples.</p>
<p>Personally, there are few things that bring me greater joy or a sense of
fulfillment than being involved in something collaborative. It can be as simple
as a tête-à-tête or as complex as a large scale project of some form. I love
watching the efforts of a group of people come together into glorious fruition.
Perhaps that’s why I ended up leading teams, often delivering incredibly
difficult, apparently impossible challenges.</p>
<p>One severe burnout and several adventures later, I am at a place in my life
where I am pondering next steps. Having run my own company for almost two
decades, I have a personal style of working, which makes working in a corporate
environment more challenging for me. I would like to start my own company again,
perhaps on the basis of a product, either of my own, or in partnership with
someone else.</p>
<p>Since I have little to no capital (I put almost all of it into buying a house,
and the rest went into what we’ll call some poor investment choices), I figured
to start with something small. Admittedly, I might run out of money before I get
it completed, but I intend to give it a decent shot.</p>
<p>I believe in and follow Lean methodologies, BDD, TDD, DevOps, and most
importantly on putting people first. In every job before, I have had to make
compromises because the focus was so heavily on commercials. I am taking this
opportunity to put my money where my mouth is and follow the methodologies with
faith that the commercials will work itself out.</p>
<p>For the product itself, I decided to try and build something like
<a href="https://axolo.co/">Axolo</a>. I used Axolo at my previous company and absolutely
loved it. It is a great <em>collaborative</em> tool that really eases the peer review
process. I have only one (but major) complaint about Axolo and that’s the price.
$8 per user per month feels incredibly overpriced, particularly when you
consider that GitHub charges $4 per month and Slack charge $7.25.</p>
<p>Most businesses consider how they can maximise revenue – how to exploit a
marketplace. This is at odds with my people-first philosophy, and I want to turn
it around. No exploitation of the market and no maximising of revenue. I want to
know what you want – well, what my customers (hopefully, including you at some
point) want. I want to consider what a fair fee for the service is – not “what
it’s worth” (I am looking at you pharma companies). At its extreme, could the
cost per seat be purely the running costs + a <em>reasonable</em> markup? Product
development could be funded by an additional revenue stream where the interested
users pay into the features they are interested in – essentially democratising
the roadmap.</p>
<p>I also believe in transparency, so I will also aim to be as transparent about
everything as possible. While I want this to work, with my limited capital, it
is possible that I will run out of money before this starts to make enough money
and I might have to go to plan B and find myself a contract or permanent role.
At that point, it is possible that any updates here will quickly stop.</p>
<p>In that event, I will consider open sourcing whatever I have completed so that
it can be put to good use.</p>
<hr>
<p>On that note, I should probably introduce myself. I am
<a href="https://www.linkedin.com/in/shriramshrishrikumar/">Shri</a> and I ran kraya, a
digital technology company for 15+ years. I have extensive experience in
leadership and tech, having played a huge range of roles within the digital
sector including CEO, CTO, Technical Architect, Software Engineer, Systems
Engineer, DevOps etc. etc. etc.</p>
<p>As I mentioned at the start, I love collaboration and feel that it’s my
superpower – or at least my favourite power. I would love to hear your thoughts
– please
<a href="https://www.linkedin.com/in/shriramshrishrikumar/">ping me a message on LinkedIn</a>
and I’ll do my best to respond.</p>
]]></content:encoded></item><item><title>How to run your lambda code locally as its role (for testing)</title><link>https://icle.es/2023/11/14/automated-testing-of-aws-lambda-locally/</link><pubDate>Tue, 14 Nov 2023 14:23:37 +0000</pubDate><guid>https://icle.es/2023/11/14/automated-testing-of-aws-lambda-locally/</guid><description>&lt;p>While AWS Lambda is fantastic in providing a serverless platform with few
worries about maintaining servers, it is not the easiest to test in an automated
fashion with rapid feedback.&lt;/p>
&lt;p>You could write end to end tests, but it means a deployment after each change
and then checking the logs to see what failed. Even if you use iac
(terraform/pulumi), the deployment will take seconds or a minute or two - not
exact rapid test feedback.&lt;/p>
&lt;p>What I have been doing is to set up a hook which is called from the lambda
handler, which can also be called locally. Within the test, I then assume the
role that runs the lambda and then test the hook.&lt;/p>
&lt;p>This mechanism allows to me easily test that the permissions are set up
correctly and that details are in place for the code to work.&lt;/p>
&lt;p>For the full end to end test, I then have a simple smoke test or two.&lt;/p>
&lt;p>The code samples are in golang(only because it happens to be my current language
of choice), but the idea should be equally applicable in other languages.&lt;/p></description><content:encoded><![CDATA[<p>While AWS Lambda is fantastic in providing a serverless platform with few
worries about maintaining servers, it is not the easiest to test in an automated
fashion with rapid feedback.</p>
<p>You could write end to end tests, but it means a deployment after each change
and then checking the logs to see what failed. Even if you use iac
(terraform/pulumi), the deployment will take seconds or a minute or two - not
exact rapid test feedback.</p>
<p>What I have been doing is to set up a hook which is called from the lambda
handler, which can also be called locally. Within the test, I then assume the
role that runs the lambda and then test the hook.</p>
<p>This mechanism allows to me easily test that the permissions are set up
correctly and that details are in place for the code to work.</p>
<p>For the full end to end test, I then have a simple smoke test or two.</p>
<p>The code samples are in golang(only because it happens to be my current language
of choice), but the idea should be equally applicable in other languages.</p>
<h1 id="assuming-the-role">Assuming The Role</h1>
```go
  roleToAssume := os.Getenv("AUTH_LAMBDA_ROLE_ARN")

    ctx := context.TODO()
    cfg, err := config.LoadDefaultConfig(ctx)

    if err != nil {
        logger.Fatal("error: ", err)
    }
    // Create the credentials from AssumeRoleProvider to assume the role
    // referenced by the "myRoleARN" ARN using the MFA token code provided.
    creds := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), roleToAssume)

    logger.Debugf("creds: %v", creds)

    cfg.Credentials = aws.NewCredentialsCache(creds)
```
<p>the cfg is then passed into the <code>New</code> method for the resource you are interested
in. e.g.:</p>
```go
   ssmClient := ssm.NewFromConfig(cfg)
```
<h1 id="working-example">Working Example</h1>
<p>You can find a full, working example test in
<a href="https://github.com/drone-ah/wordsonsand">my github repo</a> under
<a href="https://github.com/drone-ah/wordsonsand/tree/main/post/2023/11/autolambdatest">post/2023/11/autolambdatest</a></p>
<p>NOTE: It WILL automatically try and deploy a role and a ssm parameter, and it
will delete it after the test.</p>
<p>The <code>BeforeSuite</code> will deploy the minimum configuration to be able to run the
test, and the <code>AfterSuite</code> will destroy the same stack.</p>
<p>You will likely need to log into pulumi to get this test to work.</p>
<p>If you run into permissions issue for AssumeRole, read on.</p>
<h1 id="assumerole-permissions">AssumeRole Permissions</h1>
<p>For this to work, the user running the tests need to have permissions to
<code>AssumeRole</code>.</p>
<p>There are two steps to this. The first part is to allow &quot;anyone&quot; to
<code>AssumeRole</code> the relevant role:</p>
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<your-account-id>:root"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```
<p>This will allow &quot;any user&quot; to assume the role, as long as they have the
permission to do so.</p>
<p><code>arn:aws:iam::[your-account]:root</code> is a special user the represents the account
(and the non-IAM root user). Since IAM (user, roles etc.) exists under the
&quot;root&quot; account, all calls are also authenticated by this account - i.e. all
users, roles etc. in IAM is also this account. There is
<a href="https://www.reddit.com/r/aws/comments/oorjl2/what_exactly_is_the_root_iam_principal/">a post on reddit discussing what exactly the root iam principal is for more information</a></p>
<p>Finally, unless you have the <code>Administrator</code>Access policy set against your
account, you will also need to attach a policy to the relevant group (or your
user) that grants permissions to call <code>sts:AssumeRole</code> (or <code>*</code>)</p>
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "123",
      "Effect": "Allow",
      "Action": ["sts:AssumeRole"],
      "Resource": ["arn:aws:iam::123456789012:role/desired-role"]
    }
  ]
}
```
<p>You can of course, also use <code>*</code> for Resource above to allow the user/group to
Assume Any role. In practice, you might want to automate this as part of the
creation of the relevant roles. (i.e. create the role, then give the relevant
group permissions to Assume that role).</p>]]></content:encoded></item><item><title>Unable to write to $HOME/.pulumi/credentials.json during bazel test</title><link>https://icle.es/2023/11/14/unable-to-write-to-home-pulumi-credentials-json-during-bazel-test/</link><pubDate>Tue, 14 Nov 2023 13:46:46 +0000</pubDate><guid>https://icle.es/2023/11/14/unable-to-write-to-home-pulumi-credentials-json-during-bazel-test/</guid><description>&lt;h1 id="the-problem">The Problem&lt;/h1>
&lt;p>You can add it to your ~/.bazelrc (it needs the path to be absolute)&lt;/p>
&lt;p>From our integration tests, we run &lt;code>pulumi stack output&lt;/code> (or in some cases
&lt;code>pulumi up&lt;/code>) through the automation API before we run the tests so that we can&lt;/p>
&lt;ul>
&lt;li>Confirm that the stack is up&lt;/li>
&lt;li>Get the relevant parameters (actual names of lambdas / dynamo db tables etc.)&lt;/li>
&lt;/ul>
&lt;p>However, since we use bazel for our tests, we ran into a small problem in that
Bazel (rightly) prevents the tests from writing to anything outside the sandbox.
This restrictions results in this error&lt;/p>
```
error: open /home/&lt;username>/.pulumi/credentials.json: read-only file system
```</description><content:encoded><![CDATA[<h1 id="the-problem">The Problem</h1>
<p>You can add it to your ~/.bazelrc (it needs the path to be absolute)</p>
<p>From our integration tests, we run <code>pulumi stack output</code> (or in some cases
<code>pulumi up</code>) through the automation API before we run the tests so that we can</p>
<ul>
<li>Confirm that the stack is up</li>
<li>Get the relevant parameters (actual names of lambdas / dynamo db tables etc.)</li>
</ul>
<p>However, since we use bazel for our tests, we ran into a small problem in that
Bazel (rightly) prevents the tests from writing to anything outside the sandbox.
This restrictions results in this error</p>
```
error: open /home/<username>/.pulumi/credentials.json: read-only file system
```
<h1 id="the-solution">The Solution</h1>
<p>The easiest way to solve this is to ask <code>bazel</code> to allow writing to this
location, which you can do with:</p>
```bash
bazel test ... --sandbox_writable_path=$HOME/.pulumi
```
<p><code>bazel</code> needs to the path to be absolute, so <code>~/.pulumi</code> won't work.</p>
<h1 id="automation">Automation</h1>
<p>It is annoying to add this flag into all the tests, but there is an way to
automatically add it to all tests. You can add it to <code>.bazelrc</code>. Due to the
aforementioned requirement for the path to be absolute, it is not possible to
put it into the git repo root. However, you can put it into your home directory
rool <code>.bazelrc</code></p>
<p><code>$HOME/.bazelrc</code></p>
```
test --sandbox_writable_path=/home/<your-username>/.pulumi
```]]></content:encoded></item><item><title>Separating out integration tests for golang in Bazel</title><link>https://icle.es/2023/11/13/separating-out-integration-tests-for-golang-in-bazel/</link><pubDate>Mon, 13 Nov 2023 13:47:53 +0000</pubDate><guid>https://icle.es/2023/11/13/separating-out-integration-tests-for-golang-in-bazel/</guid><description>&lt;p>There are many kinds of automated tests and two main kinds are integration tests
and unit tests.&lt;/p>
&lt;p>Unit tests are designed to run as fast as possible, so any slower processes like
databases are mocked out. While super helpful and powerful in terms of providing
confidence in the software, it should be only one part of the testing strategy.&lt;/p>
&lt;p>Integration tests, as is implied runs tests of the different part of the
software integrated together. Technically speaking, you can still mock out the
database and other slower layers to keep it running quickly. However, there is
value in including a database or other slower services in the process to test as
them in an automated fashion.&lt;/p>
&lt;p>What this does mean though, is that you want to be able to run only the unit
tests or run the integration tests as well. You might also want to have smoke
tests, which are run on your live production environment.&lt;/p></description><content:encoded><![CDATA[<p>There are many kinds of automated tests and two main kinds are integration tests
and unit tests.</p>
<p>Unit tests are designed to run as fast as possible, so any slower processes like
databases are mocked out. While super helpful and powerful in terms of providing
confidence in the software, it should be only one part of the testing strategy.</p>
<p>Integration tests, as is implied runs tests of the different part of the
software integrated together. Technically speaking, you can still mock out the
database and other slower layers to keep it running quickly. However, there is
value in including a database or other slower services in the process to test as
them in an automated fashion.</p>
<p>What this does mean though, is that you want to be able to run only the unit
tests or run the integration tests as well. You might also want to have smoke
tests, which are run on your live production environment.</p>
<h1 id="how">How</h1>
<p>You could define a separate target in your <code>BUILD</code> file with the unit tests and
let <code>gazelle</code> automatically build your default test target with all the tests. I
found this frustrating to use as I had to keep tweaking the dependencies
manually whenever anything changed (which happened often)</p>
<h2 id="tagging">Tagging</h2>
<p>The easiest way to achieve this for golang and bazel is to tag your source code
files. You can do this by adding the following to the top of your integration
test files</p>
<p><code>something_integration_test.go</code></p>
```go
//go:build integration_test

package somepackage
```
<p>You can pick any tag name you want instead of <code>integration_test</code> like
<code>integration</code>, <code>smoke_test</code> etc.</p>
<h2 id="ide-support">IDE Support</h2>
<p>You will likely need to add this source file into the IDEs build constraints to
get the IDE to treat it as a source file. In IntelliJ (IDEA/Goland), you will be
warned of this</p>
<p>
  <img src="/assets/2023/11/image.png" alt="idea screenshot, main_test.go is ignored by build tool">

</p>
<p>If you click <code>Edit settings</code>, you can add the tag in</p>
<p>
  <img src="/assets/2023/11/image-1.png" alt="set custom tags in build tags">

</p>
<p>When running <code>gazelle</code>, you want to include the files with these tags</p>
<h2 id="gazelle">Gazelle</h2>
```bash
bazel run //:gazelle -- -build_tags=integration_test
```
<p>If you have multiple tags, you can separate them with commas. This command will
generate a test target with all of the source files and its dependencies</p>
<h2 id="bazel-integration-on-test">Bazel Integration on test</h2>
<p>To run only the unit tests, you test as normal:</p>
```bash
bazel test ... # or the specific target, and it'll run only the unit tests
```
<p>To run the integration tests as well, include that tag</p>
```bash
bazel test ... --define  gotags=integration_test # Will run unit & integration tests
```
<h2 id="run-only-unit-tests">Run only Unit Tests</h2>
<p>This setup will currently not allow you to run ONLY the integration tests. To be
able to do that you'll need to add a <code>unit_test</code> tag to the unit test files so
that you can exclude them.</p>
```
something_test.go
```
```go
//go:build integration_test

package somepackage
```
<p>You can then run only the unit tests with</p>
```bash
bazel test … --define gotags=unit_test # Will run unit & integration tests
```
<p>Only the integration tests</p>
```bash
bazel test … --define gotags=integration_test # Will run unit & integration tests
```
<p>Or both:</p>
```bash
bazel test … --define gotags=unit_test,integration_test # Will run unit & integration tests
```
<h2 id="simpler-gazelle-command">Simpler gazelle command</h2>
<p>You can enable the tags by default in the <code>BUILD</code> file so that you don't have
to pass the tags into gazelle each time.</p>
<p><code>BUILD</code></p>
```starlark
# gazelle:build_tags unit_test,integration_test
```
<p>You can then just run <code>bazel run //:gazelle</code> which will run with these tags
enabled.</p>
<h1 id="sample-source">Sample Source</h1>
<p>You can find sample source code demonstrating this in
<a href="https://github.com/drone-ah/wordsonsand">my github repo</a>, under
<a href="https://github.com/drone-ah/wordsonsand/tree/main/post/2023/11/separatetests">post/2023/11/separatetests</a></p>]]></content:encoded></item><item><title>Streaming Progress With Pulumi Automation API</title><link>https://icle.es/2023/11/08/streaming-progress-with-pulumi-automation-api/</link><pubDate>Wed, 08 Nov 2023 12:38:32 +0000</pubDate><guid>https://icle.es/2023/11/08/streaming-progress-with-pulumi-automation-api/</guid><description>&lt;p>When using the pulumi automation API, you lose some of the niceties of the
pulumi CLI, like having to set up command line args processing and the output is
not as friendly or pretty as before. It also doesn't stream the output - though
this one is easier to fix.&lt;/p>
&lt;p>This is lifted straight out of
&lt;a href="https://github.com/pulumi/automation-api-examples/blob/3114b754ea84ebd0cc1e1b67f128df75795bd4c3/go/local_program/automation/main.go#L74C2-L82C3">their golang example code&lt;/a>,
so if you're working in another language - you should be able to find the
relevant code in the same repo&lt;/p></description><content:encoded><![CDATA[<p>When using the pulumi automation API, you lose some of the niceties of the
pulumi CLI, like having to set up command line args processing and the output is
not as friendly or pretty as before. It also doesn't stream the output - though
this one is easier to fix.</p>
<p>This is lifted straight out of
<a href="https://github.com/pulumi/automation-api-examples/blob/3114b754ea84ebd0cc1e1b67f128df75795bd4c3/go/local_program/automation/main.go#L74C2-L82C3">their golang example code</a>,
so if you're working in another language - you should be able to find the
relevant code in the same repo</p>
```go
   // wire up our update to stream progress to stdout
    stdoutStreamer := optup.ProgressStreams(os.Stdout)

    // run the update to deploy our fargate web service
    res, err := stack.Up(ctx, stdoutStreamer)
    if err != nil {
        fmt.Printf("Failed to update stack: %v\n\n", err)
    }
```
]]></content:encoded></item><item><title>How to get current Function URL (aws-lambda + golang)</title><link>https://icle.es/2023/11/08/how-to-get-current-function-url-aws-lambda-golang/</link><pubDate>Wed, 08 Nov 2023 12:10:16 +0000</pubDate><guid>https://icle.es/2023/11/08/how-to-get-current-function-url-aws-lambda-golang/</guid><description>&lt;p>When deploying a function lambda that needs details of its own function URL.
It's an OAuth Callback, and needs to calculate the redirect. There are possible
security issues doing it this way, so will switch to http gateway on launch. In
the meantime, though, I ran into a bit of a chicken and egg problem.&lt;/p>
&lt;p>In Pulumi, the function URL is created after the function (and even otherwise),
I can't pass the output of the lambda (or lambdaFunctionUrl) back in as an
environment variable. Fortunately, there is an easy way to pick up the Function
URL (or the function name for that matter) - if you know how ;)&lt;/p></description><content:encoded><![CDATA[<p>When deploying a function lambda that needs details of its own function URL.
It's an OAuth Callback, and needs to calculate the redirect. There are possible
security issues doing it this way, so will switch to http gateway on launch. In
the meantime, though, I ran into a bit of a chicken and egg problem.</p>
<p>In Pulumi, the function URL is created after the function (and even otherwise),
I can't pass the output of the lambda (or lambdaFunctionUrl) back in as an
environment variable. Fortunately, there is an easy way to pick up the Function
URL (or the function name for that matter) - if you know how ;)</p>
```wp-block-syntaxhighlighter-code
   domainName := request.RequestContext.DomainName
    funcName := os.Getenv("AWS_LAMBDA_FUNCTION_NAME")
    return fmt.Sprintf("Domain: %s, funcName: %s", domainName, funcName), nil
```
<p>There are other
<a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime">defined lambda function environment variables</a>
as well that you can use.</p>
]]></content:encoded></item><item><title>Bazel + Pulumi Automation API. Deploying an inline stack along with Pulumi.[stack].yaml</title><link>https://icle.es/2023/11/07/bazel-pulumi-automation-api-deploying-an-inline-stack-along-with-pulumi-yaml/</link><pubDate>Tue, 07 Nov 2023 15:57:52 +0000</pubDate><guid>https://icle.es/2023/11/07/bazel-pulumi-automation-api-deploying-an-inline-stack-along-with-pulumi-yaml/</guid><description>&lt;p>I believe in CI/CD/CD as in Continuous, integration, delivery and deployment. As
part of this, I am setting up a workflow where on merge to develop (or
main/trunk), the deployment is triggered automatically. Pulumi deploys the
current state of code and infrastructure through GitHub actions and
&lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services">OpenID Connect(OIDC)&lt;/a>
as part of the GitHub Action.&lt;/p>
&lt;p>I used to configure Pulumi to be triggered directly from the build process, but
bazel (as far I know), does not support Pulumi. When I used pants, there was a
custom module, developed by one of the community members which did support
pulumi (You might have to ask in the slack channel if you're interested), but
they stopped maintaining it as they moved to the Pulumi Automation API.&lt;/p></description><content:encoded><![CDATA[<p>I believe in CI/CD/CD as in Continuous, integration, delivery and deployment. As
part of this, I am setting up a workflow where on merge to develop (or
main/trunk), the deployment is triggered automatically. Pulumi deploys the
current state of code and infrastructure through GitHub actions and
<a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services">OpenID Connect(OIDC)</a>
as part of the GitHub Action.</p>
<p>I used to configure Pulumi to be triggered directly from the build process, but
bazel (as far I know), does not support Pulumi. When I used pants, there was a
custom module, developed by one of the community members which did support
pulumi (You might have to ask in the slack channel if you're interested), but
they stopped maintaining it as they moved to the Pulumi Automation API.</p>
<p>I am using Automation API from the start, and configuring a &quot;deployer&quot; per
product/project within the monorepo. The intention is for the deployer to be as
smart as possible - eventually <code>up</code>-ing only the stacks that have changes since
the last time- but that's a way down the line.</p>
<p>Another benefit from the Automation API is to pick up the stack outputs
automatically when running integration/e2e tests, making the test configuration
smoother.</p>
<p>The first step is to be able to define a stack within a product, hook it into
the main iac executable and have it working. My directory structure is roughly
as below: While I am using golang as my language of choice, it's probably not
hugely different in other languages.</p>
<ul>
<li>products
<ul>
<li>productA
<ul>
<li>auth
<ul>
<li>iac_auth
<ul>
<li>BUILD</li>
<li>deploy.go</li>
<li>Pulumi.dev.yaml</li>
<li>Pulumi.yaml</li>
</ul>
</li>
<li>OtherAuthModules</li>
</ul>
</li>
<li>iac
<ul>
<li>BUILD</li>
<li>main.go</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="iac_authbuild"><code>iac_auth/BUILD</code></h1>
```wp-block-syntaxhighlighter-code
# load etc.
go_library(
    name = "iac_auth",
    srcs = ["deploy.go"],
    data = glob([
        "Pulumi.*.yaml",
    ]) + [
        "Pulumi.yaml",
    ],
    visibility = ["//visibility:public"],
    deps = [
       # dependencies automated with gazelle
    ],
)
```
<h1 id="iac_authdeploy.go"><code>iac_auth/deploy.go</code></h1>
```wp-block-syntaxhighlighter-code
package iac_sync

import (
    "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

const moduleName = "auth"

func DeployGithubLambda(ctx *pulumi.Context) error {
    fmt.Println("Deploy")

    conf := config.Require(ctx, "key")
    fmt.Printf("conf: %s\n", conf) // Will successfully pick up the value if key is in Pulumi.<stack>.yaml

    // Actual deployment code here

    return nil
}
```
<h1 id="iacbuild"><code>iac/BUILD</code></h1>
```wp-block-syntaxhighlighter-code
# other config including standard config of :iac_lib by gazelle

go_binary(
    name = "iac",
    args = [
        "-iac_auth",
        "$(location //products/productA/auth/iac_auth)",
    ],
    data = [
        "//products/gitolink/productA/iac_auth",
    ],
    embed = [":iac_lib"],
    visibility = ["//visibility:public"],
)
```
<p>Worth noting the <code>args</code> bit, which is what we use to identify the path where the
<code>Pulumi.yaml</code> and <code>Pulumi.&lt;stack&gt;.yaml</code> files are:</p>
<h1 id="iacmain.go"><code>iac/main.go</code></h1>
```wp-block-syntaxhighlighter-code
package main

import (
    "context"
    "flag"
    "fmt"
    iacAuth "github.com/drone-ah/monorepo/products/gitolink/auth/iac_auth"
    "github.com/pulumi/pulumi/sdk/v3/go/auto"
    "path/filepath"
)

func main() {

    // Pick up the path from args
    var iacAuthPath = flag.String("iac_auth", "", "bin for iac_auth")
    flag.Parse()
    fmt.Printf("param iac: %s \n", *iacAuthPath)

    ctx := context.Background()

    projectName := "gitolink"
    stackName := "dev"

    stack, err := auto.NewStackInlineSource(ctx,
        stackName,
        projectName,
        iacAuth.DeployGithubLambda,
        // Define workdir for locatin of the Pulumi yaml files
        auto.WorkDir(filepath.Dir(*iacAuthPath)))

    preview, err := stack.Preview(ctx)

    fmt.Println(err)
    fmt.Println(preview)
}
```
<p>You might also have to use <code>RLocation</code> (see my previous post about
<a href="https://drone-ah.com/2023/11/01/including-a-built-artifact-in-another-target-bazel-golang/">including an artifact in another target</a>
for an example of this), though when I tried it, it was missing one of the yaml
files and I didn't investigate further.</p>]]></content:encoded></item><item><title>Including a built artifact in another target (Bazel, golang)</title><link>https://icle.es/2023/11/01/including-a-built-artifact-in-another-target-bazel-golang/</link><pubDate>Wed, 01 Nov 2023 19:42:30 +0000</pubDate><guid>https://icle.es/2023/11/01/including-a-built-artifact-in-another-target-bazel-golang/</guid><description>&lt;p>We use pulumi to do IaC and we use a monorepo with Bazel as the build tool. We
have out modules set out as following&lt;/p>
&lt;p>One of the requirements we have is to build a lambda module and then deploy it.
The lambda module is a target being built by Bazel (golang, but shouldn't
matter):&lt;/p>
```go
go_binary(
 name = "lambda_module",
 visibility = ["//visibility:public"],
)
```</description><content:encoded><![CDATA[<p>We use pulumi to do IaC and we use a monorepo with Bazel as the build tool. We
have out modules set out as following</p>
<p>One of the requirements we have is to build a lambda module and then deploy it.
The lambda module is a target being built by Bazel (golang, but shouldn't
matter):</p>
```go
go_binary(
    name = "lambda_module",
    visibility = ["//visibility:public"],
)
```
<p>We then have the iac module, which should get the built version of the above
module, so that it can then upload it into lambda</p>
```go
go_binary(
    name = "iac",
    args = [
        "-lambda_module",
        "$(location //products/productA/module/lambda_module)",
    ],
    data = ["//products/productA/module/lambda_module"],
    visibility = ["//visibility:public"],
)
```
<p>There are two key parameters here to note:</p>
<ul>
<li><code>args</code>: We generate the path to the target module using
<code> //products/productA/module/lambda_module)</code></li>
<li><code>data</code>: We use the data tag to ensure that the built output is included when
building/running this target</li>
</ul>
<p>We then need to use runfiles support within golang to be ablet to identify the
correct location for the built binary. The reason this part is complex is to be
able to support multiple operating systems. I should caveat that I have only got
this working on Linux, but Mac/Win shouldn't be too different.</p>
```go
package main

import (
    _ "embed"
    "flag"
    "fmt"
    "github.com/bazelbuild/rules_go/go/runfiles"
    "path/filepath"
)

func main() {

    var webhookAuth = flag.String("webhook_auth", "", "bin for webhook_auth")
    flag.Parse()
    fmt.Printf("param : %s \n", *webhookAuth)

    path, err := runfiles.Rlocation(fmt.Sprintf("workspace_name/%s", *webhookAuth))
    fmt.Printf("rLoc path: %s, err: %v \n", path, err)

    symlinks, err := filepath.EvalSymlinks(path)
    fmt.Printf("evaluated path: %s, err: %v \n", symlinks, err)

}
```
<p>We use the flag module to retrieve the path passed in as a runtime parameter</p>
<p>We use <code>runfiles.Rlocation</code> to pick up the &quot;real&quot; path to the file, prepending
the workspace name to the start. You can
<a href="https://bazel.build/rules/lib/globals/workspace#workspace">define the workspace name</a>
in the root WORKSPACE file with <code>workspace(name = &quot;workspace_name&quot;)</code></p>
<p>Finally, resolve the Symlink to get the actual file path</p>
<h2 id="references">References</h2>
<p>There are similar mechanisms to find the rLocation in other languages, a couple
of which are described in
<a href="https://docs.google.com/document/d/e/2PACX-1vSDIrFnFvEYhKsCMdGdD40wZRBX3m3aZ5HhVj4CtHPmiXKDCxioTUbYsDydjKtFDAzER5eg7OjJWs3V/pub">its design document</a></p>
<p>There is some documentation in <code>rules_go</code> around
<a href="https://github.com/bazelbuild/rules_go#how-do-i-access-go_binary-executables-from-go_test">accessing <code>go_binary</code> from <code>go_test</code></a>
which I referenced and updated to get the above example</p>
<p>I found the above link from
<a href="https://stackoverflow.com/questions/70193581/feed-bazel-output-to-another-bazel-rule">a stackoverflow post about feeding bazel output to another bazel rule</a></p>]]></content:encoded></item><item><title>Visual Studio, C++ &amp; Google Test</title><link>https://icle.es/2021/08/09/visual-studio-c-google-test/</link><pubDate>Mon, 09 Aug 2021 16:22:08 +0000</pubDate><guid>https://icle.es/2021/08/09/visual-studio-c-google-test/</guid><description>&lt;p>Using Visual Studio for C++ and Google Test seems like it should be absolutely
straightforward. I'm trying to do everything in Visual Studio, so it should
just set it up automatically right at the start when I create a project. Perhaps
I am spoilt from working primarily with Java for many years, but here I was,
trying to set it up and it took an inordinate amount of time.&lt;/p>
&lt;p>First things first, the way it works in C++, at least in Visual Studio, unlike
in Java is that the tests are set up in a separate project, but within the same
solution. I will try and remember all the steps I had to undertake to set this
up.&lt;/p>
&lt;p>This, however, is not the only requirement. When testing, since we can have only
one main function in the final executable, it also makes sense to put all of
your code, apart from the main function for your application in another project.&lt;/p></description><content:encoded><![CDATA[<p>Using Visual Studio for C++ and Google Test seems like it should be absolutely
straightforward. I'm trying to do everything in Visual Studio, so it should
just set it up automatically right at the start when I create a project. Perhaps
I am spoilt from working primarily with Java for many years, but here I was,
trying to set it up and it took an inordinate amount of time.</p>
<p>First things first, the way it works in C++, at least in Visual Studio, unlike
in Java is that the tests are set up in a separate project, but within the same
solution. I will try and remember all the steps I had to undertake to set this
up.</p>
<p>This, however, is not the only requirement. When testing, since we can have only
one main function in the final executable, it also makes sense to put all of
your code, apart from the main function for your application in another project.</p>
<p>In summary, you end up with three projects</p>
<ul>
<li>Your Solution
<ul>
<li>Static Library with all your code *except* the main function</li>
<li>Executable project with your main function (linked with your static library
above)</li>
<li>Tests project (again, linked with your static library above.</li>
</ul>
</li>
</ul>
<p>Once you have this set up, you will want to add a reference to the static
library from both the executable project and the tests project</p>
<p>
  <figure>
    <img src="/assets/2021/08/image.png" alt="Add Reference menu" class="figcaption-img">
    <figcaption>Add Reference</figcaption>
  </figure>

</p>
<p>Finally, you will also have to update the linker to link with the library
project. I am sure there is a better way of doing this, but I did it by right
clicking the <code>project -&gt; Properties -&gt; Linker -&gt; General</code>:</p>
<p><code>Additional Library Directories -&gt;</code> and adding in something like
&ldquo;<code>$(SolutionDir)&lt;lib-folder&gt;\$(IntermediateOutputPath)*.obj</code>&rdquo;</p>
<p>You may have to add the same into <code>Linker -&gt; Input -&gt; Additional Dependencies</code>.</p>
<p>Hope that helps</p>]]></content:encoded></item><item><title>How to fix terminal title after disconnecting from ssh</title><link>https://icle.es/2020/06/25/how-to-terminal-title-after-disconnecting-from-ssh/</link><pubDate>Thu, 25 Jun 2020 08:47:45 +0000</pubDate><guid>https://icle.es/2020/06/25/how-to-terminal-title-after-disconnecting-from-ssh/</guid><description>&lt;p>For some reason, ssh does not clean up after itself in terms of updating the
terminal title when you disconnect.&lt;/p>
&lt;p>Here is a simple solution, a combination of
&lt;a href="https://unix.stackexchange.com/a/341277/25975">https://unix.stackexchange.com/a/341277/25975&lt;/a> and
&lt;a href="https://unix.stackexchange.com/a/28520/25975">https://unix.stackexchange.com/a/28520/25975&lt;/a>&lt;/p>
&lt;p>Add the following functions into your &lt;code>~/.bashrc&lt;/code> It will push the current title
and icon into a stack and pop it afterwards.&lt;/p>
```bash
function ssh()
{
 # push current title and icon to stack
 echo -ne '\e[22t'
 # Execute ssh as expected
 /usr/bin/ssh "$@"
 # revert the window title after the ssh command
 echo -ne '\e[23t'
}
```
&lt;p>Restart bash / log out and back in, and it should work.&lt;/p></description><content:encoded><![CDATA[<p>For some reason, ssh does not clean up after itself in terms of updating the
terminal title when you disconnect.</p>
<p>Here is a simple solution, a combination of
<a href="https://unix.stackexchange.com/a/341277/25975">https://unix.stackexchange.com/a/341277/25975</a> and
<a href="https://unix.stackexchange.com/a/28520/25975">https://unix.stackexchange.com/a/28520/25975</a></p>
<p>Add the following functions into your <code>~/.bashrc</code> It will push the current title
and icon into a stack and pop it afterwards.</p>
```bash
function ssh()
{
    # push current title and icon to stack
    echo -ne '\e[22t'
    # Execute ssh as expected
    /usr/bin/ssh "$@"
    # revert the window title after the ssh command
    echo -ne '\e[23t'
}
```
<p>Restart bash / log out and back in, and it should work.</p>
<p>For security reasons, it is not possible to query the current title of the
terminal. However, with the following command, you can push the current one on
to a stack</p>
```bash
echo -ne '\e[22t'
```
<p>The title can then be set to anything, by ssh for example. You can then pop that
back from the stack using</p>
```bash
echo -ne '\e[23t'
```
]]></content:encoded></item><item><title>Getting Docker Desktop Working with WSL2 on Windows</title><link>https://icle.es/2020/05/28/getting-docker-desktop-working-with-wsl2-on-windows/</link><pubDate>Thu, 28 May 2020 17:29:52 +0000</pubDate><guid>https://icle.es/2020/05/28/getting-docker-desktop-working-with-wsl2-on-windows/</guid><description>&lt;p>I ran into several issues while trying to get this to work. Here are the steps I
had to complete to get it working. Hopefully this will save some hair on your
head ;)&lt;/p>
&lt;p>The main step is to go into the settings in Docker Desktop -&amp;gt; Resources and
make sure that your distribution is enabled for docker.&lt;/p>
&lt;p>
 &lt;img src="https://icle.es/assets/2020/05/image.png" alt="enable your distro on docker">

&lt;/p>
&lt;ol>
&lt;li>Make sure that you have no docker packages installed on your WSL
distribution. Docker Desktop will deploy its own binaries, and any
pre-existing binaries will confuse it. This issue exhibited itself for me
with errors related to missing files around credentials.&lt;/li>
&lt;li>Remove any &lt;code>DOCKER_HOST &lt;/code>environment variables. Docker Desktop will sort it
out. Docker kept hanging for me until I fixed this.&lt;/li>
&lt;li>If you want to use docker as non-root user, add yourself to the
&lt;code>docker &lt;/code>group.&lt;/li>
&lt;/ol>
&lt;p>Errors / Issues I ran into:&lt;/p></description><content:encoded><![CDATA[<p>I ran into several issues while trying to get this to work. Here are the steps I
had to complete to get it working. Hopefully this will save some hair on your
head ;)</p>
<p>The main step is to go into the settings in Docker Desktop -&gt; Resources and
make sure that your distribution is enabled for docker.</p>
<p>
  <img src="/assets/2020/05/image.png" alt="enable your distro on docker">

</p>
<ol>
<li>Make sure that you have no docker packages installed on your WSL
distribution. Docker Desktop will deploy its own binaries, and any
pre-existing binaries will confuse it. This issue exhibited itself for me
with errors related to missing files around credentials.</li>
<li>Remove any <code>DOCKER_HOST </code>environment variables. Docker Desktop will sort it
out. Docker kept hanging for me until I fixed this.</li>
<li>If you want to use docker as non-root user, add yourself to the
<code>docker </code>group.</li>
</ol>
<p>Errors / Issues I ran into:</p>
<p><code>docker.credentials.errors.InitializationError: docker-credential-desktop.exe not installed or not available in PATH</code> -
Fixed by 1 above.</p>
<p><code>docker-compose</code> from WSL2 errors out - Again, fixed by 1</p>
<p>Unable to run <code>docker</code> as non-root user - fixed by 3.</p>
<p>Docker hangs when run as non-root user - fixed by 2.</p>
]]></content:encoded></item><item><title>How do you access a specific version of a parameter from AWS Systems Manager Parameter Store</title><link>https://icle.es/2020/04/08/how-do-you-access-a-specific-version-of-a-parameter-from-aws-systems-manager-parameter-store/</link><pubDate>Wed, 08 Apr 2020 15:53:50 +0000</pubDate><guid>https://icle.es/2020/04/08/how-do-you-access-a-specific-version-of-a-parameter-from-aws-systems-manager-parameter-store/</guid><description>&lt;p>This was a bit tricky to find and doesn't seem to be well documented.&lt;/p>
```js
// version number below can be a number, i.e. 3 or a label
let params = {
 Name: "/path/to/parameter:&lt;version-number>",
 WithDecryption: false,
};

ssm.getParameter(params, function (err, data) {
 if (err) console.error(err, err.stack);
 else console.log(data);
});
```</description><content:encoded>&lt;p>This was a bit tricky to find and doesn't seem to be well documented.&lt;/p>
```js
// version number below can be a number, i.e. 3 or a label
let params = {
  Name: "/path/to/parameter:&lt;version-number>",
  WithDecryption: false,
};

ssm.getParameter(params, function (err, data) {
  if (err) console.error(err, err.stack);
  else console.log(data);
});
```
</content:encoded></item><item><title>Getting vuex-module-decorators to work in nuxt</title><link>https://icle.es/2020/04/03/getting-vuex-module-decorators-to-work-in-nuxt/</link><pubDate>Fri, 03 Apr 2020 10:10:28 +0000</pubDate><guid>https://icle.es/2020/04/03/getting-vuex-module-decorators-to-work-in-nuxt/</guid><description>&lt;p>There are a few caveats to integrating
&lt;a href="https://github.com/championswimmer/vuex-module-decorators">vuex-module-decorators&lt;/a>
with nuxt. The first steps are described in the
&lt;a href="https://github.com/championswimmer/vuex-module-decorators#accessing-modules-with-nuxtjs">README&lt;/a>
(although I missed it because it nuxt was in the small text).&lt;/p>
&lt;p>In addition to this, according to
&lt;a href="https://github.com/championswimmer/vuex-module-decorators/issues/179">issue #179&lt;/a>,
there are a few other caveats&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/championswimmer/vuex-module-decorators/issues/179#issuecomment-533853333">The file name has to be the same as the module name&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/championswimmer/vuex-module-decorators/issues/179#issuecomment-549326864">The store should go in ~/store, not ~/store/modules&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/championswimmer/vuex-module-decorators/issues/179">You have to &lt;strong>export default&lt;/strong> your module classes&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>There are a few caveats to integrating
<a href="https://github.com/championswimmer/vuex-module-decorators">vuex-module-decorators</a>
with nuxt. The first steps are described in the
<a href="https://github.com/championswimmer/vuex-module-decorators#accessing-modules-with-nuxtjs">README</a>
(although I missed it because it nuxt was in the small text).</p>
<p>In addition to this, according to
<a href="https://github.com/championswimmer/vuex-module-decorators/issues/179">issue #179</a>,
there are a few other caveats</p>
<ul>
<li><a href="https://github.com/championswimmer/vuex-module-decorators/issues/179#issuecomment-533853333">The file name has to be the same as the module name</a></li>
<li><a href="https://github.com/championswimmer/vuex-module-decorators/issues/179#issuecomment-549326864">The store should go in ~/store, not ~/store/modules</a></li>
<li><a href="https://github.com/championswimmer/vuex-module-decorators/issues/179">You have to <strong>export default</strong> your module classes</a></li>
</ul>
]]></content:encoded></item><item><title>List of Installed Programs on Windows from java</title><link>https://icle.es/2019/03/17/list-of-installed-programs-on-windows-from-java/</link><pubDate>Sun, 17 Mar 2019 15:13:53 +0000</pubDate><guid>https://icle.es/2019/03/17/list-of-installed-programs-on-windows-from-java/</guid><description>&lt;p>I was recently in need of a way to pick up the list of installed software on a
windows computer from Java. It was shrouded in a veil a mystery. There did not
seem to be a functional call that I could make, which makes sense since Java is
cross-platform and there is not universal way to pick up all the installed
packages on an OS.&lt;/p>
&lt;p>In fact, picking up all the installed packages on Windows seems be a bit
cryptic. There are API calls you can make and I considered JNI. I suspect this
might be a superior solution, but I haven't tried it and I read that it may be
slow.&lt;/p></description><content:encoded><![CDATA[<p>I was recently in need of a way to pick up the list of installed software on a
windows computer from Java. It was shrouded in a veil a mystery. There did not
seem to be a functional call that I could make, which makes sense since Java is
cross-platform and there is not universal way to pick up all the installed
packages on an OS.</p>
<p>In fact, picking up all the installed packages on Windows seems be a bit
cryptic. There are API calls you can make and I considered JNI. I suspect this
might be a superior solution, but I haven't tried it and I read that it may be
slow.</p>
<p>After much research, I came across
<a href="https://github.com/mavenlin/ListPrograms">ListPrograms.</a> My initial thought was
to link to it using JNI. However, it seemed simple enough to warrant a rewrite
in Java if I could access the registry somehow.</p>
<p>This is where <a href="https://github.com/java-native-access/jna">JNA</a> and the
<a href="https://java-native-access.github.io/jna/4.2.0/com/sun/jna/platform/win32/Advapi32Util.html">Advapi32Util</a>
class came in handy.</p>
<p>It didn't take me long to put together a quick replica of behaviour. I skipped
out the part about user installed programs because it isn't relevant for me
(yet).</p>
<p>I also missed out issues which will be around when running it as part of a 32bit
VM within a 64bit OS.</p>
<p>You can find <a href="https://github.com/drone-ah/JavaListApps">JavaListApps at GitHub</a></p>
]]></content:encoded></item><item><title>What I learnt developing a small JavaFX App</title><link>https://icle.es/2018/06/26/what-i-learnt-developing-a-small-javafx-app-wip/</link><pubDate>Tue, 26 Jun 2018 11:18:38 +0000</pubDate><guid>https://icle.es/2018/06/26/what-i-learnt-developing-a-small-javafx-app-wip/</guid><description>&lt;h1 id="introduction">Introduction&lt;/h1>
&lt;p>This is a collection of the things I learnt developing a simple JavaFX app over
the last month or two. My background is very much in Java EE with decades of
experience building high end, high-performance ticketing systems. This means
that my expectation from a development environment is relatively high. There are
many optional components in here that I find worthwhile setting up at the start,
but are not necessary&lt;/p></description><content:encoded><![CDATA[<h1 id="introduction">Introduction</h1>
<p>This is a collection of the things I learnt developing a simple JavaFX app over
the last month or two. My background is very much in Java EE with decades of
experience building high end, high-performance ticketing systems. This means
that my expectation from a development environment is relatively high. There are
many optional components in here that I find worthwhile setting up at the start,
but are not necessary</p>
<h2 id="tools">Tools</h2>
<h3 id="maven">Maven</h3>
<p>One of the most useful tools I have found while working with Java is maven. If
maven isn't a part of your build, have a look at it and re-evaluate that. I
have no doubt that maven has saved me hundreds, if not thousands of hours over
the last few years.</p>
<h3 id="javafx-scene-builder">JavaFX Scene Builder</h3>
<p>While this one has a bunch of issues and serious limitations, it can still be a
helpful tool. It helped me get a handle on the components available and placing
items.</p>
<h1 id="libraries">Libraries</h1>
<h2 id="testing">Testing</h2>
<p>I use <strong>junit5,</strong> but there are other options like test-ng which are equally
good. I use **Mockito **for mocking, but there are many other options like
PowerMock, JMockit, EasyMock, etc.</p>
<p>For UI Testing, you can use <strong>TestFX.</strong> I don't like UI work, so haven't done
much work with this.</p>
<pre><code>    org.junit.platform
    junit-platform-launcher
    1.2.0
    test


    org.junit.jupiter
    junit-jupiter-engine
    5.2.0
    test


    org.junit.vintage
    junit-vintage-engine
    5.2.0
    test



    org.mockito
    mockito-core
    2.18.3
    test
</code></pre>
<h2 id="logging">Logging</h2>
<p>I can&rsquo;t live without logging in any application. It can make troubleshooting
much easier, particularly when you&rsquo;ve deployed your app. <strong>log4j2</strong> is the main
logging framework out there. You can choose another one if you like, but I
strongly recommend having and using one.</p>
<pre><code>    org.apache.logging.log4j
    log4j-slf4j-impl
    2.11.0


    org.apache.logging.log4j
    log4j-api
    2.11.0


    org.apache.logging.log4j
    log4j-core
    2.11.0
</code></pre>
<h2 id="dependency-injection">Dependency Injection</h2>
<p>If you have working the Java EE Environment, you have almost certainly come
across
<a href="https://en.wikipedia.org/wiki/Inversion_of_control">Inversion of Control</a>, 
particularly in the form
of <a href="https://en.wikipedia.org/wiki/Dependency_injection">Dependency Injection</a>. I
love dependency injection. It helps with decoupling components and with testing.
I looked at various frameworks including
<a href="https://google.github.io/dagger/">Dagger 2</a>, <a href="https://spring.io/">Spring</a>,
<a href="https://github.com/google/guice">Guice</a>.</p>
<h3 id="dagger">Dagger</h3>
<p>The fully static (compile time) nature of Dagger 2 means that it doesn't gel
well with JavaFX which is very dynamic.</p>
<h3 id="spring">Spring</h3>
<p>I had worked with Spring many years ago and didn't want to tangle with a
behemoth for a small project. There are many components and loads of
functionality in spring and if you building a large and complex project, it
might be worth it.</p>
<h3 id="google-guice">Google Guice</h3>
<p>Google Guice is the framework that I ended up going with. It does have some
dependencies like Guava, but as it turned out, I Guava comes in handy for JavaFX
anyway. We don't need an entry in the pom.xml for this because of the following
dependency.</p>
<h2 id="gluon-ignite">Gluon Ignite</h2>
<p><a href="https://gluonhq.com/labs/ignite/">Gluon Ignite</a> was released by gluon labs to
integrate Dependency Injection frameworks with JavaFX. In other words, it ties
in the DI framework with the FXMLLoader so that it will load the correct
controller instances. Since I am using Guice, I needed the ignore-guice module.
If you add this into your pom.xml, it will also pull in google guice. Easy eh?
;)</p>
<pre><code>    com.gluonhq
    ignite-guice
    1.0.2
</code></pre>
<p>If you don't want to add another dependency, you could take a look at the code
in this module. It's fairly straightforward to integrate that manually into
your app. It's just easier to add in the dependency and let it do the magic</p>
<h2 id="event--publisher--subscriber-framework">Event / Publisher / Subscriber Framework</h2>
<p>It is likely that you will need an event framework or a publisher/subscriber
framework. The nature of GUI design and work is that it is an easy and simple
solution for a number of problems you will come across. Fortunately, we already
have an
<a href="https://github.com/google/guava/wiki/EventBusExplained">event framework</a> in
place within <a href="https://github.com/google/guava">Guava</a> which is a dependency of
Google Guice.</p>
<h2 id="project-lombok">Project Lombok</h2>
<p>Don't you love adding in a getter and a setter for each field? When you change
your fields, don't you love going in and updating all the getters and setters?
How about defining the long, pita to type types with generics of variables that
you are assigning from a method call? I mean the compiler can't possibly figure
that out by itself, right? How about writing out the toString, equals and
hashCode for each class? What do you mean no, you don't? You don't love these
tedious repetitive tasks of development? Good! You will love
<a href="https://projectlombok.org/setup/eclipse">Lombok.</a> Unfortunately, with Lombok,
it's not as simple as adding it into your pom.xml. Check out the install
instructions on their website for you IDE etc. You also need it in your pom.xml.</p>
<pre><code>    org.projectlombok
    lombok
    1.18.0
    provided
</code></pre>
<p>There is some controversy around Lombok. There is a
<a href="https://stackoverflow.com/questions/3852091/is-it-safe-to-use-project-lombok">good post on stackoverflow that covers some of these things in a reasonable fashion</a>.</p>
<h2 id="apache-commons">Apache Commons</h2>
<p>Last but certainly not least, we have
<a href="https://commons.apache.org/">apache commons</a>. This is a collection of libraries
rather than a single one. You know all those bits of code you write over and
over. Chances are that there is something in here that does it better and in a
simpler way.</p>
<h2 id="persistence">Persistence</h2>
<p>TBC</p>
<h1 id="packaging">Packaging</h1>
<p>TBC</p>
]]></content:encoded></item><item><title>yertoob</title><link>https://icle.es/2018/06/25/yertoob/</link><pubDate>Mon, 25 Jun 2018 00:00:00 +0000</pubDate><guid>https://icle.es/2018/06/25/yertoob/</guid><description>&lt;p>YerToob is an app that I am developing to alleviate the tedium of uploading
videos to YouTube. YerToob works by automating large parts of the YouTube upload
process.&lt;/p>
&lt;p>I currently upload a few videos a week and this involves:&lt;/p>
&lt;ul>
&lt;li>Copying and pasting various bits of text for:
&lt;ul>
&lt;li>The description&lt;/li>
&lt;li>The title&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Copying and pasting in the tags, and adding any additional ones&lt;/li>
&lt;li>Scheduling the video&lt;/li>
&lt;li>Adding to playlist&lt;/li>
&lt;li>Generating and uploading a thumbnail&lt;/li>
&lt;li>Scheduling a tweet&lt;/li>
&lt;/ul>
&lt;p>YerToob tries to automate all of the above. It works by defining templates for
the description and the title using tags.&lt;/p></description><content:encoded><![CDATA[<p>YerToob is an app that I am developing to alleviate the tedium of uploading
videos to YouTube. YerToob works by automating large parts of the YouTube upload
process.</p>
<p>I currently upload a few videos a week and this involves:</p>
<ul>
<li>Copying and pasting various bits of text for:
<ul>
<li>The description</li>
<li>The title</li>
</ul>
</li>
<li>Copying and pasting in the tags, and adding any additional ones</li>
<li>Scheduling the video</li>
<li>Adding to playlist</li>
<li>Generating and uploading a thumbnail</li>
<li>Scheduling a tweet</li>
</ul>
<p>YerToob tries to automate all of the above. It works by defining templates for
the description and the title using tags.</p>
<hr>
<h3 id="title-template-example">Title Template Example</h3>
```
Ep[episode:number] - [episode:name] | [playlist:name] | Let's Play Space Engineers
```
<hr>
<h3 id="description-template-example">Description Template Example</h3>
```
[episode:synopsis]

---------

A lone space engineer wakes up in his small ship, far away from a home he once knew. There are only a handful of people still awake, with a few thousand people across a few factions in cryo-sleep. A capital ship is being built to house these cryo chambers. Several engineers have been sent out into the galaxies to build a base.

Their mission is to colonise planets, ideally multiple planets, for each of the three factions. With a few hundred to a thousand people in each faction. Once all the habitats have been built, the engineer is to send a radio signal and await the arrival of the cargo.

With the heavy burden of this mission on their shoulders, this space engineer wakes up, caught in the gravitational pull of a blue planet.

---------

Blog Post: https://drone-ah.com/2018/03/19/lets-play-space-engineers-pilot-series/
Playlist: https://www.youtube.com/playlist?list=PLQb-fOWHIdUzyM-bJllq3QpWdVGDBhC98
Twitter: https://twitter.com/drone_ah

Space Engineers Genome: https://www.genomised.com/games/space-engineers

Games like Space Engineers: https://www.gamecupid.com/games/space-engineers/games-like

Website: http://www.spaceengineersgame.com/

[episode:addendum1]
---------
Credits

https://freesound.org/people/kasa90/sounds/143203/

[episode:addendum2]
```
<hr>
<h3 id="tags">Tags</h3>
<p>There are a set of tags associated with each playlist and with each episode.
These are combined together and set against the video.</p>
<hr>
<h3 id="schedule">Schedule</h3>
<p>You can select which days of the week a video should be uploaded in each
playlist. You also select the time at which it should be published.</p>
<p>The app will then schedule them accordingly.</p>
<hr>
<h3 id="playlist">Playlist</h3>
<p>Each video is added to the relevant playlist automatically.</p>
<hr>
<h3 id="thumbnails">Thumbnails</h3>
<p>The app will pick up a screenshot from one folder, put the specified overlay
image on top, and then scan and overlay another image on top based on the
episode number. The thumbnail is then set against the video.</p>
<hr>
<h3 id="tweet">Tweet</h3>
<p>You define a tweet template:</p>
```
#LetsPlay @SpaceEngineersG | Ep [episode:number] - [episode:title]|
https://youtu.be/[episode:videoId] | #pcgaming
```
<p>Link up your Twitter account, and it will post that tweet at the scheduled time.</p>
<hr>
<h3 id="finding-videos-to-upload">Finding Videos to Upload</h3>
<p>For each playlist, you specify which folder the app should scan for new videos.
Each video in that folder is then uploaded. Each video needs to be tagged with
an episode number. For example, the 50th episode could be:</p>
```
[50] The 50th Episode.mp4
```
<p>If you haven&rsquo;t already defined the episode details in the app, it will create a
new episode with the title of &ldquo;The 50th Episode&rdquo; by removing the episode number
tag and the file extension.</p>
<p>This can be useful if you don&rsquo;t have much episode-specific information.</p>
<hr>
<h3 id="current-status">Current Status</h3>
<p>I am currently testing this app. If you are interested in further updates,
<a href="https://twitter.com/drone_ah">please follow me on Twitter.</a></p>
]]></content:encoded></item><item><title>Shrouded in a Cloud</title><link>https://icle.es/2018/06/24/shrouded-in-a-cloud/</link><pubDate>Sun, 24 Jun 2018 16:13:01 +0000</pubDate><guid>https://icle.es/2018/06/24/shrouded-in-a-cloud/</guid><description>&lt;p>Shrouded in a cloud,&lt;br>
Of mist, fog, haze,&lt;br>
memories of roses,&lt;br>
Pitter patter, pitter patter.&lt;/p>
&lt;p>Holding me tight,&lt;br>
A little too tight&lt;br>
Holding me close&lt;br>
Hold me closer&lt;/p>
&lt;p>A drop rolls down my face,&lt;br>
Another races to my chin,&lt;br>
One is stopped,&lt;br>
By upturned lips.&lt;/p>
&lt;p>Is it today or tomorrow,&lt;br>
Maybe it&amp;rsquo;s yesterday,&lt;br>
Is that the sun I see,&lt;br>
Or is it the moon?&lt;/p>
&lt;p>There&amp;rsquo;s the roses again,&lt;br>
Is that how rainbows smell?&lt;br>
Pitter patter, pitter patter,&lt;br>
All around me.&lt;/p></description><content:encoded><![CDATA[<p>Shrouded in a cloud,<br>
Of mist, fog, haze,<br>
memories of roses,<br>
Pitter patter, pitter patter.</p>
<p>Holding me tight,<br>
A little too tight<br>
Holding me close<br>
Hold me closer</p>
<p>A drop rolls down my face,<br>
Another races to my chin,<br>
One is stopped,<br>
By upturned lips.</p>
<p>Is it today or tomorrow,<br>
Maybe it&rsquo;s yesterday,<br>
Is that the sun I see,<br>
Or is it the moon?</p>
<p>There&rsquo;s the roses again,<br>
Is that how rainbows smell?<br>
Pitter patter, pitter patter,<br>
All around me.</p>
]]></content:encoded></item><item><title>Shipwrecked | Let's Play The Azure Isle</title><link>https://icle.es/2018/06/01/shipwrecked-lets-play-the-azure-isle/</link><pubDate>Fri, 01 Jun 2018 09:01:43 +0000</pubDate><guid>https://icle.es/2018/06/01/shipwrecked-lets-play-the-azure-isle/</guid><description>&lt;p>[I stumbled across this game on
&lt;a href="https://gamejolt.com/games/theazureisle/295076">gamejolt.com&lt;/a> and tried it out.
It sucked me in and I was smitten. See what happened.]{style=&amp;ldquo;font-weight:400;&amp;rdquo;}&lt;/p>
&lt;p>#01 - Shipwrecked&lt;/p>
&lt;p> &lt;/p>
&lt;p>[We find ourselves shipwrecked on an island we don&amp;rsquo;t recognise. Let&amp;rsquo;s
explore]{style=&amp;ldquo;font-weight:400;&amp;rdquo;}&lt;/p>
&lt;p>#02 - Heading East&lt;/p>
&lt;p> &lt;/p>
&lt;p>What's over there in the east of the island? I get stuck, and think it's all
over, but there is more.&lt;/p></description><content:encoded><![CDATA[<p>[I stumbled across this game on
<a href="https://gamejolt.com/games/theazureisle/295076">gamejolt.com</a> and tried it out.
It sucked me in and I was smitten. See what happened.]{style=&ldquo;font-weight:400;&rdquo;}</p>
<p>#01 - Shipwrecked</p>
<p> </p>
<p>[We find ourselves shipwrecked on an island we don&rsquo;t recognise. Let&rsquo;s
explore]{style=&ldquo;font-weight:400;&rdquo;}</p>
<p>#02 - Heading East</p>
<p> </p>
<p>What's over there in the east of the island? I get stuck, and think it's all
over, but there is more.</p>
]]></content:encoded></item><item><title>Daily Meditation | Let's Meditate with Playne</title><link>https://icle.es/2018/06/01/daily-meditation-lets-meditate-with-playne/</link><pubDate>Fri, 01 Jun 2018 08:54:17 +0000</pubDate><guid>https://icle.es/2018/06/01/daily-meditation-lets-meditate-with-playne/</guid><description>&lt;p>I am involved in the beta testing for playne, a meditation game. While I had
meditated many many times in my life, I had never done it consistently. That is,
until Playne.&lt;/p>
&lt;p>I got curious about doing it as a series, and here it is&lt;/p>
&lt;p>This episodes talks about getting started with Playne&lt;/p>
&lt;p>#01 - Getting Started&lt;/p>
&lt;p> &lt;/p>
&lt;p>Let's get started with Playne&lt;/p>
&lt;p>#02 - Settling in&lt;/p>
&lt;p> &lt;/p>
&lt;p>Settling into meditating daily&lt;/p></description><content:encoded><![CDATA[<p>I am involved in the beta testing for playne, a meditation game. While I had
meditated many many times in my life, I had never done it consistently. That is,
until Playne.</p>
<p>I got curious about doing it as a series, and here it is</p>
<p>This episodes talks about getting started with Playne</p>
<p>#01 - Getting Started</p>
<p> </p>
<p>Let's get started with Playne</p>
<p>#02 - Settling in</p>
<p> </p>
<p>Settling into meditating daily</p>
<p>#03 - Time for meditation</p>
<p> </p>
<p>Making time for meditation is important</p>
<p>#04 - Layers of the mind</p>
<p> </p>
<p>The mind is like an onion - so many layers and it makes you cry.</p>
<p>#05 - Level 2</p>
<p> </p>
<p>We get to level 2</p>
<p>#06 - Positive Changes</p>
<p> </p>
<p>Have you started seeing positive changes?</p>
<p>#07 - One Week in</p>
<p> </p>
<p>That's one week of meditating daily. How do you feel?</p>
<p>#08 - Stick with it</p>
<p> </p>
<p>There are definitely benefits to sticking to meditating daily</p>
<p>#09 -</p>
]]></content:encoded></item><item><title>Unfolding the Story | Let's Play Epistory</title><link>https://icle.es/2018/05/28/unfolding-the-story-lets-play-epistory/</link><pubDate>Mon, 28 May 2018 10:11:37 +0000</pubDate><guid>https://icle.es/2018/05/28/unfolding-the-story-lets-play-epistory/</guid><description>&lt;p>The other day I stumbled across Origin All Access and saw that it had a bunch of
games I wanted to play. One of these was Epistory, which had been in my wishlist
for a long time. When trying the game out, I fell in love with it. It is
absolutely a treat, so I decided to go through it as part of a let's play.&lt;/p>
&lt;h3 id="01---signal-fires">#01 - Signal Fires&lt;/h3>
&lt;p> &lt;/p></description><content:encoded><![CDATA[<p>The other day I stumbled across Origin All Access and saw that it had a bunch of
games I wanted to play. One of these was Epistory, which had been in my wishlist
for a long time. When trying the game out, I fell in love with it. It is
absolutely a treat, so I decided to go through it as part of a let's play.</p>
<h3 id="01---signal-fires">#01 - Signal Fires</h3>
<p> </p>
<p>A meteor falls from the sky, and everything changes.</p>
<h3 id="02---forgotten-forest">#02 - Forgotten Forest</h3>
<p> </p>
<p>We find a dark forest, forgotten and resistant.</p>
<h3 id="03---cold-shoulder">#03 - Cold Shoulder</h3>
<p> </p>
<p>We witness another calamity, which once again changes everything.</p>
<h3 id="04---drowning-halls">#04 - Drowning Halls</h3>
<p> </p>
<p>Ice and water in these halls. Do we learn another language?</p>
<h3 id="05---spark">#05 - Spark</h3>
<p> </p>
<p>We stumble across Chapter 3 and another language</p>
<h3 id="06---creation-city">#06 - Creation City</h3>
<p> </p>
<p>Explore the rest of Creation City</p>
<h3 id="07---ice-mausoleum">#07 - Ice Mausoleum</h3>
<p> </p>
<p>We come across the Ice Mausoleum while exploring and delve into it.</p>
<h3 id="08---winds-of-change">#08 - Winds of Change</h3>
<p> </p>
<p>We run across Chapter 4 while exploring.</p>
<h3 id="09---shattered-isles">#09 - Shattered Isles</h3>
<p> </p>
<p>We go into the shattered isles and discover the fourth language.</p>
]]></content:encoded></item><item><title>Remembering Mars Beta 1 | Let's Play Memories of Mars</title><link>https://icle.es/2018/05/14/remembering-mars-beta-1-lets-play-memories-of-mars/</link><pubDate>Mon, 14 May 2018 14:20:58 +0000</pubDate><guid>https://icle.es/2018/05/14/remembering-mars-beta-1-lets-play-memories-of-mars/</guid><description>&lt;blockquote>
&lt;p>&amp;quot;100 years in the future, something happened on Mars. Once frequently
visited, it is now abandoned with some left behind. Explore and Survive the
Red Planet.&amp;quot;&lt;/p>
&lt;p>~ Memories of Mars&lt;/p>&lt;/blockquote>
&lt;p>When I saw the
&lt;a href="https://store.steampowered.com/app/644290/MEMORIES_OF_MARS/">game description on steam&lt;/a>,
I was intrigued. When they were giving out Beta keys, I volunteered and got
myself a key. Following are videos put together from maybe around 10 hours of
gameplay. Hope you enjoy&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>&quot;100 years in the future, something happened on Mars. Once frequently
visited, it is now abandoned with some left behind. Explore and Survive the
Red Planet.&quot;</p>
<p>~ Memories of Mars</p></blockquote>
<p>When I saw the
<a href="https://store.steampowered.com/app/644290/MEMORIES_OF_MARS/">game description on steam</a>,
I was intrigued. When they were giving out Beta keys, I volunteered and got
myself a key. Following are videos put together from maybe around 10 hours of
gameplay. Hope you enjoy</p>
<h3 id="01---exploring-mars">#01 - Exploring Mars</h3>
<p> </p>
<p>Random wanderings, musings and exploration of Mars.</p>
<h3 id="02---building-a-base">#02 - Building a base</h3>
<p> </p>
<p>This is mainly the highlights of me figuring out and building a base on Mars :)</p>
<h3 id="03---beta-1-highlights">#03 - Beta 1 Highlights</h3>
<p> </p>
<p>Highlights from around 6 hours of gameplay over the Beta 1 Weekend.</p>
]]></content:encoded></item><item><title>yertoob</title><link>https://icle.es/endeavours/yertoob/</link><pubDate>Sat, 12 May 2018 15:49:54 +0100</pubDate><guid>https://icle.es/endeavours/yertoob/</guid><description>&lt;p>While working on &lt;a href="https://icle.es/drone-ah-adventures.md">drone ah adventures&lt;/a>, I got pretty
sick of setting up all and uploading all the youtube videos.&lt;/p>
&lt;p>I spent a few weeks putting together a quick app to do that for me.&lt;/p>
&lt;p>I build it in &lt;a href="https://icle.es/tags/javafx">javafx&lt;/a> and it was far too clunky in the end. I dug
it out, but it doesn&amp;rsquo;t compile anymore.&lt;/p>
&lt;p>I&amp;rsquo;ll fix it and make it available, at some point&lt;/p></description><content:encoded><![CDATA[<p>While working on <a href="https://icle.es/drone-ah-adventures.md">drone ah adventures</a>, I got pretty
sick of setting up all and uploading all the youtube videos.</p>
<p>I spent a few weeks putting together a quick app to do that for me.</p>
<p>I build it in <a href="https://icle.es/tags/javafx">javafx</a> and it was far too clunky in the end. I dug
it out, but it doesn&rsquo;t compile anymore.</p>
<p>I&rsquo;ll fix it and make it available, at some point</p>
]]></content:encoded></item><item><title>Space Engineers Guides</title><link>https://icle.es/2018/04/13/space-engineers-guides/</link><pubDate>Fri, 13 Apr 2018 13:45:37 +0000</pubDate><guid>https://icle.es/2018/04/13/space-engineers-guides/</guid><description>&lt;h2 id="01---rdavs-ai-autominer-script">#01 - Rdav's AI Autominer Script&lt;/h2>
&lt;p>&lt;a href="https://youtu.be/">https://youtu.be/&lt;/a>_075xAh0VNM&lt;/p>
&lt;p>I had been pondering automated mining in Space Engineers for a long time and I
need ponder no longer. I stumbled across this script one fine morning and have
implemented it more than once. I absolutely love this script. This video covers
how to set it up and various things to consider for maximum mining efficiency.&lt;/p></description><content:encoded><![CDATA[<h2 id="01---rdavs-ai-autominer-script">#01 - Rdav's AI Autominer Script</h2>
<p><a href="https://youtu.be/">https://youtu.be/</a>_075xAh0VNM</p>
<p>I had been pondering automated mining in Space Engineers for a long time and I
need ponder no longer. I stumbled across this script one fine morning and have
implemented it more than once. I absolutely love this script. This video covers
how to set it up and various things to consider for maximum mining efficiency.</p>
]]></content:encoded></item><item><title>ZFS Deleting files doesn't free up space</title><link>https://icle.es/2018/04/12/zfs-deleting-files-doesnt-free-up-space/</link><pubDate>Thu, 12 Apr 2018 10:15:02 +0000</pubDate><guid>https://icle.es/2018/04/12/zfs-deleting-files-doesnt-free-up-space/</guid><description>&lt;p>So I have a proxmox server on which I run a few VMs and the other day it
completely ran out of space. This was because of overprovisioning through thin
volumes.&lt;/p>
&lt;p>After much head scratching and metaphorically banging my head against a wall,
here are the things I learnt.&lt;/p></description><content:encoded><![CDATA[<p>So I have a proxmox server on which I run a few VMs and the other day it
completely ran out of space. This was because of overprovisioning through thin
volumes.</p>
<p>After much head scratching and metaphorically banging my head against a wall,
here are the things I learnt.</p>
<h2 id="empty-trash">Empty Trash</h2>
<h3 id="local-trash">Local Trash</h3>
<p>Make sure that have emptied the trash on the VMs .Ubuntu has this issue and so
might other distributions</p>
<h3 id="network-trash">Network Trash</h3>
<p>If you have SAMBA enabled on your VMs make sure that the Recycle Bin is not
enabled. I have openmediavault running on a VM and I had to go through and
disable the Recycling Bin. Make sure that the Recycle bin is emptied. They are
hidden folders in the root of your shares.</p>
<h2 id="correct-driver--settings">Correct Driver &amp; Settings</h2>
<ul>
<li>When setting up the hard drive for your VM, make sure you use virtio-scsi (or
just scsi on the web interface).
<ul>
<li>If you disk is already set up using IDE or VirtIO,
<ul>
<li>Delete it. Don't worry, it's only deleting the link. The disk itself
will show up in the interface afterwards</li>
<li>Double click on the unattached disk and select SCSI and Discard</li>
<li>You might have to fix the references to the drive in the OS</li>
</ul>
</li>
</ul>
</li>
<li>On the Device Screen, make sure discard is selected.</li>
</ul>
<h2 id="trim">TRIM</h2>
<p>Configure the OS to send TRIM commands to the drive</p>
<h3 id="linux">Linux</h3>
<h4 id="on-mount">On Mount</h4>
<p>You can pass the parameter discard to any mountpoint and the correct TRIM
commands will be sent to the disk. <strong>HOWEVER</strong>, this is apparently a big
performance hit.</p>
<p>To do the actual trim, run</p>
```bash
$ fstrim
```
<p>OR to run fstrim on all supported drives</p>
```bash
$ fstrim -a
```
<p><a href="https://www.digitalocean.com/community/tutorials/how-to-configure-periodic-trim-for-ssd-storage-on-linux-servers">Digital Ocean has a detailed post about setting TRIM and setting up a schedule etc.</a></p>
<h3 id="windows">Windows</h3>
<p>My condolences! Also, I don&rsquo;t run Windows on any of my VM&rsquo;s so I have no
experience with it.</p>]]></content:encoded></item><item><title>First Run | Let's Play while True; learn() [DONE]</title><link>https://icle.es/2018/04/05/first-run-lets-play-while-true-learn/</link><pubDate>Thu, 05 Apr 2018 12:01:06 +0000</pubDate><guid>https://icle.es/2018/04/05/first-run-lets-play-while-true-learn/</guid><description>&lt;p>I stumbled across a let's play series on this game on YouTube and was
absolutely fascinated. I bought it and started playing it. It's in early access
and while it's a little rough around the edges, I am enjoying playing it so far&lt;/p>
&lt;h3 id="01---getting-started">#01 - Getting Started&lt;/h3>
&lt;p>&lt;a href="https://www.youtube.com/watch?v=PByyKwJ-ouY">https://www.youtube.com/watch?v=PByyKwJ-ouY&lt;/a>&lt;/p>
&lt;p>Getting the hang of the game and getting involved with a startup.&lt;/p>
&lt;h3 id="02---self-driving-cars">#02 - Self Driving Cars&lt;/h3>
&lt;p>&lt;a href="https://www.youtube.com/watch?v=owx83tHERio">https://www.youtube.com/watch?v=owx83tHERio&lt;/a>&lt;/p>
&lt;p>Getting involved with self driving cars.&lt;/p></description><content:encoded><![CDATA[<p>I stumbled across a let's play series on this game on YouTube and was
absolutely fascinated. I bought it and started playing it. It's in early access
and while it's a little rough around the edges, I am enjoying playing it so far</p>
<h3 id="01---getting-started">#01 - Getting Started</h3>
<p><a href="https://www.youtube.com/watch?v=PByyKwJ-ouY">https://www.youtube.com/watch?v=PByyKwJ-ouY</a></p>
<p>Getting the hang of the game and getting involved with a startup.</p>
<h3 id="02---self-driving-cars">#02 - Self Driving Cars</h3>
<p><a href="https://www.youtube.com/watch?v=owx83tHERio">https://www.youtube.com/watch?v=owx83tHERio</a></p>
<p>Getting involved with self driving cars.</p>
<h3 id="03---optimisin">#03 - Optimisin'</h3>
<p><a href="https://www.youtube.com/watch?v=e9Hd2cV-7GE">https://www.youtube.com/watch?v=e9Hd2cV-7GE</a></p>
<p>Lots of optimisations.</p>
<h3 id="04---more-optimisin">#04 - More Optimisin'</h3>
<p> </p>
<p>Try my hand at more optimisations and also complete one more task.</p>
<h3 id="05--2147483648">#05 - $2,147,483,648</h3>
<p> </p>
<p>Fail at completing a task because it's just a little too slow. Run into a bug
that puts me billions of dollars in debt, effectively ending my playthrough.</p>
]]></content:encoded></item><item><title>Space Engineers Showcases</title><link>https://icle.es/2018/04/05/space-engineers-showcases/</link><pubDate>Thu, 05 Apr 2018 11:12:17 +0000</pubDate><guid>https://icle.es/2018/04/05/space-engineers-showcases/</guid><description>&lt;p>Here we have some of the items I have showcased&lt;/p>
&lt;h2 id="ships">Ships&lt;/h2>
&lt;h3 id="big-bertha---leviathan-mining-ship">Big Bertha - Leviathan Mining Ship&lt;/h3>
&lt;p> &lt;/p>
&lt;p>I got tired of the multiple mining trips I had to do. This frustration led me to
build this large mining ship. It took me several hours and more steel plates
than I could count. Hope you enjoy this tour&lt;/p></description><content:encoded><![CDATA[<p>Here we have some of the items I have showcased</p>
<h2 id="ships">Ships</h2>
<h3 id="big-bertha---leviathan-mining-ship">Big Bertha - Leviathan Mining Ship</h3>
<p> </p>
<p>I got tired of the multiple mining trips I had to do. This frustration led me to
build this large mining ship. It took me several hours and more steel plates
than I could count. Hope you enjoy this tour</p>
]]></content:encoded></item><item><title>Wet Feet | Let's Play Subnautica</title><link>https://icle.es/2018/03/26/wet-feet-lets-play-subnautica/</link><pubDate>Mon, 26 Mar 2018 10:23:46 +0000</pubDate><guid>https://icle.es/2018/03/26/wet-feet-lets-play-subnautica/</guid><description>&lt;p>I started watching a
&lt;a href="https://www.youtube.com/playlist?list=PLckQAgHLvx_Hm-Tmml-mR6BiXBNWCLTbz">Let's play series on Subnautica&lt;/a>
while I was doing
&lt;a href="http://drone-ah.com/2018/03/19/lets-play-space-engineers-pilot-series/">my own series on Space Engineers&lt;/a>.
I got so immersed in it that I stopped part way through and wanted to play the
game by myself. It seems to have a gripping story and absolutely gorgeous views.&lt;/p>
&lt;p>I dusted off my credit card, bought Subnautica and dived right into it. This is
what happened.&lt;/p>
&lt;p>I will be continuing both these series in parallel.&lt;/p></description><content:encoded><![CDATA[<p>I started watching a
<a href="https://www.youtube.com/playlist?list=PLckQAgHLvx_Hm-Tmml-mR6BiXBNWCLTbz">Let's play series on Subnautica</a>
while I was doing
<a href="http://drone-ah.com/2018/03/19/lets-play-space-engineers-pilot-series/">my own series on Space Engineers</a>.
I got so immersed in it that I stopped part way through and wanted to play the
game by myself. It seems to have a gripping story and absolutely gorgeous views.</p>
<p>I dusted off my credit card, bought Subnautica and dived right into it. This is
what happened.</p>
<p>I will be continuing both these series in parallel.</p>
<h3 id="01---getting-my-feet-wet">#01 - Getting my feet wet</h3>
<p><a href="https://www.youtube.com/watch?v=xZmipiqFrYs">https://www.youtube.com/watch?v=xZmipiqFrYs</a></p>
<p>In this episode, I get acquainted with the surroundings and craft myself a
knife. I also end up dying  :(</p>
<h3 id="02---let-there-be-light">#02 - Let There Be Light</h3>
<p><a href="https://www.youtube.com/watch?v=rJkxGAkeeL8&amp;t=1s">https://www.youtube.com/watch?v=rJkxGAkeeL8&t=1s</a></p>
<p>I craft a flashlight so I can see in the dark, which is a lot of places / time.
I go hunting for materials and end up blowing up :(</p>
<h3 id="03---dont-die">#03 - Don't Die</h3>
<p><a href="https://www.youtube.com/watch?v=S5XQ3hZ_2i4">https://www.youtube.com/watch?v=S5XQ3hZ_2i4</a></p>
<p>The main aim of this episode was to not die. Do I succeed? I also go find a
lifepod.</p>
<h3 id="04---lifepod-17">#04 - Lifepod 17</h3>
<p><a href="https://www.youtube.com/watch?v=6FugOP8Aczk">https://www.youtube.com/watch?v=6FugOP8Aczk</a></p>
<p>I start this episode trying to find some silver, and end up going to find
Lifepod 17.</p>
<h3 id="05---explorin">#05 - Explorin'</h3>
<p><a href="https://www.youtube.com/watch?v=jWCLYEl_zpU">https://www.youtube.com/watch?v=jWCLYEl_zpU</a></p>
<p>We continue our hunt for silver, lead and the final piece of the seamoth. Trying
to remain alive at the same time.</p>
<h3 id="06---the-sunbeam">#06 - The Sunbeam</h3>
<p><a href="https://www.youtube.com/watch?v=Yt0yU9c-TRw">https://www.youtube.com/watch?v=Yt0yU9c-TRw</a></p>
<p>We get a message from the Sunbeam telling us that they are about to land. Rescue
at last! or is it?</p>
<h3 id="07---lifepod-13">#07 - Lifepod 13</h3>
<p><a href="https://www.youtube.com/watch?v=l3YCJ8Z7Dhw">https://www.youtube.com/watch?v=l3YCJ8Z7Dhw</a></p>
<p>We head out to lifepod 13. On the way back, &quot;Detecting multiple leviathan class
lifeforms in the region. Are you certain whatever you're doing is worth it?&quot;</p>
<h3 id="08---the-aurora">#08 - The Aurora</h3>
<p><a href="https://www.youtube.com/watch?v=c0710Yo8Viw">https://www.youtube.com/watch?v=c0710Yo8Viw</a></p>
<p>We head over to the Aurora and manage to explore a part of it.</p>
<h3 id="09---time-capsules">#09 - Time Capsules</h3>
<p><a href="https://www.youtube.com/watch?v=5MV_W1HTbjU">https://www.youtube.com/watch?v=5MV_W1HTbjU</a></p>
<p>We head over to Second Officer Keens lifepod and find a few time capsules along
the way.</p>
<h3 id="10---close-encounters">#10 - Close Encounters</h3>
<p><a href="https://www.youtube.com/watch?v=kHbejQwsVuM">https://www.youtube.com/watch?v=kHbejQwsVuM</a></p>
<p>Try to head over to lifepod 12 which has sunk over 250m, but we don't make it
that far. Why not?</p>
<h3 id="11---land-ahoy">#11 - Land Ahoy</h3>
<p><a href="https://www.youtube.com/watch?v=ZYfkuaUCdLU">https://www.youtube.com/watch?v=ZYfkuaUCdLU</a></p>
<p>Head over to the co-ordinates provided by Second Officer Keen. Still scared of
water from the last episode.</p>
<h3 id="12---the-jellyshroom-habitat">#12 - The Jellyshroom Habitat</h3>
<p><a href="https://www.youtube.com/watch?v=RxHVvBqcm-I">https://www.youtube.com/watch?v=RxHVvBqcm-I</a></p>
<p>Follow the Degasi into the Jellyshroom cave where we discover some treasures.</p>
<h3 id="13---stasis-rifle">#13 - Stasis Rifle</h3>
<p><a href="https://youtu.be/3wxo_75jfoA">https://youtu.be/3wxo_75jfoA</a></p>
<p>We head back into the Jellyshroom cave looking for Lithium but ending up finding
a lot more.</p>
<h3 id="14---wanted-gel-sack">#14 - Wanted: Gel Sack</h3>
<p><a href="https://youtu.be/AGEHX0G4dq8">https://youtu.be/AGEHX0G4dq8</a></p>
<p>Realising how crucial these little things are, and regretting accidentally
eating a bunch of them, I try and find some. Do I succeed?</p>
<h3 id="15---some-base-building">#15 - Some Base Building</h3>
<p><a href="https://www.youtube.com/watch?v=g20Unqz5bZU">https://www.youtube.com/watch?v=g20Unqz5bZU</a></p>
<p>A little bit of base maintenance and upgrades. Making life easier ;)</p>
<h3 id="16---back-to-the-aurora">#16 - Back to the Aurora</h3>
<p><a href="https://www.youtube.com/watch?v=">https://www.youtube.com/watch?v=</a>_4Yq6BErfM4</p>
<p>We go back to the Aurora, trying to get back into the Prawn Bay.</p>
<h3 id="17---lifepod-4">#17 - Lifepod 4</h3>
<p><a href="https://www.youtube.com/watch?v=4Fjh-x79NiQ">https://www.youtube.com/watch?v=4Fjh-x79NiQ</a></p>
<p>We try to find Lifepod 4 which is in Reaper territory. Scary!</p>
<h3 id="18---avoiding-lifepod-12">#18 - Avoiding Lifepod 12</h3>
<p><a href="https://www.youtube.com/watch?v=RdFlpFSea8U">https://www.youtube.com/watch?v=RdFlpFSea8U</a></p>
<p>Terrified of going back to lifepod 12, I find something else to do.</p>
<h3 id="19---balls">#19 - Balls</h3>
<p><a href="https://www.youtube.com/watch?v=">https://www.youtube.com/watch?v=</a>_kMAntYaaps</p>
<p>I visit lifepod 12 (the balls). I then make my way to the Degasi habitat at 500m
and run into a field of blue balls. On my way back, Balls! What the hell was
that?</p>
<h3 id="20---prawn">#20 - PRAWN</h3>
<p><a href="https://www.youtube.com/watch?v=gR1Vb7XNJCg">https://www.youtube.com/watch?v=gR1Vb7XNJCg</a></p>
<p>Finally get the parts together to build a PRAWN suit.</p>
<h3 id="21---torpedoes">#21 - Torpedoes</h3>
<p><a href="https://www.youtube.com/watch?v=wg70y0OZq2w">https://www.youtube.com/watch?v=wg70y0OZq2w</a></p>
<p>Get down the 500m deep Degasi habitat, then decide I probably need some
torpedoes.</p>
<h3 id="22---crabsquid-ahoy">#22 - Crabsquid Ahoy</h3>
<p><a href="https://www.youtube.com/watch?v=">https://www.youtube.com/watch?v=</a>_MOd5cnGLTM</p>
<p>We head back to the 500m Degasi habitat, torpedoes armed and full of bravery.</p>
<h3 id="23---enforcement-platform">#23 - Enforcement Platform</h3>
<p>I try and find the island which shot down the sunbeam again and this time, I
succeed. Lots of exploration and finding lots of new things.</p>
<h3 id="24---enforcement-platform-pt-2">#24 - Enforcement Platform pt 2</h3>
<p>Offload all the loot and go back to the Enforcement Platform for more
explorin'. What is that alien device that needed an ion cube?</p>
<h3 id="25---what-are-you">#25 - What... are... you?</h3>
<p>This episode is full of surprises.</p>
<h3 id="26---knowing-me-knowing-yu">#26 - Knowing Me, Knowing Yu</h3>
<p>We go looking for Yu's lifepod and find some treasures.</p>
<h3 id="27---scanning-output">#27 - Scanning Output</h3>
<p>Set up a scanning output to try and find fragments a bit easier</p>
<h3 id="28---scanning-from-output">#28 - Scanning from Output</h3>
<p>Use the new output to try and find fragments for blueprints</p>
]]></content:encoded></item><item><title>drone ah adventures</title><link>https://icle.es/endeavours/drone-ah-adventures/</link><pubDate>Tue, 20 Mar 2018 10:13:18 +0100</pubDate><guid>https://icle.es/endeavours/drone-ah-adventures/</guid><description>&lt;p>After watching many a youtube gaming video, I wanted to do a channel of my own,
and I did for a while.&lt;/p>
&lt;p>&lt;a href="https://www.youtube.com/@drone.ah-adventures">drone ah adventures&lt;/a>&lt;/p>
&lt;h2 id="space-engineers">Space Engineers&lt;/h2>
&lt;p>&lt;a href="https://www.youtube.com/watch?v=GOqmD4lVTGM&amp;amp;list=PLQb-fOWHIdUzyM-bJllq3QpWdVGDBhC98&amp;amp;pp=gAQB">The Pilot Series&lt;/a>
I still have the last video or two, which I never got around to uploading&lt;/p>
&lt;p>&lt;a href="https://www.youtube.com/watch?v=_075xAh0VNM&amp;amp;list=PLQb-fOWHIdUz61gNb9AJQrVfri4qZp0Jd&amp;amp;pp=gAQB">Academy&lt;/a>&lt;/p>
&lt;p>Just the one tutorial video&lt;/p>
&lt;h2 id="subnautica">Subnautica&lt;/h2>
&lt;p>&lt;a href="https://www.youtube.com/watch?v=xZmipiqFrYs&amp;amp;list=PLQb-fOWHIdUy4sWwpo8T68bm_kpiKlm-4&amp;amp;pp=gAQB">Wet Feet&lt;/a>&lt;/p>
&lt;p>I got too scared to be able to finish this one&lt;/p>
&lt;h2 id="while-true-learn">while True; learn()&lt;/h2>
&lt;p>&lt;a href="https://www.youtube.com/watch?v=PByyKwJ-ouY&amp;amp;list=PLQb-fOWHIdUxe7N6CETzEuUOT7YTljiOS&amp;amp;pp=gAQB">First Run&lt;/a>&lt;/p></description><content:encoded><![CDATA[<p>After watching many a youtube gaming video, I wanted to do a channel of my own,
and I did for a while.</p>
<p><a href="https://www.youtube.com/@drone.ah-adventures">drone ah adventures</a></p>
<h2 id="space-engineers">Space Engineers</h2>
<p><a href="https://www.youtube.com/watch?v=GOqmD4lVTGM&amp;list=PLQb-fOWHIdUzyM-bJllq3QpWdVGDBhC98&amp;pp=gAQB">The Pilot Series</a>
I still have the last video or two, which I never got around to uploading</p>
<p><a href="https://www.youtube.com/watch?v=_075xAh0VNM&amp;list=PLQb-fOWHIdUz61gNb9AJQrVfri4qZp0Jd&amp;pp=gAQB">Academy</a></p>
<p>Just the one tutorial video</p>
<h2 id="subnautica">Subnautica</h2>
<p><a href="https://www.youtube.com/watch?v=xZmipiqFrYs&amp;list=PLQb-fOWHIdUy4sWwpo8T68bm_kpiKlm-4&amp;pp=gAQB">Wet Feet</a></p>
<p>I got too scared to be able to finish this one</p>
<h2 id="while-true-learn">while True; learn()</h2>
<p><a href="https://www.youtube.com/watch?v=PByyKwJ-ouY&amp;list=PLQb-fOWHIdUxe7N6CETzEuUOT7YTljiOS&amp;pp=gAQB">First Run</a></p>
<p>Played when it was still pretty early. Run was terminated by a bug.</p>
<p>A lot of the levels also got changed and I was never quite motivated to go back</p>
<h2 id="memories-of-mars">Memories of Mars</h2>
<p><a href="https://www.youtube.com/watch?v=2LIR0-AWW1I&amp;list=PLQb-fOWHIdUzAMEOWsfxUmoWwV9Y4AjJn&amp;pp=gAQB">Beta 1 Gameplay</a></p>
<p>Cut together videos of the beta gameplay.</p>
<h2 id="epistory">Epistory</h2>
<p><a href="https://www.youtube.com/watch?v=KKUZZtMtFCk&amp;list=PLQb-fOWHIdUyMfxpPKw7lYEcxK6KQGc8G&amp;pp=gAQB">Unfolding the Story</a></p>
<p>A beautiful game and a lovely story - one that I finished on here</p>
<h2 id="playne">Playne</h2>
<p><a href="https://www.youtube.com/watch?v=Bb29g5tzE5Y&amp;list=PLQb-fOWHIdUzLfpda98zPG2Em7sc-jMvE&amp;pp=gAQB">Daily Meditation</a></p>
<p>I think I got 30 consecutive days of meditation with this awesome game</p>
<p><a href="https://www.youtube.com/watch?v=EnlL2XW3kEk&amp;list=PLQb-fOWHIdUwkwvzkfSwJYB-uZ89nCqsf&amp;pp=gAQB">Revisiting Playne</a></p>
<p>Another couple weeks of meditation</p>
<h2 id="the-azure-isle">The Azure Isle</h2>
<p><a href="https://www.youtube.com/watch?v=hxzlaGvhM-c&amp;list=PLQb-fOWHIdUzEMftIdH-weiRSu6bnEVik&amp;pp=gAQB">Shipwrecked</a></p>
<p>I loved this little game, but I got stuck!</p>
<h2 id="slay-the-spire">Slay the Spire</h2>
<p><a href="https://www.youtube.com/watch?v=KJhu7fEZ46I&amp;list=PLQb-fOWHIdUz5SSCcnkbkwb1Yl0FrNc0Z&amp;pp=gAQB">Clambering the Spire</a></p>
<p>Loved this game - I might not have gotten round to uploading the last video</p>
<h2 id="xcom-chimera-squad">XCom Chimera Squad</h2>
<p><a href="https://www.youtube.com/watch?v=YFa1PxCbdJ0&amp;list=PLQb-fOWHIdUy1_V5sOrJVmSDCT6JwYw2o&amp;pp=gAQB0gcJCWMEOCosWNin">Exploring City 31</a></p>
<p>A great game, but I got distrated.</p>
<h2 id="book-of-demons">Book of Demons</h2>
<p><a href="https://www.youtube.com/watch?v=QFY9zYwrUSg&amp;list=PLQb-fOWHIdUyWIwxTOUK_XiUY3_N3D0W1&amp;pp=gAQB">Deck Building Diablo</a></p>
<p>I enjoyed this one for a little bit</p>
]]></content:encoded></item><item><title>Let's Play Space Engineers | Pilot Series</title><link>https://icle.es/2018/03/19/lets-play-space-engineers-pilot-series/</link><pubDate>Mon, 19 Mar 2018 15:00:07 +0000</pubDate><guid>https://icle.es/2018/03/19/lets-play-space-engineers-pilot-series/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>I have been watching gameplay videos on youtube for a while now. I have always
been inspired by them and wanted to start a series of my own.&lt;/p>
&lt;p>I hadn't played space engineers for a few months and had been wanting to go
back to it. That made it an ideal candidate to try this on. One thing I always
struggled with Space Engineers is that it doesn't have a goal to work towards.
To this end, I have tried to come up with a backstory that'll give something to
work towards&lt;/p></description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>I have been watching gameplay videos on youtube for a while now. I have always
been inspired by them and wanted to start a series of my own.</p>
<p>I hadn't played space engineers for a few months and had been wanting to go
back to it. That made it an ideal candidate to try this on. One thing I always
struggled with Space Engineers is that it doesn't have a goal to work towards.
To this end, I have tried to come up with a backstory that'll give something to
work towards</p>
<p>It was substantially more difficult than it looks to talk and play a game and
the first couple of episodes are incredibly stumbly. I share them for posterity,
continuity and hopefully to also show how much improvement there is later on,
hopefully.</p>
<h3 id="backstory">Backstory</h3>
<p>A lone space engineer wakes up in his small ship, far away from a home he once
knew. There are only a handful of people still awake, with a few thousand people
across a few factions in cryo-sleep. A capital ship is being built to house
these cryo chambers. Several engineers have been sent out into the galaxies to
build a base.</p>
<p>Their mission is to colonise planets, ideally multiple planets, for each of the
three factions. With a few hundred to a thousand people in each faction. Once
all the habitats have been built, the engineer is to send a radio signal and
await the arrival of the cargo.</p>
<p>With the heavy burden of this mission on their shoulders, this space engineer
wakes up, caught in the gravitational pull of a blue planet.</p>
<h2 id="episodes">Episodes</h2>
<p>Below are the episodes already uploaded to youtube.</p>
<h3 id="01---hunting-for-uranium">#01 - Hunting for Uranium</h3>
<p><a href="https://youtu.be/GOqmD4lVTGM">https://youtu.be/GOqmD4lVTGM</a></p>
<p>In the first episode, we start in a respawn ship within the gravitational pull
of Earth. We then move off into space and go hunting for uranium before we run
out of power. The most excitement came when I realised that I was running out of
oxygen and I hadn't taken a canister with me. Will I die from suffocation? Find
out ;)</p>
<p>This is a very noob video, my very first. Also, due to a recording mistake,
there are no game sounds on this video :(</p>
<h3 id="02---starting-a-base">#02 - Starting a Base</h3>
<p><a href="https://youtu.be/7qVowX5whJ4">https://youtu.be/7qVowX5whJ4</a></p>
<p>We identify a location to start our base out on and start building it in
earnest. Partway through, I realise that the scaffolding that I had painstaking
built out was way too large, so I tear it down and try to build a small base. I
also spend some time mining out a bunch of iron.</p>
<p>This is also a very noob video, though I think there are fewer boring parts
(hopefully). The game audio issue is also fixed part way through.</p>
<h3 id="03---furnishing">#03 - Furnishing</h3>
<p><a href="https://youtu.be/m5Enko3Sxxg">https://youtu.be/m5Enko3Sxxg</a></p>
<p>This episode revolves around finishing construction of some of the items inside
the base. There was some confusion about how to build a cargo container without
dismantling the one on the ship.</p>
<p>This is hopefully starting to get a little better.</p>
<h3 id="04---more-furnishings">#04 - More Furnishings</h3>
<p><a href="https://youtu.be/oqEjvVypnlI">https://youtu.be/oqEjvVypnlI</a></p>
<p>Realise that I am running out of oxygen (again) and we don't have an Oxygen
Generator or Ice. Fix that first, then build a turret for some protection.</p>
<p>I felt this episode was a lot better.</p>
<h3 id="05---the-bug">#05 - The Bug</h3>
<p><a href="https://youtu.be/QXClJZm9Hb0">https://youtu.be/QXClJZm9Hb0</a></p>
<p>This episode starts with building a rudimentary mining ship which I screw up. I
had been away from this game for a while ;) I then fix a bunch of the stuff, do
some mining, then fix a bunch of other things.</p>
<p>This episode also felt pretty ok to me.</p>
<h3 id="06---the-shredder">#06 - The Shredder</h3>
<p><a href="https://youtu.be/3w1T7pClos0">https://youtu.be/3w1T7pClos0</a></p>
<p>In this episode, I build a grinding ship. I then use this ship to grind down the
original respawn ship completely. I also reconstruct the roof of the base to be
a little tidier. The rest of the episode is spent scouting for resources.</p>
<p>It feels like I need to commentate more. While editing, I realise that there are
a few unexpressed thoughts and the sheer amount of time when I am not saying
anything still surprises me. A lot more work still to do :)</p>
<h3 id="07---the-digger">#07 - The Digger</h3>
<p><a href="https://www.youtube.com/watch?v=xcBFEOzxyEQ">https://www.youtube.com/watch?v=xcBFEOzxyEQ</a></p>
<p>We build another version of the miner, so that we don't have to deal with the
container snafu 'bug' ;) This episode runs a little longer, but we end up with
a much better version of the miner which I call the digger. This is a callback
to <a href="https://www.genomised.com/games/digger">a game that was released in 1983</a>
which I loved growing up :) You can
<a href="http://www.futrega.org/digger/">try it online.</a></p>
<p>I feel that the videos are definitely improving and look forward to hearing what
you think :)</p>
<h3 id="08---tim-is-in-charge">#08 - Tim is in Charge</h3>
<p><a href="https://youtu.be/6NCFKtMxRnQ">https://youtu.be/6NCFKtMxRnQ</a></p>
<p>Tidy the base up a bit, plugging the reactor into the conveyor system. I then
set up Talendan's inventory management (TIM) to automate the refinery,
assembler and the inventories. Set up some LCD panels to see the status of these
as well.</p>
<h3 id="09---base-expansion">#09 - Base Expansion</h3>
<p><a href="https://www.youtube.com/watch?v=3PmEuCeQu8U&amp;index=9&amp;t=0s&amp;list=PLQb-fOWHIdUzyM-bJllq3QpWdVGDBhC98">https://www.youtube.com/watch?v=3PmEuCeQu8U&index=9&t=0s&list=PLQb-fOWHIdUzyM-bJllq3QpWdVGDBhC98</a></p>
<p>Fix a couple of things on the base and start on expanding it.</p>
<h3 id="10---the-mole">#10 - The Mole</h3>
<p>Build a tiny mining ship using the Vertex Thrust 2 Script. This doesn't go
exactly according to plan.</p>
<h3 id="11---the-refinery-room">#11 - The Refinery Room</h3>
<p><a href="https://www.youtube.com/watch?v=tiqFMIspVHU">https://www.youtube.com/watch?v=tiqFMIspVHU</a></p>
<p>Get the framework for the refinery room more or less complete</p>
<h3 id="12---the-beaver">#12 - The Beaver</h3>
<p><a href="https://www.youtube.com/watch?v=L7-unJTywWo">https://www.youtube.com/watch?v=L7-unJTywWo</a></p>
<p>Most of this episode was spent trying to build a small and compact enough
welding ship.</p>
<h3 id="13---the-refinery-room-pt-2">#13 - The Refinery Room Pt 2</h3>
<p><a href="https://www.youtube.com/watch?v=hsMY4q_hBHw">https://www.youtube.com/watch?v=hsMY4q_hBHw</a></p>
<p>We weld up the refinery room and link it up to the rest of the cargo network.</p>
<h3 id="14---misbehavin-tim">#14 - Misbehavin' Tim</h3>
<p><a href="https://www.youtube.com/watch?v=CMMrfkdvItM">https://www.youtube.com/watch?v=CMMrfkdvItM</a></p>
<p>Start on boring out the refinery room and run into some trouble with Tim too.</p>
<h3 id="15---assembler-jigsaw">#15 - Assembler Jigsaw</h3>
<p><a href="https://www.youtube.com/watch?v=Nq0E45KqhhM">https://www.youtube.com/watch?v=Nq0E45KqhhM</a></p>
<p>Start putting down assemblers.</p>
<h3 id="16---assembler-room">#16 - Assembler Room</h3>
<p><a href="https://www.youtube.com/watch?v=vORayj8-f-0">https://www.youtube.com/watch?v=vORayj8-f-0</a></p>
<p>Finish construction of the assembler room.</p>
<h3 id="17---base-with-a-view">#17 - Base with a View</h3>
<p><a href="https://www.youtube.com/watch?v=9i2N8o9c1KQ">https://www.youtube.com/watch?v=9i2N8o9c1KQ</a></p>
<p>Prettify the base and tidy things up.</p>
<h3 id="18---the-storage-room">#18 - The Storage Room</h3>
<p><a href="https://www.youtube.com/watch?v=UcmL3luT_8U">https://www.youtube.com/watch?v=UcmL3luT_8U</a></p>
<p>Drilling out the storage room</p>
<h3 id="19---storage-room-complete">#19 - Storage Room Complete</h3>
<p><a href="https://www.youtube.com/watch?v=BrTGpPYQi0c">https://www.youtube.com/watch?v=BrTGpPYQi0c</a></p>
<p>Completed the welding and setup of the storage room.</p>
<h3 id="20---control-room">#20 - Control Room</h3>
<p><a href="https://www.youtube.com/watch?v=TaAL_MOdghY&amp;t=2s">https://www.youtube.com/watch?v=TaAL_MOdghY&t=2s</a></p>
<p>Tidying up the Control room and preparing the base for pressurisation.</p>
<h3 id="21---fully-automated-mining">#21 - Fully Automated Mining</h3>
<p><a href="https://www.youtube.com/watch?v=t3uaLVomaMo">https://www.youtube.com/watch?v=t3uaLVomaMo</a></p>
<p>Using Rdav's AI Autominer Script to put together a fully autonomous mining
ship.</p>
<h3 id="22---ant-mk-ii-better-automated-miner">#22 - Ant Mk II Better Automated Miner</h3>
<p><a href="https://www.youtube.com/watch?v=uvLNVdIRlkg">https://www.youtube.com/watch?v=uvLNVdIRlkg</a></p>
<p>While falling asleep, I had some ideas on how to improve the Ant. I get on with
constructing the Ant Mk II.</p>
<h3 id="23---testing-the-ant-mk-ii">#23 - Testing the Ant Mk II</h3>
<p><a href="https://youtu.be/WhtVJZxtj3E">https://youtu.be/WhtVJZxtj3E</a></p>
<p>Wait, why does the ship try and drill through the asteroid sideways?</p>
<h3 id="24---ant-mk-ii-chassis">#24 - Ant Mk II Chassis</h3>
<p>We build the Chassis around most of the Ant, send it off mining and start
working on airlocks.</p>
<h3 id="25---rotary-airlock">#25 - Rotary Airlock</h3>
<p>Put in Airlocks and start on getting the base airtight.</p>
<h3 id="26---airtight-refinery-room">#26 - Airtight Refinery Room</h3>
<p>We manage to get the refinery room airtight? What was the problem - something I
had done right at the start of setting the room up.</p>
<h3 id="27---fully-pressurised">#27 - Fully Pressurised</h3>
<p>Fix the autominers so they work across reloads. Get the base fully pressurised
and move on to the next project.</p>
<h3 id="28---holey-cave">#28 - Holey Cave</h3>
<p>What I wanted was to have the new section to be mostly just rock, or in this
case, iron deposits. Do I manage to get it airtight?</p>
<h3 id="29---unholey-cave">#29 - Unholey Cave</h3>
<p>Working on making the way to the hanger airtight.</p>
<h3 id="30---fixing-sim-speed">#30 - Fixing Sim Speed</h3>
<p>Space Engineers had been dragging for a few episodes. Fix that.</p>
<h3 id="31---way-to-the-hangar">#31 - Way to the Hangar</h3>
<p>We make progress building our way towards the hangar.</p>
<h3 id="32---building-a-tunnel">#32 - Building a Tunnel</h3>
<p>Building the tunnel that'll take us to the Hangar.</p>
<h3 id="33---finishing-the-tunnels">#33 - Finishing the Tunnels</h3>
<p>We finish off the tunnel to the Hangar</p>
<h3 id="34---under-the-stairs">#34 - Under the Stairs</h3>
<p>We start furnishing under the stairs ;)</p>
<h3 id="35---starting-the-hangar">#35 - Starting the Hangar</h3>
<p>We make our way to the hangar and start on it in earnest.</p>
<h3 id="36---hangar-foundations-pt-1">#36 - Hangar Foundations pt 1</h3>
<p>We start putting in the struts that will hold the hangar together</p>
<h3 id="37---hangar-foundations-pt-2">#37 - Hangar Foundations pt 2</h3>
<p>We continue our work in putting in the struts around the hangar</p>
<h3 id="38---small-steel-tubes">#38 - Small Steel Tubes</h3>
<p>While putting in the tubing around the hangar, we run out of small steel tubes.</p>
<h3 id="39---automining-without-lag">#39 - Automining Without Lag</h3>
<p>Try and get the autominers working without lag.</p>
<h3 id="40---faster-mining">#40 - Faster Mining</h3>
<p>Try and speed up automatic mining by attaching several more drills.</p>
<h3 id="41---hangar-foundations-pt-3">#41 - Hangar Foundations pt 3</h3>
<p>Putting down another set of foundations for the hangar</p>
<h3 id="42---hangar-foundations-pt-4">#42 - Hangar Foundations pt 4</h3>
<p>Piping up the hangar</p>
<h3 id="43---the-printer">#43 - The Printer</h3>
<p>Build a welding ship and get started on the capital ship.</p>
<h3 id="44---everything-is-broken-">#44 - Everything is Broken :(</h3>
<p>One by one fail by fail. At least, that's how it felt.</p>
<h3 id="45---fixing-things">#45 - Fixing Things</h3>
<p>Try and fix everything that broke in the last episode.</p>
<h3 id="46---making-space">#46 - Making Space</h3>
<p>Making more space inside the large grid ship to put things.</p>
<h3 id="47---interior-design">#47 - Interior Design</h3>
<p>So... What should go inside this brand spanking new ship?</p>
<h3 id="48---no-more-gold">#48 - No More Gold!</h3>
<p>We run out of gold while building the jump drive.</p>
<h3 id="49---detaching-the-ship">#49 - Detaching the Ship</h3>
<p>We grind off the supporting beams. Will the ship crash and burn?</p>
<h3 id="50---the-big-five-o">#50 - The Big Five O</h3>
<p>The fiftieth episode of this series. My first ever YouTube series and it has got
to 50 episodes. Hurrah!</p>
<h3 id="51---interior-redesign">#51 - Interior Redesign</h3>
<p>I start off with the intention of finishing off the hanger, but end up grinding
the whole hangar down.</p>
<h3 id="52---rebuilding-the-hangar">#52 - Rebuilding the Hangar</h3>
<p>We get back on to the hangar and get most of it down</p>
<h3 id="53---the-right-wing">#53 - The Right Wing</h3>
<p>No, it's not a political episode, it's about building the wing on the right
hand side of the ship.</p>
<h3 id="54---out-of-cobalt">#54 - Out of Cobalt</h3>
<p>Working on the right wing and I run out of metal grids, which as it turned out
was because we were out of Cobalt.</p>
<h2 id="future-plans">Future Plans</h2>
<p>The main next plan is to flesh out the base a bit and get a solid starting
point. After that, we'll go to the moon. We'll build a moon base with a
habitat of some form.</p>
<p>If I am not bored after that, I might try heading to Mars or the Alien planet
since I've not been to either of those locations.</p>
<h2 id="unlikely-but-would-be-awesome">Unlikely but would be awesome</h2>
<p>In an offline map, kaljack and I were working on building a massive capital ship
with hundreds (or thousands) of cryo chambers. The idea being that this ship
would contain the remaining people from a now dead civilisation. They are
looking for a place to land and call home.</p>
<p>The habitats and the environments we are building are for these people. If we
ever finish that capital ship, we'll get it jump into our world and land on one
or more of these locations.</p>
<h2 id="finally">Finally</h2>
<p>I welcome comments, ideas and thoughts, ideally in the form of compliments ;)
but I also welcome constructive criticisms, ideas, questions and requests.</p>
<p>If you like Space Engineers, you might also like
<a href="https://www.gamecupid.com/games/space-engineers/games-like">games like Space Engineers (by game cupid)</a>\</p>
]]></content:encoded></item><item><title>Rags to Riches</title><link>https://icle.es/2017/11/20/rags-to-riches/</link><pubDate>Mon, 20 Nov 2017 16:13:01 +0000</pubDate><guid>https://icle.es/2017/11/20/rags-to-riches/</guid><description>&lt;p>In a land of misery and pain,&lt;br>
I grew up believing in fairy tales,&lt;br>
In princes, princesses and happy endings.&lt;/p>
&lt;p>In a land of sorrow and sadness,&lt;br>
I grew up believing the words of rock n roll,&lt;br>
I believed in fate, destiny, and happily ever after.&lt;/p>
&lt;p>In a land of poverty and subservience,&lt;br>
I felt wealthy beyond all comparison,&lt;br>
I was rich with hopes, dreams and fantasies.&lt;/p>
&lt;p>In a land of despair and regret,&lt;br>
I grew up and moved away, far far away,&lt;br>
To a land rich and happy, filled with marvels.\&lt;/p></description><content:encoded><![CDATA[<p>In a land of misery and pain,<br>
I grew up believing in fairy tales,<br>
In princes, princesses and happy endings.</p>
<p>In a land of sorrow and sadness,<br>
I grew up believing the words of rock n roll,<br>
I believed in fate, destiny, and happily ever after.</p>
<p>In a land of poverty and subservience,<br>
I felt wealthy beyond all comparison,<br>
I was rich with hopes, dreams and fantasies.</p>
<p>In a land of despair and regret,<br>
I grew up and moved away, far far away,<br>
To a land rich and happy, filled with marvels.\</p>
<p>In a land of wealth and influence,<br>
My dreams of fairy tales were dashed,<br>
I experienced misery and pain, day after day.\</p>
<p>In the land of princes and princesses,<br>
I learnt of music full of sadness and anger.<br>
I played the rhythm and the blues day after day, <br>
night after night.</p>
<p>In the land of independence and power,<br>
I felt trapped and powerless,<br>
Life was happening<br>
I fought against it<br>
I grew up,<br>
I lost</p>
<p>Then I met you!</p>
]]></content:encoded></item><item><title>Drupal 8 error: "The following reasons prevent the modules from being uninstalled: Fields pending deletion"</title><link>https://icle.es/2017/09/28/drupal-8-error-the-following-reasons-prevent-the-modules-from-being-uninstalled-fields-pending-deletion/</link><pubDate>Thu, 28 Sep 2017 15:53:15 +0000</pubDate><guid>https://icle.es/2017/09/28/drupal-8-error-the-following-reasons-prevent-the-modules-from-being-uninstalled-fields-pending-deletion/</guid><description>&lt;p>When you try and uninstall a module which has a field that you have used, it can
throw the following error:&lt;/p>
```
The following reasons prevent the modules from being uninstalled: Fields pending
deletion\
```
&lt;p>This is an issue in both Drupal 7 and Drupal 8. This is due to the fact that
drupal doesn't actually delete the data for the field when you delete the
field. It deletes the data during cron runs. If cron hasn't been run enough
times since you deleted the field, drupal won't let you uninstall the module.&lt;/p></description><content:encoded><![CDATA[<p>When you try and uninstall a module which has a field that you have used, it can
throw the following error:</p>
```
The following reasons prevent the modules from being uninstalled: Fields pending
deletion\
```
<p>This is an issue in both Drupal 7 and Drupal 8. This is due to the fact that
drupal doesn't actually delete the data for the field when you delete the
field. It deletes the data during cron runs. If cron hasn't been run enough
times since you deleted the field, drupal won't let you uninstall the module.</p>
<p>To force drupal to purge the data, you can run the following command</p>
```bash
drush php-eval \'field_purge_batch(500);\'\
```
<p>Increase 500 to a high enough number to wipe out the data. Afte this has
completed, you should be able to uninstall the module</p>
<p>References:<br>
<a href="https://drupal.stackexchange.com/questions/184690/module-uninstall-dependencies">Module uninstall dependencies (drupal stackexchange)</a><br>
<a href="https://www.drupal.org/node/1331922">Message &quot;Required by Drupal (Fields Pending Deletion)&quot; baffles users</a><br>
<a href="https://www.drupal.org/node/2835035">Can't uninstall YAML because of following reason: Fields pending deletion</a></p>
]]></content:encoded></item><item><title>Drupal 7 + Services: Paging &amp;amp; Filtering the index endpoint</title><link>https://icle.es/2017/09/03/drupal-7-services-paging-filtering-the-index-endpoint/</link><pubDate>Sun, 03 Sep 2017 12:20:54 +0000</pubDate><guid>https://icle.es/2017/09/03/drupal-7-services-paging-filtering-the-index-endpoint/</guid><description>&lt;p>There are a lot of ways to manipulate the data returned by the index endpoint.
In this post, we are going to consider the node index endpoint. By default, this
endpoint returns all nodes sorted in descending order of last update with 20
items per page.&lt;/p>
&lt;p>You access the node index endpoint by going to&lt;/p>
&lt;p>&lt;code>http://&amp;lt;domain&amp;gt;/&amp;lt;endpoint-pathnode.json&lt;/code> (or the alias given to node in the
resources section)&lt;/p>
&lt;p>You can replace .json with with other extensions to get the same data in
different formats&lt;/p></description><content:encoded><![CDATA[<p>There are a lot of ways to manipulate the data returned by the index endpoint.
In this post, we are going to consider the node index endpoint. By default, this
endpoint returns all nodes sorted in descending order of last update with 20
items per page.</p>
<p>You access the node index endpoint by going to</p>
<p><code>http://&lt;domain&gt;/&lt;endpoint-pathnode.json</code> (or the alias given to node in the
resources section)</p>
<p>You can replace .json with with other extensions to get the same data in
different formats</p>
<p>To access the second page, you can use the page parameter</p>
```
node.json?page=2
node.json?page=5
```
<p>To change the number of items on each page, you need the &ldquo;perform unlimited
index&rdquo; queries permission. You use the <code>pagesize</code> parameter to change it</p>
```
node.json?pagesize=100
node.json?pagesize=50
```
<p>To filter a field, you can use the parameters[property] where &lsquo;property&rsquo; is the
field on which you want to filter. It needs to be a field on the node table, and
not a drupal field as it does not do the joins to pull in field data.</p>
```
node.json?parameters[type]=blog_post
node.json?parameters[type]=article
```
<p>To apply a different filter than of equality, you can use
options[parameters_op][property] where property is the same as above.</p>
```
node.json?parameters[created]=1431065220&options[parameters_op][created]=<
node.json?parameters[changed]=1431065220&options[parameters_op][changed]=>
```
<p>To return fewer fields, you can use fields and comma separate the properties.
Once again, you can only specify properties on the entity (i.e fields on the
base table)</p>
```
node.json?fields=nid,changed
node.json?fields=nid,created,title
```
<p>you can sort the results by using options<code>[property]=&lt;asc|desc&gt;</code></p>
```
node.json?options[orderby][nid]=asc
node.json?options[orderby][created]=desc
```
<p>You can also mix and match these separate options</p>
```
node.json?page=10&pagesize=100&parameters[type]=blog_post&options[parameters_op][type]=!=&fields=nid,changed
```
]]></content:encoded></item><item><title>Understanding ZFS Disk Utilisation and available space</title><link>https://icle.es/2017/08/16/understanding-zfs-disk-utilisation-and-available-space/</link><pubDate>Wed, 16 Aug 2017 12:02:07 +0000</pubDate><guid>https://icle.es/2017/08/16/understanding-zfs-disk-utilisation-and-available-space/</guid><description>&lt;p>I am hopeful the following will help someone scratch their head a little less in
trying to understand the info returned by zfs.&lt;/p>
&lt;p>I set up a pool using 4 2TB SATA disks.&lt;/p>
```bash
$ zpool list -v
NAME SIZE ALLOC FREE EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
rpool 7.25T 2.50T 4.75T - 10% 34% 1.00x ONLINE -
raidz2 7.25T 2.50T 4.75T - 10% 34%
sda2 - - - - - -
sdb2 - - - - - -
sdc2 - - - - - -
sdd2 - - - - - -
```
&lt;p>The total size displayed here is the total size of the 4 disks. The maths works
as 4*2TB = 8TB = ~7.25TiB&lt;/p></description><content:encoded><![CDATA[<p>I am hopeful the following will help someone scratch their head a little less in
trying to understand the info returned by zfs.</p>
<p>I set up a pool using 4 2TB SATA disks.</p>
```bash
$ zpool list -v
NAME SIZE ALLOC FREE EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
rpool 7.25T 2.50T 4.75T - 10% 34% 1.00x ONLINE -
raidz2 7.25T 2.50T 4.75T - 10% 34%
sda2 - - - - - -
sdb2 - - - - - -
sdc2 - - - - - -
sdd2 - - - - - -
```
<p>The total size displayed here is the total size of the 4 disks. The maths works
as 4*2TB = 8TB = ~7.25TiB</p>
<p>RAIDZ2 is like RAID6 and it uses two disks for parity. Thus, I would expect to
have ~4TB or 3.63TiB of available space. I haven&rsquo;t been able to find this number
displayed anywhere.</p>
<p><strong>However</strong>, you can find the amount of disk space still available using the
following command.</p>
```bash
$# zfs list
NAME USED AVAIL REFER MOUNTPOINT
rpool 1.21T 2.19T 140K /rpool
rpool/ROOT 46.5G 2.19T 140K /rpool/ROOT
rpool/ROOT/pve-1 46.5G 2.19T 46.5G /
rpool/data 1.16T 2.19T 140K /rpool/data
rpool/data/vm-100-disk-1 593M 2.19T 593M -
rpool/data/vm-101-disk-1 87.1G 2.19T 87.1G -
rpool/data/vm-102-disk-1 71.2G 2.19T 71.2G -
rpool/data/vm-103-disk-1 2.26G 2.19T 2.26G -
rpool/data/vm-103-disk-2 13.2M 2.19T 13.2M -
rpool/data/vm-103-disk-3 13.2M 2.19T 13.2M -
rpool/data/vm-103-disk-4 93K 2.19T 93K -
rpool/data/vm-103-disk-5 1015G 2.19T 1015G -
rpool/data/vm-104-disk-1 4.73G 2.19T 4.73G -
rpool/data/vm-105-disk-1 4.16G 2.19T 4.16G -
rpool/swap 8.66G 2.19T 8.66G -
```
<p>The value of <strong>2.19T</strong> is the amount of unallocated space available in the pool.
To verify this, you can run</p>
```
# zfs get all rpool
NAME PROPERTY     VALUE                           SOURCE
rpool type        filesystem                       -
rpool creation    Fri Aug 4 20:39 2017             -
rpool used        1.21T                            -
rpool available   2.19T                            -

...
```
<p>If we add the two numbers here, 1.21T + 2.19T = 3.4T.</p>
<p>5% of disk space is reserved, so 3.63 * 0.95 = 3.4T</p>
<p>et voila</p>
]]></content:encoded></item><item><title>Only Love</title><link>https://icle.es/2017/06/07/only-love/</link><pubDate>Wed, 07 Jun 2017 16:13:01 +0000</pubDate><guid>https://icle.es/2017/06/07/only-love/</guid><description>&lt;p>Imagine your heart, beating in your chest. &lt;br>
It has two parts, &lt;br>
Love is created in one,&lt;br>
And recevied in the other.&lt;/p>
&lt;p>See the love flowing from your heart,&lt;br>
Redirect it all into the other part of your heart&lt;br>
Watch as your heart fills slowly.&lt;/p>
&lt;p>Don’t try too hard, let it flow naturally.&lt;/p>
&lt;p>When ready, let it overflow, slowly.&lt;/p>
&lt;p>Let flow all over your body, cover your entire body with love&lt;br>
Let it flow into your mind, cover all of it with love&lt;br>
Let it flow into your entire being&lt;/p></description><content:encoded><![CDATA[<p>Imagine your heart, beating in your chest. <br>
It has two parts, <br>
Love is created in one,<br>
And recevied in the other.</p>
<p>See the love flowing from your heart,<br>
Redirect it all into the other part of your heart<br>
Watch as your heart fills slowly.</p>
<p>Don’t try too hard, let it flow naturally.</p>
<p>When ready, let it overflow, slowly.</p>
<p>Let flow all over your body, cover your entire body with love<br>
Let it flow into your mind, cover all of it with love<br>
Let it flow into your entire being</p>
<p>Now, let it flow outside you,<br>
The barriers that kept that love inside you is gone.</p>
<p>In the middle of your heart is a tunnel that connects you<br>
It connects you to everybody whom you love<br>
Let the love flow.</p>
<p>The love within your being does not diminish.<br>
The newly created love replenishes any that is flowing out.</p>
<p>Let it flow out and it flows through the ones near you.</p>
<p>The love dances with the love from the others</p>
<p>Slowly, bit by bit, dancing, flowing, playing, all of the love merges together.</p>
<p>We are one, particles within an ocean of love.</p>
<p>We are no more, there is only the love.</p>
]]></content:encoded></item><item><title>Dark &amp; Light</title><link>https://icle.es/2017/05/10/dark-light/</link><pubDate>Wed, 10 May 2017 16:13:01 +0000</pubDate><guid>https://icle.es/2017/05/10/dark-light/</guid><description>&lt;p>All around, as far as the eye can see,&lt;br>
Nothing around but darkness,&lt;br>
Not a flame in sight,&lt;br>
Not a single light,&lt;br>
No creatures bright.&lt;/p>
&lt;p>Fear has gone missing,&lt;br>
A glimmer of it only now and again,&lt;br>
And it hides again.&lt;br>
Every action certain,&lt;br>
Every step sure.&lt;/p>
&lt;p>Walking, running, crawling, &lt;br>
always moving.&lt;/p>
&lt;p>A moment, a day, a year, an eon,&lt;br>
It all moved the same.&lt;/p>
&lt;p>A glimmer in the far&lt;br>
Growing faintly as I draw near.&lt;/p></description><content:encoded><![CDATA[<p>All around, as far as the eye can see,<br>
Nothing around but darkness,<br>
Not a flame in sight,<br>
Not a single light,<br>
No creatures bright.</p>
<p>Fear has gone missing,<br>
A glimmer of it only now and again,<br>
And it hides again.<br>
Every action certain,<br>
Every step sure.</p>
<p>Walking, running, crawling, <br>
always moving.</p>
<p>A moment, a day, a year, an eon,<br>
It all moved the same.</p>
<p>A glimmer in the far<br>
Growing faintly as I draw near.</p>
<p>Walking, running crawling,<br>
Always moving.</p>
<p>Light abound, as far as the eye can see,<br>
Nothing but light<br>
Shadows around,<br>
That and the memories,<br>
Everything else is light.</p>
<p>Standing a crossroad,<br>
Fear has been found,<br>
And it fills the light.<br>
All the courage I could muster<br>
Crushed under the weight of the fear,<br>
Ash in the light</p>
]]></content:encoded></item><item><title>Rapture</title><link>https://icle.es/2016/10/24/rapture/</link><pubDate>Mon, 24 Oct 2016 12:44:39 +0100</pubDate><guid>https://icle.es/2016/10/24/rapture/</guid><description>&lt;p>Xioban had been preparing for this for thousands of years. All his hard work
would soon come to an end. While he preferred the male pronouns, like his boss,
he was in fact without gender.&lt;/p>
&lt;p>When god created him, and his brothers and sisters, they were all created
without gender. There was no need for angels to procreate. Besides, there was no
place for sex or other animalistic activities in heaven.&lt;/p></description><content:encoded><![CDATA[<p>Xioban had been preparing for this for thousands of years. All his hard work
would soon come to an end. While he preferred the male pronouns, like his boss,
he was in fact without gender.</p>
<p>When god created him, and his brothers and sisters, they were all created
without gender. There was no need for angels to procreate. Besides, there was no
place for sex or other animalistic activities in heaven.</p>
<p>It had taken him millennia but he didn’t regret volunteering for this. He had
been preparing for the rapture and it was a lot of work. He didn&rsquo;t know exactly
how many people will be lifted up at the moment of rapture. All he knew is that
it could be anywhere between 0 and near enough 7 billion.</p>
<p>He had decided to err on the side of caution and prepare for 7 billion people.
Now that is a large welcoming hall and a complicated party to organise.</p>
<p>The biggest one he had done before was for a few thousand people. He had
organised music throughout the hall, played by millions of angels. He had
rehearsed with them and it took many hundreds of years before he could get them
all to be co-ordinated. The timing is complicated to get right in a hall built
for 7 billion people.</p>
<p>Halfway through the plans, he had changed the hall too. He felt it might be a
little too restrictive, and so he changed it to a meadow. That took many years
again. This also threw off all the acoustics and it took many more years to
practice for the musicians to get it right again.</p>
<p>He had thought about food and he flip-flopped on whether to provide it. There is
no need to eat in heaven, but people are so used to pleasure and comfort of
eating, he thought. In the end, he organised food, or at least something like
it.</p>
<p>He recruited a few thousand angels to prepare food and drink. One of the
benefits of heaven is that things don&rsquo;t go bad. This meant that they had as much
as they needed to prepare all the food.</p>
<p>He watched as platters of food started to pile up on the millions of tables
across the meadow. He watched as butterflies floated across the meadow, basking
in the late afternoon glow.</p>
<p>Xioban had decided that the feeling of late afternoon would be the ideal time.
It would be the time when you get that lull in the day.</p>
<p>While all this was being prepared, he was also negotiating with the four
horsemen. They were not big fans of the apocalypse. They had enjoyed their
laziness over the years and were not looking forward to doing some actual work.</p>
<p>If they were on earth, they would have gotten fat and useless. Luckily for them,
they were immune to such things.</p>
<p>They had spent their time between heaven and hell, partying hard and taking it
easy. Xioban had found it difficult to light the fire of enthusiasm within them.
They did not share the passion as he did for the rapture. This would have made
Xioban sad, but he didn&rsquo;t have the gift of the rollercoaster ride of emotions.
None of the angels were.</p>
<p>Pestilence had been the most vocal</p>
<p>&ldquo;How many people will my babies have to&rdquo; he made quotation marks in the air
&ldquo;take care of?&rdquo;</p>
<p>&ldquo;I have no idea&rdquo; Xioban had to admit &ldquo;but, it&rsquo;s more than likely no more than
maybe a third. I hear the earth has a lot of religious people.&rdquo;</p>
<p>&ldquo;Alright, fine!&rdquo; Pestilence grumbled</p>
<p>Death was a bit more direct</p>
<p>&ldquo;I better not have to make too many trips. I don&rsquo;t like Pela getting tired&rdquo; He
looked at his horse as he said this</p>
<p>&ldquo;Don&rsquo;t worry about Pela. Your horse, and you can take as much time as you like&rdquo;
Xioban had said.</p>
<p>The other two horsemen had just grumbled. Over the centuries, Xioban would
revisit this conversation in one guise or other. Each time, they would complain
and they would finally agree.</p>
<p>Over the centuries, he had found that it took longer and longer to get to the
point where they agree. While he had found this a trifle worrying, he figured it
wouldn&rsquo;t be an issue. Since there was no deadline, he could take all the time he
needed to convince them.</p>
<p>One day, Xioban was basking in the glory of the almighty, in a meadow somewhere.
His eyes closed, and his mind still, he heard someone clear their throat. He
opens his eyes to find someone standing over him. His (he assumed from the way
he was dressed) wings were white and only partly open. He was smiling, as
everyone does.</p>
<p>&ldquo;It&rsquo;s ready!&rdquo; He said</p>
<p>&ldquo;Everything?&rdquo; Xioban asked, almost surprised. It had been centuries that he had
almost forgotten about it.</p>
<p>&ldquo;Yes, everything.&rdquo; He said</p>
<p>Xioban smiled, and thanked him.</p>
<p>Xioban got up and made his way to the four horsemen. It might have taken a few
minutes, a few hours, or perhaps a few years. Nobody knew, and nobody cared.</p>
<p>He saw them dancing in the moonlight, around a fire. As he walked up to them,
they stopped, and from the expression on their face, he suspected they already
knew.</p>
<p>&ldquo;It is time&rdquo; Xioban said</p>
<p>&ldquo;Yes, it is&rdquo; Pestilence replied.</p>
<p>Xioban smiled, turned around and made his way to the gatekeeper. A few moments
of walking later, he could hear soft hoofbeats behind him. He smiled.</p>
<p>It took some time to get to the gatekeeper and nobody spoke the entire time.
Xioban enjoyed the walk. He liked the rhythmic beats of the hooves following
him.</p>
<p>He saw the gatekeeper, sitting at an ornate desk. The desk was in the middle of
a meadow, under a solitary tree. The tree was lush and green and seemed to have
different types of fruits. They had different shapes, sizes and colours.</p>
<p>Behind the tree was a stream, and he could hear it babbling. It soothed his
heart.</p>
<p>When Xioban got closer, he could see that she was smiling. She heard him and
looks up.</p>
<p>&ldquo;Oh! Hello, I have been waiting for you&rdquo; She said</p>
<p>&ldquo;Hello, and I, you. It is done!&rdquo; He bows as he speaks</p>
<p>&ldquo;Are you sure?&rdquo; She asked. Xioban wondered if she too had almost forgotten.</p>
<p>&ldquo;Alright!&rdquo; she said, &ldquo;I&rsquo;ll push the button as soon as you get to the hall.&rdquo;</p>
<p>Xioban bowed and started to make his way to the hall.</p>
<p>&ldquo;Wait!&rdquo; Pestilence called him from behind and he turned around</p>
<p>&ldquo;We&rsquo;ll come with you&rdquo; He explained, &ldquo;We want to see how many make it up&rdquo;</p>
<p>Xioban nodded, turned around and continued to the hall.</p>
<p>It might have taken many years again, but once again, he enjoyed the walk.</p>
<p>When he got there, he closed his eyes and prayed. He then walked over to a
podium, and pushed the big red button on it.</p>
<p>The musicians started to play music. The air, already filled with the luscious
aroma of food, now also carried the sweetness of music.</p>
<p>Xioban waited, and nothing happened. He pushed the red button again. This time
he heard a click and a whoosh.</p>
<p>Three people appeared in front of him. It looked like they had just jumped up,
though he didn&rsquo;t see them jump.</p>
<p>Xioban pushed the red button again, and nothing happened. He heard the click,
but no whoosh. He tried pushed it several times more and there was nothing apart
from the clicks.</p>
<p>It was dawning on him that his millenia of preparation was perhaps meant for
only three people. He put on his biggest smile and looked over to the three
people who looked lost and confused.</p>
<p>&ldquo;Welcome to heaven!&rdquo; He said into the microphone.</p>
<p>He heard his voice across the meadow. He turned the microphone away and walks
down the stage and to the three people.</p>
<p>&ldquo;It would seem that you are the only ones worthy of heaven&rdquo; Xioban started to
explain.</p>
<p>From the looks on their faces, he knew that this would take some time. He called
over some of the angels waiting to induct the newcomers and handed the three
people off to them.</p>
<p>Pestilence got off his horse and walked towards Xioban.</p>
<p>&ldquo;We are not going down there to take care of 7 billion souls.&rdquo;</p>
<p>&ldquo;Wait! Let&rsquo;s go back to the gatekeeper. Maybe there is a problem&rdquo; Xioban hoped
there was a problem preventing more people from coming up.</p>
<p>The walk back to the Gatekeeper took some time. He did not enjoy this walk.</p>
<p>He was almost running now, and he could still hear the soft hoofbeats, following
him. It was as if they were chasing him now.</p>
<p>&ldquo;What happened? What went wrong?&rdquo; He asked as soon as he got there</p>
<p>&ldquo;What do you mean?&rdquo; The gatekeeper looked confused</p>
<p>&ldquo;What do you mean? What do you mean? There were only three people&rdquo; He was almost
shouting now.</p>
<p>&ldquo;Really? Well, what can I say! There are only three people deserving of heaven
down there&rdquo; The gatekeeper was nonchalant.</p>
<p>&ldquo;But that&rsquo;s impossible! There are 7 billion people down there&rdquo; Xioban was
pleading now</p>
<p>&ldquo;Ah! You have not been watching them&rdquo; The gatekeeper tried to explain.</p>
<p>&ldquo;What do I do now?&rdquo; Xioban seemed to muster up sadness from somewhere.</p>
<p>Xioban hears someone clear their throat behind him and he turns around. It&rsquo;s
pestilence and he too looked distraught.</p>
<p>&ldquo;Erm, so what do we do?&rdquo; Pestilence seemed to be pointing the question more to
the gatekeeper.</p>
<p>Xioban looked at the gatekeeper, eyes almost full with tears, expectant.</p>
<p>The gatekeeper mulled it over for a moment.</p>
<p>&ldquo;Listen guys, just go back to what you were doing. The people down there are
doing a fine job of bringing the apocalypse down on themselves&rdquo;</p>
]]></content:encoded></item><item><title>[Mahout] Deploying custom drivers to mahout</title><link>https://icle.es/2016/08/09/mahout-deploying-custom-drivers-to-mahout/</link><pubDate>Tue, 09 Aug 2016 13:16:11 +0000</pubDate><guid>https://icle.es/2016/08/09/mahout-deploying-custom-drivers-to-mahout/</guid><description>&lt;p>Developing custom drivers on Mahout is fairly straightforward. You can inherit
from MahoutDriver for Java drivers and MahourSparkDriver for spark drivers.&lt;/p>
&lt;p>The Javadoc for MahoutDriver (if you can find it) provides a good summary of how
to implement it&lt;/p>
&lt;blockquote>
&lt;p>[General-purpose driver class for Mahout programs. Utilizes &amp;gt; &amp;gt;
org.apache.hadoop.util.ProgramDriver to run main methods of other &amp;gt; classes, &amp;gt;
but first loads up default properties from a properties &amp;gt;&lt;/p>
&lt;blockquote>
&lt;p>file.]{style=&amp;ldquo;color:#353833;font-family:Arial, Helvetica,
sans-serif;font-size:12.16px;line-height:normal;&amp;rdquo;}&lt;/p></description><content:encoded><![CDATA[<p>Developing custom drivers on Mahout is fairly straightforward. You can inherit
from MahoutDriver for Java drivers and MahourSparkDriver for spark drivers.</p>
<p>The Javadoc for MahoutDriver (if you can find it) provides a good summary of how
to implement it</p>
<blockquote>
<p>[General-purpose driver class for Mahout programs. Utilizes &gt; &gt;
org.apache.hadoop.util.ProgramDriver to run main methods of other &gt; classes, &gt;
but first loads up default properties from a properties &gt;</p>
<blockquote>
<p>file.]{style=&ldquo;color:#353833;font-family:Arial, Helvetica,
sans-serif;font-size:12.16px;line-height:normal;&rdquo;}</p></blockquote>
<p>To run locally:</p>
```
$MAHOUT_HOME/bin/mahout run shortJobName [over-ride ops]
```
<p>Works like this: by default, the file &ldquo;driver.classes.props&rdquo; is loaded from
the classpath, which defines a mapping between short names like &ldquo;vectordump&rdquo;
and fully qualified class names. The format of driver.classes.props is like
so:</p>
```
fully.qualified.class.name = shortJobName : descriptive string
```
<p>The default properties to be applied to the program run is pulled out of, by
default, &ldquo;.props&rdquo; (also off of the classpath).</p>
<p>The format of the default properties files is as follows:</p>
```
  i|input = /path/to/my/input
  o|output = /path/to/my/output
  m|jarFile = /path/to/jarFile
  # etc - each line is shortArg|longArg = value
```
<p>[The next argument to the Driver is supposed to be the short name of &gt; the &gt;
class to be run (as defined in the driver.classes.props &gt;</p>
<blockquote>
<p>file).]{style=&ldquo;color:#353833;font-family:Arial, Helvetica,
sans-serif;font-size:12.16px;line-height:normal;&rdquo;}</p></blockquote>
<p>Then the class which will be run will have it&rsquo;s main called with</p>
```
main(new String[] { "--input", "/path/to/my/input", "--output", "/path/to/my/output" });
```
<p>[After all the &ldquo;default&rdquo; properties are loaded from the file, any &gt; further</p>
<blockquote>
<p>command-line arguments are taken in, and over-ride the &gt;
defaults.]{style=&ldquo;color:#353833;font-family:Arial, Helvetica,
sans-serif;font-size:12.16px;line-height:normal;&rdquo;}</p></blockquote>
<p>So if your driver.classes.props looks like so:</p>
```
org.apache.mahout.utils.vectors.VectorDumper = vecDump : dump vectors from a sequence file
```
<p>[and you have a file core/src/main/resources/vecDump.props which looks &gt;</p>
<blockquote>
<p>like]{style=&ldquo;color:#353833;font-family:Arial, Helvetica,
sans-serif;font-size:12.16px;line-height:normal;&rdquo;}</p></blockquote>
```
  o|output = /tmp/vectorOut
  s|seqFile = /my/vector/sequenceFile
```
<p>[And you execute the &gt; command-line:]{style=&ldquo;color:#353833;font-family:Arial,
Helvetica, sans-serif;font-size:12.16px;line-height:normal;&rdquo;}</p>
```
$MAHOUT_HOME/bin/mahout run vecDump -s /my/otherVector/sequenceFile
```
<p>[Then org.apache.mahout.utils.vectors.VectorDumper.main() will be called with
arguments:</p></blockquote>
<p>You can also implement it slightly differently by just dumping the jar into the
mahout home directory and naming it starting with &ldquo;mahout-&rdquo; i.e.
mahout-mydriver.jar</p>
<p>Hope this helps someone</p>
]]></content:encoded></item><item><title>Setting up Mahout in Linux</title><link>https://icle.es/2016/07/26/setting-up-mahout-in-linux/</link><pubDate>Tue, 26 Jul 2016 10:06:41 +0000</pubDate><guid>https://icle.es/2016/07/26/setting-up-mahout-in-linux/</guid><description>&lt;p>A few simple steps to get Mahout running in Linux. This is mostly about the bash
script to get it to run easily&lt;/p>
&lt;p>You&amp;rsquo;ll need to install Java first, then download and unpack the mahout
distribution.&lt;/p>
&lt;p>I then placed it in &lt;code>/usr/local/mahout&lt;/code>&lt;/p>
&lt;p>To be able to run Mahout from the path, the following bash script was placed in
&lt;code>/usr/local/bin&lt;/code>&lt;/p>
&lt;p>Update the paths as relevant&lt;/p>
```bash
 #!/bin/bash
 export MAHOUT_JAVA_HOME=/usr/lib/jvm/java-8-oracle/jre/
export MAHOUT_HOME=/usr/local/mahout
export MAHOUT_HEAPSIZE=4000
export MAHOUT_LOCAL=y
```</description><content:encoded><![CDATA[<p>A few simple steps to get Mahout running in Linux. This is mostly about the bash
script to get it to run easily</p>
<p>You&rsquo;ll need to install Java first, then download and unpack the mahout
distribution.</p>
<p>I then placed it in <code>/usr/local/mahout</code></p>
<p>To be able to run Mahout from the path, the following bash script was placed in
<code>/usr/local/bin</code></p>
<p>Update the paths as relevant</p>
```bash
 #!/bin/bash
 export MAHOUT_JAVA_HOME=/usr/lib/jvm/java-8-oracle/jre/
export MAHOUT_HOME=/usr/local/mahout
export MAHOUT_HEAPSIZE=4000
export MAHOUT_LOCAL=y
```
]]></content:encoded></item><item><title>[UE4] Unreal Engine &amp;amp; miniupnp</title><link>https://icle.es/2016/02/28/ue4-unreal-engine-miniupnp/</link><pubDate>Sun, 28 Feb 2016 10:11:15 +0000</pubDate><guid>https://icle.es/2016/02/28/ue4-unreal-engine-miniupnp/</guid><description>&lt;p>This post covers how to integrate pnp into an unreal project using miniupnp.&lt;/p>
&lt;p>The first step is to clone the project from
&lt;a href="https://github.com/miniupnp/miniupnp/">github&lt;/a>&lt;/p>
&lt;p>The module that we are interested in is the miniupnpc and in that directory,
there is another directory called msvc and this contains the solution file for
Visual Studio. Open this and if you have a more recent version of visual studio
(which very likely do), it will want to upgrade everything. Let it go through
the upgrade process.&lt;/p>
&lt;p>Building the project now will most likely fail due to a missing file
miniupnpcstrings.h . This file needs to be generated and the way to do that is
to run a script in that folder called updateminiupnpstrings.sh. You will most
probably need something like &lt;a href="https://www.cygwin.com/">cygwin&lt;/a> to for this
script to work as it a &lt;a href="https://en.wikipedia.org/wiki/Unix_shell">unix shell&lt;/a> to
work&lt;/p></description><content:encoded><![CDATA[<p>This post covers how to integrate pnp into an unreal project using miniupnp.</p>
<p>The first step is to clone the project from
<a href="https://github.com/miniupnp/miniupnp/">github</a></p>
<p>The module that we are interested in is the miniupnpc and in that directory,
there is another directory called msvc and this contains the solution file for
Visual Studio. Open this and if you have a more recent version of visual studio
(which very likely do), it will want to upgrade everything. Let it go through
the upgrade process.</p>
<p>Building the project now will most likely fail due to a missing file
miniupnpcstrings.h . This file needs to be generated and the way to do that is
to run a script in that folder called updateminiupnpstrings.sh. You will most
probably need something like <a href="https://www.cygwin.com/">cygwin</a> to for this
script to work as it a <a href="https://en.wikipedia.org/wiki/Unix_shell">unix shell</a> to
work</p>
<p>Once the miniupnpcstrings.h has been generated, we also need to follow some
instructions for Unreal Engine
for <a href="https://wiki.unrealengine.com/Linking_Static_Libraries_Using_The_Build_System">Linking Static Libraries Using The Build System</a>,
particularly the section on customizations for targeting UE4 modules.</p>
<p>From the project properties page, choose configuration manager. From
the <strong>Active Solutions Platform</strong>, select new and type in or select x64 and save
it. You have to do this for only one of the projects.</p>
<p>Building of the static project will fail since it can&rsquo;t find the lib which is
now in x64Release as opposed to just Release. The exe is not required for
integrating with Unreal Engine, but if you want to complete the build, just fix
the path in Project <code>Properties -&gt; Linker -&gt; Input</code>.</p>
<p>You should choose the release build instead of the debug build and you should
now be able to build the solution from visual studio. It did pop up some
warnings for me, but the build completed successfully.</p>
<p>The rest of the instructions are from the unreal engine documentation about
integrating static libraries, starting from section about
<a href="https://wiki.unrealengine.com/Linking_Static_Libraries_Using_The_Build_System#Third_Party_Directory">Third Party Directory</a></p>]]></content:encoded></item><item><title>[UE4] Source Control</title><link>https://icle.es/2015/12/15/ue4-source-control/</link><pubDate>Tue, 15 Dec 2015 10:35:17 +0000</pubDate><guid>https://icle.es/2015/12/15/ue4-source-control/</guid><description>&lt;p>As with any software project, it is important to use some form of source control
solution. For most software projects, there are a number of good solutions out
there. However, in the world of game development, most of these are not viable
since they won&amp;rsquo;t handle binary files very well, and unreal engine (as most
games) will have a large amount of binary resources.&lt;/p>
&lt;p>Perforce is a good tool for small teams since it is free for teams with less
than 20 developers.&lt;/p></description><content:encoded><![CDATA[<p>As with any software project, it is important to use some form of source control
solution. For most software projects, there are a number of good solutions out
there. However, in the world of game development, most of these are not viable
since they won&rsquo;t handle binary files very well, and unreal engine (as most
games) will have a large amount of binary resources.</p>
<p>Perforce is a good tool for small teams since it is free for teams with less
than 20 developers.</p>
<p>Another thing that can be confusing is what files/folders to add into the source
control. Generally, we do not want to include any files which can be
auto-generated (e.g. builds) or are transient (e.g. logs). You should generally
include the following folders into source control</p>
<ul>
<li>Config</li>
<li>Content</li>
<li><code>Intermediate/ProjectFiles/&lt;projectname&gt;.vcxproj\*</code> (the .vcxproj.user file
may not be relevant if there are multiple developers)</li>
<li>Source</li>
<li><code>&lt;projectname&gt;.sln</code></li>
<li><code>&lt;projectname&gt;/.uproject</code></li>
</ul>
<p>I found it odd that the project file is in the intermediate folder since one
wouldn&rsquo;t intuitively think to include it in source control</p>
]]></content:encoded></item><item><title>Death of an Immortal</title><link>https://icle.es/endeavours/death-of-an-immortal/</link><pubDate>Mon, 13 Jul 2015 20:30:28 +0100</pubDate><guid>https://icle.es/endeavours/death-of-an-immortal/</guid><description>&lt;p>On a few hungover mornings, occasionally heartbroken, other times merely
inspired, I wrote &lt;a href="https://icle.es/tags/death-of-an-immortal">a handful of short stories&lt;/a>&lt;/p>
&lt;p>Many years later, I sat down and expanded it out into a novel size story. As my
first writing venture, it is far from ideal. I clearly loved exposition and I
probably go through enough story there to cover a handful of novels rather than
one.&lt;/p>
&lt;p>In my mind, there were at least two more such exposition filled books yet to
come. Alas, I ran out of steam and motivation and all that is left is this one
book.&lt;/p></description><content:encoded><![CDATA[<p>On a few hungover mornings, occasionally heartbroken, other times merely
inspired, I wrote <a href="https://icle.es/tags/death-of-an-immortal">a handful of short stories</a></p>
<p>Many years later, I sat down and expanded it out into a novel size story. As my
first writing venture, it is far from ideal. I clearly loved exposition and I
probably go through enough story there to cover a handful of novels rather than
one.</p>
<p>In my mind, there were at least two more such exposition filled books yet to
come. Alas, I ran out of steam and motivation and all that is left is this one
book.</p>
<p>I will find it and include it here</p>
]]></content:encoded></item><item><title>Unending Love</title><link>https://icle.es/2015/06/20/unending-love/</link><pubDate>Sat, 20 Jun 2015 13:11:49 +0000</pubDate><guid>https://icle.es/2015/06/20/unending-love/</guid><description>&lt;p>I seem to have loved you in numberless forms, numberless times\&lt;/p>
&lt;p>In life after life, in age after age, forever,&lt;br>
My spellbound heart has made and remade the necklace of songs,&lt;/p>
&lt;p>That you take as a gift, to wear around your neck in many forms&lt;/p></description><content:encoded><![CDATA[<p>I seem to have loved you in numberless forms, numberless times\</p>
<p>In life after life, in age after age, forever,<br>
My spellbound heart has made and remade the necklace of songs,</p>
<p>That you take as a gift, to wear around your neck in many forms</p>
]]></content:encoded></item><item><title>The Joy of Misery</title><link>https://icle.es/2015/06/19/the-joy-of-misery/</link><pubDate>Fri, 19 Jun 2015 20:34:10 +0000</pubDate><guid>https://icle.es/2015/06/19/the-joy-of-misery/</guid><description>&lt;p>I want to write of happy things,&lt;br>
Joyous ocassions,&lt;br>
Of which there sure are aplenty&lt;br>
Yet, for some some reason,&lt;br>
Perhaps a long standing conditioning,&lt;br>
I keep getting dragged back,&lt;br>
Back into the mud,&lt;br>
Into the quicksand,&lt;br>
A murky swamp\&lt;/p>
&lt;p>Why oh why?&lt;br>
Why is there so much joy,&lt;br>
In the misery and sorrow?&lt;br>
In the chewing bubble gum,&lt;br>
To solve a maths equation.\&lt;/p>
&lt;p>Why oh why,&lt;br>
Do I,&lt;br>
Am I,&lt;br>
Do I,&lt;br>
With little to carry around,&lt;br>
Worry that I indeed have little,&lt;br>
I have only this trouble,&lt;br>
This trouble or that\&lt;/p></description><content:encoded><![CDATA[<p>I want to write of happy things,<br>
Joyous ocassions,<br>
Of which there sure are aplenty<br>
Yet, for some some reason,<br>
Perhaps a long standing conditioning,<br>
I keep getting dragged back,<br>
Back into the mud,<br>
Into the quicksand,<br>
A murky swamp\</p>
<p>Why oh why?<br>
Why is there so much joy,<br>
In the misery and sorrow?<br>
In the chewing bubble gum,<br>
To solve a maths equation.\</p>
<p>Why oh why,<br>
Do I,<br>
Am I,<br>
Do I,<br>
With little to carry around,<br>
Worry that I indeed have little,<br>
I have only this trouble,<br>
This trouble or that\</p>
<p>I am lacking,<br>
Not in joy,<br>
Yes in joy,<br>
For I am lacking,<br>
In troubles\</p>
]]></content:encoded></item><item><title>[UE4] Intellisense Errors and USTRUCT</title><link>https://icle.es/2015/02/16/ue4-intellisense-errors-and-ustruct/</link><pubDate>Mon, 16 Feb 2015 16:55:32 +0000</pubDate><guid>https://icle.es/2015/02/16/ue4-intellisense-errors-and-ustruct/</guid><description>&lt;p>The Intellisense feature of Visual Studio, while useful is quite different from
the compiler and on occassion produces false positive errors. According to the
document about
&lt;a href="https://docs.unrealengine.com/latest/INT/Programming/Development/VisualStudioSetup/index.html">Setting Up Visual Studio for UE4&lt;/a>,
this can happen for few possible reason&lt;/p>
&lt;blockquote>
&lt;ul>
&lt;li>The IntelliSense compiler (EDG) is more strict than the MSVC compiler.&lt;/li>
&lt;li>Some #defines are setup differently for IntelliSense than when building
normally.&lt;/li>
&lt;li>C++ compiled by IntelliSense is always treated as 32-bit.&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;p>However, there is also the case where the Intellisense is just plain wrong.
USTRUCT() structures are just such a case. You will find that Intellisense
complains about definitions with the structures defined as USTRUCT.&lt;/p></description><content:encoded><![CDATA[<p>The Intellisense feature of Visual Studio, while useful is quite different from
the compiler and on occassion produces false positive errors. According to the
document about
<a href="https://docs.unrealengine.com/latest/INT/Programming/Development/VisualStudioSetup/index.html">Setting Up Visual Studio for UE4</a>,
this can happen for few possible reason</p>
<blockquote>
<ul>
<li>The IntelliSense compiler (EDG) is more strict than the MSVC compiler.</li>
<li>Some #defines are setup differently for IntelliSense than when building
normally.</li>
<li>C++ compiled by IntelliSense is always treated as 32-bit.</li>
</ul></blockquote>
<p>However, there is also the case where the Intellisense is just plain wrong.
USTRUCT() structures are just such a case. You will find that Intellisense
complains about definitions with the structures defined as USTRUCT.</p>
<p>This may not be a big deal, except for the fact these fales positives makes it
difficult to spot actual errors and I like to see a clean workspace with no
errors (not even false positives). Fortunately, there is a workaround. You can
include the USTRUCT definitions only if it is not being compiled by
Intellisense.</p>
<p>Fortunately, it is just the GENERATED_USTRUCT_BODY() line that is the culprit.
To get these errors to go away, instead of that single line, use the following</p>
```c++
#ifndef __INTELLISENSE__ /* Eliminating erroneous Intellisense Squigglies
*/
GENERATED_USTRUCT_BODY()
#endif
```
<p>That&rsquo;ll make those pesky squiggly lines to disappear and give you a nice clean
workspace :-D</p>
]]></content:encoded></item><item><title>[UE4] Getting Sprint to work (C++)</title><link>https://icle.es/2015/01/25/1038/</link><pubDate>Sun, 25 Jan 2015 13:29:21 +0000</pubDate><guid>https://icle.es/2015/01/25/1038/</guid><description>&lt;p>Getting Shift to sprint in the Unreal Engine 4 is very easy. There are
technically two ways of doing it.&lt;/p>
&lt;ul>
&lt;li>Change the scale down to say 0.5 and then increase it to 1.0 for sprinting&lt;/li>
&lt;li>Change the &lt;code>MaxWalkSpeed&lt;/code> for walking and sprinting.&lt;/li>
&lt;/ul>
&lt;p>I prefer the second technique because it will keep the code simple enough and
continue to function as expected with analog controllers.&lt;/p>
&lt;p>Lets start by adding the input action bindings&lt;/p></description><content:encoded><![CDATA[<p>Getting Shift to sprint in the Unreal Engine 4 is very easy. There are
technically two ways of doing it.</p>
<ul>
<li>Change the scale down to say 0.5 and then increase it to 1.0 for sprinting</li>
<li>Change the <code>MaxWalkSpeed</code> for walking and sprinting.</li>
</ul>
<p>I prefer the second technique because it will keep the code simple enough and
continue to function as expected with analog controllers.</p>
<p>Lets start by adding the input action bindings</p>
<p><code>Edit -&gt; Project Settings -&gt; Input -&gt; Bindings</code> and add an action for <strong>Sprint</strong>
with <strong>Left Shift</strong> for the key.</p>
<p>
  <img src="/assets/2015/01/Sprint%2520Keybinding.png" alt="">

</p>
<p>All the code is in the Character class.</p>
<p>Within the header file, there are two blueprint variables for the <code>WalkSpeed</code>
and <code>SprintSpeed</code> so they can be modified easily.</p>
<p>We also override the <code>BeginPlay()</code> Method. This is used to default to Walking.</p>
<p>Add the following to your character header file</p>
```cpp
/* The speed at which the character will be walking */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Character)
uint16 WalkSpeed;

/*_ The speed at which the character will be sprinting */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Character)
uint16 SprintSpeed;

virtual void BeginPlay();

protected:
/* Enables Sprinting for the character */
void EnableSprint();

/* Disables Sprinting again */
void DisableSprint();
```
<p>Add the following to the constructor so we have defaults</p>
```cpp
AMyCharacter::AMyCharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer) {

    // Other code // Set the default Defaults ;-)
    WalkSpeed = 250;
    SprintSpeed = 600;

}
```
<p>Plug in the Sprint action to the methods by adding the following lines into
the <code>SetupPlayerInputComponent</code> method:</p>
```cpp
InputComponent->BindAction("Sprint", IE_Pressed, this, &AMyCharacter::EnableSprint);
InputComponent->BindAction("Sprint", IE_Released, this, &AMyCharacter::DisableSprint);
```
<p>And implement the rest of the methods</p>
```cpp
void AVSCharacter::BeginPlay() {
    // Ensure the player starts with Walking
    DisableSprint();
}

void AVSCharacter::EnableSprint() {
    CharacterMovement->MaxWalkSpeed = SprintSpeed;
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Black, TEXT("Sprintin"));
}

void AVSCharacter::DisableSprint() {
    CharacterMovement->MaxWalkSpeed = WalkSpeed; GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Black, TEXT("Walkin"));
}
```
]]></content:encoded></item><item><title>[UE4] Getting Multiplayer Player Pawn AI Navigation to work (C++)</title><link>https://icle.es/2014/08/25/ue4-getting-multiplayer-player-pawn-ai-navigation-to-work-c/</link><pubDate>Mon, 25 Aug 2014 15:52:34 +0000</pubDate><guid>https://icle.es/2014/08/25/ue4-getting-multiplayer-player-pawn-ai-navigation-to-work-c/</guid><description>&lt;p>Unreal Engine is an awesome piece of technology making it easy to do almost
anything you might want.&lt;/p>
&lt;p>When using the Top Down view however, there is a hurdle to get over when trying
to get multiplayer to work. This is a C++ project solution to this problem based
on
a &lt;a href="https://answers.unrealengine.com/questions/34074/does-ue4-have-client-side-prediction-built-in.html">BluePrints solution&lt;/a>.&lt;/p>
&lt;p>The basic problem stems from the fact that&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;&lt;em>SimpleMoveToLocation&lt;/em> was never intended to be used in a network
environment. It&amp;rsquo;s simple after all ;) Currently there&amp;rsquo;s no dedicated engine
way of making player pawn follow a path. &amp;quot; (from the same page)&lt;/p>&lt;/blockquote>
&lt;p>To be able to get a working version of &lt;em>SimpleMoveToLocation&lt;/em>, we need to do the
following:&lt;/p></description><content:encoded><![CDATA[<p>Unreal Engine is an awesome piece of technology making it easy to do almost
anything you might want.</p>
<p>When using the Top Down view however, there is a hurdle to get over when trying
to get multiplayer to work. This is a C++ project solution to this problem based
on
a <a href="https://answers.unrealengine.com/questions/34074/does-ue4-have-client-side-prediction-built-in.html">BluePrints solution</a>.</p>
<p>The basic problem stems from the fact that</p>
<blockquote>
<p>&ldquo;<em>SimpleMoveToLocation</em> was never intended to be used in a network
environment. It&rsquo;s simple after all ;) Currently there&rsquo;s no dedicated engine
way of making player pawn follow a path. &quot; (from the same page)</p></blockquote>
<p>To be able to get a working version of <em>SimpleMoveToLocation</em>, we need to do the
following:</p>
<ul>
<li>Create a proxy player class (BP_WarriorProxy is BP solution)</li>
<li>Set the proxy class as the default player controller class</li>
<li>Move the camera to the proxy (Since the actual player class is on the server,
we can&rsquo;t put a camera on it to display on the client)</li>
</ul>
<p>The BP solution talks about four classes - our counterparts are as follows:</p>
<ul>
<li>BP_WarriorProxy: ADemoPlayerProxy</li>
<li>BP_WarriorController: ADemoPlayerController (Auto-created when creating a c++
top down project)</li>
<li>BP_Warrior: ADemoCharacter (Auto-created when creating a C++ top down project)</li>
<li>BP_WarriorAI: AAIController - we have no reason to subclass it.</li>
</ul>
<p>So, from a standard c++ top down project, the only class we need to add is the
ADemoPlayerProxy - so go ahead and do that first.</p>
<p>The first thing we&rsquo;ll do is rewire the ADemoGameMode class to initialise the
proxy class instead of the default MyCharacter Blueprint.</p>
```c
ADemoGameMode::ADemoGameMode(const class FPostConstructInitializeProperties&
PCIP) : Super(PCIP)
    {
    // use our custom PlayerController class
    PlayerControllerClass = ADemoPlayerController::StaticClass();

    // set default pawn class to our Blueprinted character /_ static
    ConstructorHelpers::FClassFinder<apawn>
    PlayerPawnBPClass(TEXT("/Game/Blueprints/MyCharacter"));
    if (PlayerPawnBPClass.Class != NULL)
    {
        DefaultPawnClass = PlayerPawnBPClass.Class;
    }

    DefaultPawnClass = ADemoPlayerProxy::StaticClass();
}
```
<p>Our Player Proxy class declaration</p>
```cpp
/* This class will work as a proxy on the client - tracking
   the movements of the real Character on the server side
   and sending back controls. */
UCLASS() class Demo_API ADemoPlayerProxy : public APawn
{
   GENERATED_UCLASS_BODY()
        /** Top down camera */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera) TSubobjectPtr<UCameraComponent> TopDownCameraComponent;

        /* Camera boom positioning the camera above the character */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
    TSubobjectPtr<USpringArmComponent=""> CameraBoom;

    // Needed so we can pick up the class in the constructor and spawn it elsewhere
    TSubclassOf<AActor> CharacterClass;

    // Pointer to the actual character. We replicate it so we know its
    //location for the camera on the client
    UPROPERTY(Replicated) ADemoCharacter* Character;

    // The AI Controller we will use to auto-navigate the player AAIController\*
    PlayerAI;

    // We spawn the real player character and other such elements here virtual void
    BeginPlay() override;

    // Use do keep this actor in sync with the real one void Tick(float DeltaTime);

    // Used by the controller to get moving to work void MoveToLocation(const
    ADemoPlayerController* controller, const FVector& vector);
};
```
<p>and the definition:</p>
```cpp
#include "Demo.h"
#include "DemoCharacter.h"
#include "AIController.h"
#include "DemoPlayerProxy.h"
#include "UnrealNetwork.h"

ADemoPlayerProxy::ADemoPlayerProxy(const class FPostConstructInitializeProperties& PCIP) : Super(PCIP)
{
    // Don't rotate character to camera direction
    bUseControllerRotationPitch = false;
    bUseControllerRotationYaw = false;
    bUseControllerRotationRoll = false;

    // It seems that without a RootComponent, we can't place the Actual Character easily
    TSubobjectPtr<UCapsuleComponent> TouchCapsule = PCIP.CreateDefaultSubobject<ucapsulecomponent>(this, TEXT("dummy"));
    TouchCapsule->InitCapsuleSize(1.0f, 1.0f);
    TouchCapsule->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    TouchCapsule->SetCollisionResponseToAllChannels(ECR_Ignore);
    RootComponent = TouchCapsule;

    // Create a camera boom... CameraBoom =
    PCIP.CreateDefaultSubobject<USpringArmComponent>(this, TEXT("CameraBoom"));
    CameraBoom->AttachTo(RootComponent);
    CameraBoom->bAbsoluteRotation = true; // Don't want arm to rotate when
    character does CameraBoom->TargetArmLength = 800.f;
    CameraBoom->RelativeRotation = FRotator(-60.f, 0.f, 0.f);
    CameraBoom->bDoCollisionTest = false; // Don't want to pull camera in when it collides with level

    // Create a camera...
    TopDownCameraComponent = PCIP.CreateDefaultSubobject<UCameraComponent>(this, TEXT("TopDownCamera"));
    TopDownCameraComponent->AttachTo(CameraBoom, USpringArmComponent::SocketName);
    TopDownCameraComponent->bUseControllerViewRotation = false; // Camera does not rotate relative to arm

    if (Role == ROLE_Authority)
    {
        static ConstructorHelpers::FObjectFinder<UClass> PlayerPawnBPClass(TEXT("/Game/Blueprints/MyCharacter.MyCharacter_C"));
        CharacterClass = PlayerPawnBPClass.Object;
    }

}

void ADemoPlayerProxy::BeginPlay()
{
    Super::BeginPlay();
    if (Role == ROLE_Authority)
    {
        // Get current location of the Player Proxy
        FVector Location = GetActorLocation(); FRotator Rotation = GetActorRotation();

        FActorSpawnParameters SpawnParams;
        SpawnParams.Owner = this;
        SpawnParams.Instigator = Instigator;
        SpawnParams.bNoCollisionFail = true;

        // Spawn the actual player character at the same location as the Proxy
        Character = Cast<ADemoCharacter>(GetWorld()->SpawnActor(CharacterClass, &Location, &Rotation, SpawnParams));

        // We use the PlayerAI to control the Player Character for Navigation
        PlayerAI = GetWorld()->SpawnActor<AAIController>(GetActorLocation(), GetActorRotation()); PlayerAI->Possess(Character);
    }

}

void ADemoPlayerProxy::Tick(float DeltaTime) {

    Super::Tick(DeltaTime);
    if (Character)
    {
        // Keep the Proxy in sync with the real character
        FTransform CharTransform = Character->GetTransform();
        FTransform MyTransform = GetTransform();

        FTransform Transform;
        Transform.LerpTranslationScale3D(CharTransform, MyTransform, ScalarRegister(0.5f));

        SetActorTransform(Transform);

} }

void ADemoPlayerProxy::MoveToLocation(const ADemoPlayerController* controller, const FVector& DestLocation)
{
    /** Looks easy - doesn't it.

    - What this does is to engage the AI to pathfind.
    - The AI will then "route" the character correctly.
    - The Proxy (and with it the camera), on each tick, moves to the location of the
      real character
    -
    - And thus, we get the illusion of moving with the Player Character */

      PlayerAI->MoveToLocation(DestLocation);
}

void ADemoPlayerProxy::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const
{

    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    // Replicate to Everyone
    DOREPLIFETIME(ADemoPlayerProxy, Character);
}
```
<p>We&rsquo;ll now cover changes to the Player Controller. We&rsquo;ll rewire it here to ask
the proxy to move, which will in turn ask the AIController to find a path and
move the real player character.</p>
<p>This involves changing the <code>SetMoveDestination</code> method to call a server side
method to move the character. When the character moves, the player Proxy will
automatically mirror the movements.</p>
<p>In the header file, add the following function</p>
```cpp
/*_ Navigate player to the given world location (Server Version) */
UFUNCTION(reliable, server, WithValidation) void
ServerSetNewMoveDestination(const FVector DestLocation);
```
<p>Now let&rsquo;s implement it (DemoPlayerController.cpp):</p>
```
bool ADemoPlayerController::ServerSetNewMoveDestination_Validate(const FVector
DestLocation)
{
    return true;
}

/* Actual implementation of the ServerSetMoveDestination method */
void ADemoPlayerController::ServerSetNewMoveDestination_Implementation(const
FVector DestLocation)
{
    ADemoPlayerProxy* Pawn = Cast<ademoplayerproxy>(GetPawn());
    if (Pawn)
    {
        UNavigationSystem* const NaDemoys = GetWorld()->GetNavigationSystem();
        float const Distance = FVector::Dist(DestLocation, Pawn->GetActorLocation());

        // We need to issue move command only if far enough in order for walk animation
        to play correctly
        if (NaDemoys && (Distance > 120.0f))
        {
            //NaDemoys->SimpleMoveToLocation(this, DestLocation);
            Pawn->MoveToLocation(this, DestLocation);
        }
    }

}
```
<p>And finally, the rewiring of the original method:</p>
```cpp
void ADemoPlayerController::SetNewMoveDestination(const FVector DestLocation)
{
    ServerSetNewMoveDestination(DestLocation);
}
```
<p>Finally, in terms of the character class, the only change is really to remove
the camera components that we moved to the Player Proxy which I shall leave to
you :-p</p>]]></content:encoded></item><item><title>Frostfall &amp;amp; DayZ</title><link>https://icle.es/2014/07/04/frostfall-dayz/</link><pubDate>Fri, 04 Jul 2014 03:24:55 +0000</pubDate><guid>https://icle.es/2014/07/04/frostfall-dayz/</guid><description>&lt;p>When it comes to gaming, my preferences seem to lie in the RPG Genre with
Strategy and Simulators coming in second. It is not often that I dabble in First
Person shooters. While I have some fond memories of the multiplayer mods that
came out of Half Life, I did not find myself curious about the
&lt;a href="https://www.playne.com/games/battlefield-4" title="Features of Battlefield 4">features of Battlefield 4&lt;/a>.&lt;/p>
&lt;p>The
&lt;a href="https://www.playne.com/games/dayz-standalone" title="Features of DayZ (Standalone)">features of DayZ&lt;/a>,
on the other hand are interesting. While it doesn't really have any RPG
elements, it does seem to have unusual features and when the mod came out, it
was noticed for being different... and difficult...&lt;/p></description><content:encoded><![CDATA[<p>When it comes to gaming, my preferences seem to lie in the RPG Genre with
Strategy and Simulators coming in second. It is not often that I dabble in First
Person shooters. While I have some fond memories of the multiplayer mods that
came out of Half Life, I did not find myself curious about the
<a href="https://www.playne.com/games/battlefield-4" title="Features of Battlefield 4">features of Battlefield 4</a>.</p>
<p>The
<a href="https://www.playne.com/games/dayz-standalone" title="Features of DayZ (Standalone)">features of DayZ</a>,
on the other hand are interesting. While it doesn't really have any RPG
elements, it does seem to have unusual features and when the mod came out, it
was noticed for being different... and difficult...</p>
<p>My brother has been trying to get me to play DayZ for a little while and I did
try the mod a while back. I found it to be a bit &quot;too real&quot; to be really
enjoyable. From the near explosion of similarly themed games recently, it does
seem like a popular genre though...</p>
<p>The new hunting and weather mechanics that they are talking about - particularly
the ones being compared to Frostfall in Skyrim is particularly interesting. I
thoroughly enjoyed playing through hardcore mode in Fallout: NV and the
Frostfall and the more complex needs of Skyrim.</p>
<p>The permadeath part of the game is unattractive for me though - but maybe one
day this will change..</p>
]]></content:encoded></item><item><title>Road to Rome</title><link>https://icle.es/2014/06/12/road-to-rome/</link><pubDate>Thu, 12 Jun 2014 16:13:01 +0000</pubDate><guid>https://icle.es/2014/06/12/road-to-rome/</guid><description>&lt;p>It was just another day,&lt;br>
or so it seemed…&lt;/p>
&lt;p>The sun shone ever so bright&lt;br>
on a soul dark and bitter…&lt;/p>
&lt;p>Sipping another drag of nicotine,&lt;br>
quaffing another shot of something strong,&lt;br>
at least in the mind,&lt;br>
of the soul still so dark and bitter.&lt;/p>
&lt;p>The heat bearing down… &lt;br>
trying its best to burn away..&lt;br>
at least at the edges of the bitterness…&lt;br>
and even the sun…&lt;br>
could do very little…&lt;/p></description><content:encoded><![CDATA[<p>It was just another day,<br>
or so it seemed…</p>
<p>The sun shone ever so bright<br>
on a soul dark and bitter…</p>
<p>Sipping another drag of nicotine,<br>
quaffing another shot of something strong,<br>
at least in the mind,<br>
of the soul still so dark and bitter.</p>
<p>The heat bearing down… <br>
trying its best to burn away..<br>
at least at the edges of the bitterness…<br>
and even the sun…<br>
could do very little…</p>
<p>The birds chirping tried…<br>
so did everything in its path..<br>
but none had any effect.</p>
<p>Then it encountered something unexpected…<br>
Hidden away in a corner of the world!</p>
<p>Something it had given up on finding…<br>
a time before time itself.</p>
<p>In a moment, <br>
lasting an eternity,<br>
it changed… <br>
everything changed…<br>
existence itself..<br>
was no longer what it was…</p>
<p>The energy, the optimism,<br>
the joy and love expressed..</p>
<p>Beyond words,<br>
inexpressible in action.</p>
<p>Even art fails to grasp<br>
that which happens in an instant.</p>
<p>Some call it love at first sight.</p>
<p>Other call it fate..</p>
<p>Others yet call it destiny.</p>
<p>I just called it my destination.</p>
<p>Everything conspired,<br>
sometimes cruelly,<br>
sometimes sweetly,<br>
sometimes stubbornly….</p>
<p>But as all roads once led…<br>
led to Rome…</p>
<p>All roads led me to you…</p>
<p>And time is suddenly moving too fast…<br>
Trying to slow it down,<br>
but time is more stubborn,<br>
than poor old me&hellip;</p>
<p>Two years, a lifetime, many lifetimes..</p>
<p>A brand new life - one barely imagined..<br>
barely contained…</p>
<p>Happiness discovered; love infinite&hellip;</p>
]]></content:encoded></item><item><title>Elder Scrolls Online - The good, the bad, the ugly</title><link>https://icle.es/2014/06/10/elder-scrolls-online-the-good-the-bad-the-ugly/</link><pubDate>Tue, 10 Jun 2014 05:13:13 +0000</pubDate><guid>https://icle.es/2014/06/10/elder-scrolls-online-the-good-the-bad-the-ugly/</guid><description>&lt;p>I must have played
&lt;a href="http://elderscrollsonline.com/" title="Elder Scrolls Online Main Website">TESO&lt;/a> for
at least 200 hours by now. It&amp;rsquo;s not a heck of a lot in the grand scheme of
things - particularly in the world of MMORPG&amp;rsquo;s. This is however, the first MMO
into which I have invested so much time (played WoW and SWToR a fair bit - both
got to around level 40). When Skyrim first came out, I took a week off and
played it more or less solid through the week. Over the years, Skyrim had me
traipse through its landscape for over 150 hours.&lt;/p>
&lt;p>While this post will talk about my gripes with the game, it should be noted that
I still love this game and look forward to hundreds more hours of playtime. The
gripes are on paper to get them off my chest and hope that they will be
resolved. Gripes about TESO are not hard to find - the
&lt;a href="http://forums.elderscrollsonline.com/categories/EN-general-discussion" title="ESO Forums">eso forums&lt;/a>
are rife with complaints and bugs - but that&amp;rsquo;s really besides the point&amp;hellip; :-p&lt;/p>
&lt;p>TESO was a game I was very much waiting for; breath baited. Took time off work,
got my brother to take time of work and played it in earnest. It is difficult to
put my finger on what was expected from this. In all honesty a multiplayer
version of Skyrim would have been fine for me. It was not the MMO elements that
attracted me but the multiplayer elements.&lt;/p></description><content:encoded><![CDATA[<p>I must have played
<a href="http://elderscrollsonline.com/" title="Elder Scrolls Online Main Website">TESO</a> for
at least 200 hours by now. It&rsquo;s not a heck of a lot in the grand scheme of
things - particularly in the world of MMORPG&rsquo;s. This is however, the first MMO
into which I have invested so much time (played WoW and SWToR a fair bit - both
got to around level 40). When Skyrim first came out, I took a week off and
played it more or less solid through the week. Over the years, Skyrim had me
traipse through its landscape for over 150 hours.</p>
<p>While this post will talk about my gripes with the game, it should be noted that
I still love this game and look forward to hundreds more hours of playtime. The
gripes are on paper to get them off my chest and hope that they will be
resolved. Gripes about TESO are not hard to find - the
<a href="http://forums.elderscrollsonline.com/categories/EN-general-discussion" title="ESO Forums">eso forums</a>
are rife with complaints and bugs - but that&rsquo;s really besides the point&hellip; :-p</p>
<p>TESO was a game I was very much waiting for; breath baited. Took time off work,
got my brother to take time of work and played it in earnest. It is difficult to
put my finger on what was expected from this. In all honesty a multiplayer
version of Skyrim would have been fine for me. It was not the MMO elements that
attracted me but the multiplayer elements.</p>
<p>If they had merged Skyrim and Borderlands (2), it would be a perfect game - or
so went the thought process. The
<a href="https://www.playne.com/games/elder-scrolls-online" title="Features of Elder Scrolls Online">features of Elder Scrolls Online</a>
do seem to be (on paper at least) a good mix of MMO Features and Elder Scrolls
Features.</p>
<p>I don&rsquo;t care about the &ldquo;bugs&rdquo; since they seem to be pretty on the ball with
regards to getting them fixed and while there are a ton of them, and they are
annoying - let&rsquo;s be fair - would it be an Elder Scrolls Series <strong>without</strong> more
bugs than a dark and damp cave?</p>
<h2 id="forced-single-player---really">Forced Single Player - Really?</h2>
<p>The first gripe with the game and still by far the largest is this. For an MMO,
there is an awful lot of content that you are forced to play single player. This
makes no sense to me!</p>
<p>When the game was designed / developed, did they not know that this was an MMO?
Why create a story that follows the entire Elder Scrolls series when you are
someone uniquely special? It was such a massive opportunity wasted to create a
story line where you could have an immersive experience accepting all the other
people jumping around the place.</p>
<p>I think it was DC Universe Online that made it so powers were like essences that
were scattered and &ldquo;creating&rdquo; a number of superheroes. This makes sense and
maintains the immersion. Most MMO&rsquo;s seem to have this kind of a story - and it
makes a lot of sense. Why would you make an MMO where you are &rsquo;the
Vestige/Soulless One&rsquo; - a unique hero? I mean, you get out of coldharbour and
instantly meet thousands of other Vestiges&hellip; :-/ WTH man, you said I was
special!</p>
<p>In TESO, this immersion is regularly and quite badly broken. You are asked to do
some missions and you see other people do the same mission - not a big deal in
itself except when the completion of the mission is supposed to change the
world - and you see someone else finish it - but nothing happens - you still
have to finish it. If the other players were just not displayed, that would have
been better.</p>
<p>Back to the soloing of missions - I can completely understand the need to
instance areas to a group(for bosses perhaps). However, for the main mission and
the missions for the two guilds (fighters and mages), you are forced to solo it.
This makes no sense. If I wanted to do missions on my own - I&rsquo;d play Skyrim,
Oblivion or Morrowind - or indeed other single player games.</p>
<p>I guess in summary - they seem to have failed in making it an MMO OR a Single
player game. In the main MMO area, there is too much immersion breaking
interaction with other players and where you do want to be able to group, you
are forced to solo.</p>
<p>Another gripe - but its a small one because they are getting fixed are the sheer
number of mission bugs - some of them are hard progress blocking bugs. The team
seems to be working hard on resolving these so this should just be a matter of
time. Unfortunately, it&rsquo;s the early adopters who suffer the most.</p>
<h2 id="xp---in-an-elder-scrolls-game">XP - In an Elder Scrolls Game?</h2>
<p>Oh the next gripe is about this game being called Elder Scrolls Online but one
of the critical parts of what an Elders Scrolls game should be is only half
implemented. The main reason I started playing the Elder Scrolls games was that
the skills levelled up not
<a href="https://www.playne.com/features/gaining-xp" title="Gaining XP (Feature)">based on XP</a>
but on
<a href="http://www.playne.com/features/using-skills" title="Level Up Using Skills (Feature)">how much it was used</a>.</p>
<p>Sure, Morrowind had some problems in that skills levelled up quicker as you
levelled up since it levelled skills based on <strong>successful</strong> use of a skill and
the better you were at it, the higher chance you had of success.</p>
<p>In my mind, this would be easily solved by changing it to level up based on how
much it was <strong>used</strong> - successfully or not. Of course, this may have other
complications.</p>
<p>In no way, however, does it make sense to level skills based on XP gained. This
at best just breaks immersion - but its just heartbreaking. If I want to get
better at a skill, I should be using it - not just swapping my toolbar and
handing in the missions!</p>
<h2 id="class-specific-skills-yeah">Class Specific Skills&hellip; Yeah?</h2>
<p>If we were to pick one word to define the entire Elder Scrolls Series - which
word do you think would be picked? No matter what you think - the word
<strong>Freedom</strong> would be up at the top or very near it&hellip; How does class specific
skills fit into that? Each class has three skill lines, and at this point, there
are four classes. This means that that any given point, every character is
missing out on 9 entire skill lines.</p>
<p>It very much feels like they got an MMO expert in to consult and they were told
&ldquo;There is only one way to do MMOs and that&rsquo;s to follow all the other ones out
there.&rdquo; Well, let&rsquo;s be fair, if we wanted to play a game like the other ones out
there - well, we would just play the other ones out there. The same goes for why
I prefer Skyrim to the plethora of other games.</p>
<p>Shoehorning in the Elder Scrolls system of skill progression and getting XP
seems to have been a &ldquo;compromise&rdquo; they struck - but let me tell ya - I don&rsquo;t
like it! The whole idea of skills progressing as you use it works because it
feels &ldquo;real&rdquo;. &ldquo;Oh I found a new location and look, I am better at fighting with
swords&hellip;&rdquo; - eh?</p>
<p>Feels like a lazy way of bringing balance to a game and if you ask me - it makes
balancing <strong>more</strong> difficult since you now have to ensure that entire skill
lines are balances instead of just skills</p>
<h2 id="finally">Finally</h2>
<p>Am I going to stop playing TESO? Am I going to recommend not playing it? Hell
no! It is still, in my opinion, the best MMO out there&hellip; What breaks my heart
is that it could have been oh so much more. I very much think that they should
have maintained their faith in the product - one which has been massively
successful thus far! I can only hope that they see the light at some point and
open it all up&hellip;</p>]]></content:encoded></item><item><title>A Renegade in Skyrim (Part 1)</title><link>https://icle.es/2014/06/02/a-renegade-in-skyrim-part-1/</link><pubDate>Mon, 02 Jun 2014 05:01:43 +0000</pubDate><guid>https://icle.es/2014/06/02/a-renegade-in-skyrim-part-1/</guid><description>&lt;p>_Skyrim has been modded here to include Frostfall, Imps more complex needs,
alternate start and a few other mods (which should be largely inconsequential to
this log). From alternate start, random was chosen. _&lt;/p>
&lt;p>This was not a good start&amp;hellip; Floating in freezing water off the shore of skyrim
somewhere on the way to Solitude. Brrrr&amp;hellip; Grasping at the strands of
consciousness seemingly content to float away on the freezing waters, I dived in
to find a way out of here.&lt;/p>
&lt;p>There was luckily a few things hanging around in the capsized ship, some weapons
and some armour - could be worse. One of the crates also had some snowberry
extract as luck would have it. Swigging it down, dived down again and slowly
traipsed my way out of the ship before the first hit from the extract finished.&lt;/p>
&lt;p>Making my way to the bottom of the ship, which was ironically on top, there was
an aldmeri lady on top carrying some stuff, which I conveniently lifted :-D&lt;/p></description><content:encoded><![CDATA[<p>_Skyrim has been modded here to include Frostfall, Imps more complex needs,
alternate start and a few other mods (which should be largely inconsequential to
this log). From alternate start, random was chosen. _</p>
<p>This was not a good start&hellip; Floating in freezing water off the shore of skyrim
somewhere on the way to Solitude. Brrrr&hellip; Grasping at the strands of
consciousness seemingly content to float away on the freezing waters, I dived in
to find a way out of here.</p>
<p>There was luckily a few things hanging around in the capsized ship, some weapons
and some armour - could be worse. One of the crates also had some snowberry
extract as luck would have it. Swigging it down, dived down again and slowly
traipsed my way out of the ship before the first hit from the extract finished.</p>
<p>Making my way to the bottom of the ship, which was ironically on top, there was
an aldmeri lady on top carrying some stuff, which I conveniently lifted :-D</p>
<p>Now was the hard part, it would seem. I was surrounded by the icy ocean and the
cold breeze did not help much either. Preparing myself mentally, finished off
the last of the snowberry extract, hoped for the best and dived right into the
icy blue seas.</p>
<p>The swim to the shore was not that bad - the snowberry extract kept me going!
Once there, I managed to collect some deadwood and light a fire to dry me off
and warm me up. There really wasn&rsquo;t much of anything around - until I spotted a
camp - with a tent and a fire and everything. It was an absolute surprise how it
was missed the first time round - and it looked a lot nicer than the little camp
I put together with deadwood.</p>
<p>I went over there and looked around. There was an Argonian there - and although
I tried to talk to him, he was really not interested.</p>
<p>Looking around, I picked up something that was on one of the barrels, and the
Argonian friend did not take kindly to me snooping around - so I had to kill
him. Hmmmm.. The first drop of blood spilled on this land.. and it was all over
a misunderstanding&hellip; :-/</p>
<p>Anyway, I warmed up a bit there and looked around more to realise that I had to
swim across to another island - and I had no snowberry extract left. Well, the
hard way it will have to be  then. Fortunately, it was a short swim across and I
found a tent on the other side and a little camp which I used to warm up.</p>
<p>After that, a short hop skip and a jump later, I was in Dawnstar, and freezing.
As luck would have it, it was night and everything was closed.. it took me a
while, and it seemed like an eternity, but I finally found the inn&hellip; and warmed
myself up&hellip;</p>
<p>As I was falling asleep, it dawned on me that there were a couple of draugr that
I killed in a cave earlier but couldn&rsquo;t remember exactly when or where it was.
The cold and the tiredness drifted me off to sleep rather swiftly&hellip;</p>]]></content:encoded></item><item><title>Drupal Entities - PHP Class per bundle</title><link>https://icle.es/2014/04/17/drupal-entities-php-class-per-bundle/</link><pubDate>Thu, 17 Apr 2014 11:18:08 +0000</pubDate><guid>https://icle.es/2014/04/17/drupal-entities-php-class-per-bundle/</guid><description>&lt;p>If you would like a bit more polymorphism in your drupal entities, this might
cheer you up :-D&lt;/p>
&lt;p>I was looking for a way to have a class hierarchy that matched the bundle
&amp;ldquo;hierarchy&amp;rdquo; of entities in drupal. Yes, they are all &amp;ldquo;subclasses&amp;rdquo; of ONE parent,
but it is still useful to be able to have a class per bundle.&lt;/p>
&lt;p>The
&lt;a href="https://drupal.org/project/entity_bundle_plugin" title="entity bundle plugin">entity bundle plugin&lt;/a> does
a good job of providing a plugin framework to instantiate classes per bundle
type. There is also
&lt;a href="http://bojanz.wordpress.com/2013/07/19/entity-bundle-plugin/" title="how to use entity bundle plugin">an example of how to use this&lt;/a>.
However, this was a bit of overkill for me. I did however borrow the idea (and
some code) to implement it in a simpler fashion.&lt;/p></description><content:encoded><![CDATA[<p>If you would like a bit more polymorphism in your drupal entities, this might
cheer you up :-D</p>
<p>I was looking for a way to have a class hierarchy that matched the bundle
&ldquo;hierarchy&rdquo; of entities in drupal. Yes, they are all &ldquo;subclasses&rdquo; of ONE parent,
but it is still useful to be able to have a class per bundle.</p>
<p>The
<a href="https://drupal.org/project/entity_bundle_plugin" title="entity bundle plugin">entity bundle plugin</a> does
a good job of providing a plugin framework to instantiate classes per bundle
type. There is also
<a href="http://bojanz.wordpress.com/2013/07/19/entity-bundle-plugin/" title="how to use entity bundle plugin">an example of how to use this</a>.
However, this was a bit of overkill for me. I did however borrow the idea (and
some code) to implement it in a simpler fashion.</p>
<p>Implement a custom controller and override the create and the query methods</p>
```phg
    class MyEntityAPIController extends EntityAPIController {
      /**
       * Overrides EntityAPIController::query().
       */
      public function query($ids, $conditions, $revision_id = FALSE) {
        $query = $this->buildQuery($ids, $conditions, $revision_id);
        $result = $query->execute();
        $result->setFetchMode(PDO::FETCH_ASSOC);

        // Build the resulting objects ourselves, since the standard PDO ways of
        // doing that are completely useless.
        $objects = array();
        foreach ($result as $row) {
          $row['is_new'] = FALSE;
          $objects[] = $this->create($row);
        }
        return $objects;
      }

      /**
       * Overrides EntityAPIController::create().
       */
      public function create(array $values = array()) {
        if (!isset($values[$this->entityInfo['entity keys']['bundle']])) {
          throw new Exception(t('No bundle provided to MyEntityAPIController::create().'));
        }

        $bundle = $values[$this->entityInfo['entity keys']['bundle']];
          // Add is_new property if it is not set.
        $values += array(
          'is_new' => TRUE,
        );

        $default_class = isset($this->entityInfo['entity class']) ? $this->entityInfo['entity class'] : NULL;
        $class = isset($this->entityInfo['bundles'][$bundle]['entity class']) ? $this->entityInfo['bundles'][$bundle]['entity class'] : $default_class;
        if (!class_exists($class)) {
          $class = "Entity";
        }

        return new $class($values, $this->entityType);
      }

    }
```
<p>I can now define a PHP class for each bundle as follows in hook_entity_info</p>
```php
      $entity['myentity'] = array(
        'label' => t('myentity'),
        'module' => 'mymodule',
        'entity class' => 'MyEntity',  // Default entity class if not defined in bundle
        'controller class' => 'MyEntityAPIController',
        'base table' => 'myentity',
        'entity keys' => array(
          'id' => 'id',
          'label' => 'name',
          'bundle' => 'bundle',
        ),
        'bundles' => array(
          'bundle1' => array(
            'label' => 'Bundle 1',
            'entity class' => 'Bundle1Entity',
          ),
          'bundle2' => array(
            'label' => 'Bundle 2',
            'entity class' => 'Bundle2Entity',
          ),
        ),
```
<p>Don&rsquo;t forget to clear the cache and you should be able to get bundle specific
classes instantiated. It will fall back to the &rsquo;entity class&rsquo; defined for the
entity if the bundle &rsquo;entity class&rsquo; is not defined or cannot be found.</p>
]]></content:encoded></item><item><title>Destructible World in Battlefield 4</title><link>https://icle.es/2013/10/10/destructible-world-in-battlefield-4/</link><pubDate>Thu, 10 Oct 2013 11:36:26 +0000</pubDate><guid>https://icle.es/2013/10/10/destructible-world-in-battlefield-4/</guid><description>&lt;p>Found this video of an
&lt;a href="http://www.gamecupid.com/game-feature/4356" title="Destructible World in BattleField 4">entire skyscraper being destroyed&lt;/a>
in
&lt;a href="http://www.gamecupid.com/games/battlefield-4" title="Features of Battlefield 4">Battlefield 4&lt;/a>.
My! things have moved on a fair bit in the world of computer games. Here I was
enjoying the lush scenery of
&lt;a href="http://www.gamecupid.com/games/elder-scrolls-v-skyrim" title="Features of Skyrim">Skyrim&lt;/a>
and all of a sudden, an entire world that can be destroyed?&lt;/p>
&lt;p>It is a shame that with all of this power, some of the features I have really
enjoyed in games like witty banter with your companions (The Baldurs Gate series
being a very good example) are still so sorely missed. :-(&lt;/p></description><content:encoded><![CDATA[<p>Found this video of an
<a href="http://www.gamecupid.com/game-feature/4356" title="Destructible World in BattleField 4">entire skyscraper being destroyed</a>
in
<a href="http://www.gamecupid.com/games/battlefield-4" title="Features of Battlefield 4">Battlefield 4</a>.
My! things have moved on a fair bit in the world of computer games. Here I was
enjoying the lush scenery of
<a href="http://www.gamecupid.com/games/elder-scrolls-v-skyrim" title="Features of Skyrim">Skyrim</a>
and all of a sudden, an entire world that can be destroyed?</p>
<p>It is a shame that with all of this power, some of the features I have really
enjoyed in games like witty banter with your companions (The Baldurs Gate series
being a very good example) are still so sorely missed. :-(</p>
]]></content:encoded></item><item><title>Pulling At My Heart Strings</title><link>https://icle.es/2013/03/25/pulling-at-my-heart-strings/</link><pubDate>Mon, 25 Mar 2013 16:31:01 +0000</pubDate><guid>https://icle.es/2013/03/25/pulling-at-my-heart-strings/</guid><description>&lt;p>&lt;em>This the story of a Wood Elf, he woke up in a cart, his memories fuzzy and
faded, little knowledge of who he was and what he was doing. He wanders the
frozen wastes of Skyrim doing as he feels. Without a clue as to who he was, he
discovers who he is. Finding himself at Riften, he joined the thieves guild. He
also got Mjoll the Lioness to accompany him on his journeys after finding
Grimsever for her. All seemingly through chance.&lt;/em>&lt;/p>
&lt;p>I found myself in Ivarstead one morning on my way to Whiterun. The walk was
largely dull except for me trying to play hunter. Trying to shoot an arrow into
a moving deer is more difficult than I imagined. Needless to say, little was
achieved more than losing a few arrows.&lt;/p>
&lt;p>Walking past the alchemists hut, I snuck in to see if the herbs and grown, ready
for another pruning ;-) but it was not. It re-ignited my curiosity about the
previous owner of the hut but I got distracted by a deer. After losing another
few arrows, I decided to focus on picking some mountain flowers and other
alchemical ingredients.&lt;/p></description><content:encoded><![CDATA[<p><em>This the story of a Wood Elf, he woke up in a cart, his memories fuzzy and
faded, little knowledge of who he was and what he was doing. He wanders the
frozen wastes of Skyrim doing as he feels. Without a clue as to who he was, he
discovers who he is. Finding himself at Riften, he joined the thieves guild. He
also got Mjoll the Lioness to accompany him on his journeys after finding
Grimsever for her. All seemingly through chance.</em></p>
<p>I found myself in Ivarstead one morning on my way to Whiterun. The walk was
largely dull except for me trying to play hunter. Trying to shoot an arrow into
a moving deer is more difficult than I imagined. Needless to say, little was
achieved more than losing a few arrows.</p>
<p>Walking past the alchemists hut, I snuck in to see if the herbs and grown, ready
for another pruning ;-) but it was not. It re-ignited my curiosity about the
previous owner of the hut but I got distracted by a deer. After losing another
few arrows, I decided to focus on picking some mountain flowers and other
alchemical ingredients.</p>
<p>Things were dull until I got to Riverwood. I suddenly remembered that I was
supposed to deliver a note from Faendal (the scoundrel) to some girl at the bar.
I didn&rsquo;t like how he wanted me to lie about some guy and as luck would have it,
the guy was at the bar too singing something.</p>
<p>He probably deserves to know what was happening so I told him the truth. Now he
wants me to lie and say that Faendal wrote a stupid letter. Now this was not my
intention. Now I am in a situation where it is obvious that this town has not
one but two lying idiots. I mean - what&rsquo;s wrong with these people - can&rsquo;t they
sort this out themselves? I mean even a old fashioned duel would still better
than this lying about each other.</p>
<p>I headed over to the girl resolving to tell her that both the men in her life
are idiots. I started to tell her that Sven (that was the other guys name now
that I remember) wanted me to lie about Faendal and before I would explain that
Faendal was the same, she shouted some abuse about Sven and told me that I
should probably thank Faendal. Oh wait, the women are crazy too. To each their
own I thought and wandered across to Faendals house.</p>
<p>It was a little late in the night and he was fast asleep. I couldn&rsquo;t be bothered
to wait till morning, so i broke into his house. Considering that he&rsquo;s a lying
bastard and he was soundly sleeping, I robbed him of all valuables - which
honestly was really not that much. I then woke him up and told him the good
news - he seemed happy. The things we do for love&hellip; I thought.. For a moment, I
wondered if I had done such silly things in my life&hellip; but only for a moment and
jumped off the balcony and ran across to Whiterun.</p>
<p>Running past the honningbrew meadery, I remember that I had kicked the previous
owner out and put somebody else in place for Maven Black Briar (one of the
things I did for the Thieves Guild). I thought to drop him a visit and see how
he was getting on.</p>
<p>He was happy to see me and told me how fortunate he was to go for being
somebodies lackey to a very wealthy man overnight. He did not even offer me a
free mead. I went upstairs and robbed him of all his valuables and I was just
about to head out when I saw that the bar was full of bottles of Black Briar
Mead. Not only are they delicious, they are also worth a fair bit of money, so I
snuck around and shoved them all into my pockets.</p>
<p>I also tried to pickpocket the now wealthy Mallus but he had nothing on him that
I could find&hellip;</p>
<p>It was pretty late when I got into town and stumbling into the first tavern, it
turns out they don&rsquo;t have rooms. I then made it across to the town square which
had another tavern. This one did have rent out rooms - thank god.</p>
<p>After ditching all of my extra stuff that I could at the merchants, I headed
back out in the direction of Markarth. I did various things on the way including
killing a bandit leader is some cave and speaking to Kematu which was
enlightening and further eroding faith in people.</p>
<p>Finally, I reached Rorikstead, it seemed like a nice quiet town. I even met
Rorik who seemed like such a nice man to have bought a bunch of land for people
to live off.</p>
<p>It was a nice enough place that I decided to spend a day hanging around and
taking in the scenery and the loveliness of it all. There were even kids running
around playing.</p>
<p>As one of them ran past, I stopped her and talked to her but it didn&rsquo;t quite go
the way I hoped. <em>&ldquo;Most days, I do all I can to stay away from my sister and my
father. The beating&rsquo;s the same from either one.&rdquo;</em> she told me. Sure enough her
sister was right behind her and I realised that they were not actually
playing&hellip;</p>
<p>This made me very curious and instead of hanging about soaking in the
atmosphere, I followed people around trying to confirm how true this was and it
saddened me a great deal to find out that it was indeed true. As it turns out
Lemkil, their Father had lost his wife to the birth of the twins. A sad thing no
excuse for what his daughters are going through.</p>
<p>I was overcome with a desire to put their father out of his misery. I did not
even have the patience to wait until it was dark. He was working on a farm, I
went up on a hill and hid behind a rock. I took aim and the first arrow hit the
rock and rolled down the hill. I couldn&rsquo;t help but giggle. I then readied
another arrow, aimed a little higher and let it loose. Time slowed as the arrow
left and found its mark.</p>
<p>Erik screamed bloody murder - pointing out it was in cold blood but I was well
hidden and safe. Erik had told me how much he wanted to travel and be an
adventurer. Hey, two birds with one stone eh? ;-)</p>]]></content:encoded></item><item><title>Getting Started: Emacs &amp;amp; C++ (w/ cmake) (On the fly syntax highlighting)</title><link>https://icle.es/2013/02/24/getting-started-emacs-c-w-cmake/</link><pubDate>Sun, 24 Feb 2013 11:54:03 +0000</pubDate><guid>https://icle.es/2013/02/24/getting-started-emacs-c-w-cmake/</guid><description>&lt;p>I am a recent convert to emacs. My vast majority of development is in Java EE
and I have not found an easy way to get the functionality in eclipse into emacs.
So I still use eclipse for this.&lt;/p>
&lt;p>However, I like to tinker with C++ and I wanted to get some of the CDT
functionality into emacs. In truth, I have used very little CDT so my
expectations from emacs will be set differently. Considering that emacs has been
used for C/C++ development for decades, I am hopeful that it will be more
feature-rich than eclipse or any of the other IDE&amp;rsquo;s like Anjuta, Code::Blocks
etc (both I have tried to use).&lt;/p>
&lt;p>First things first. In the world of Java, I am a massive fan of maven which
makes build management so easy and simple. Having used it now for years, it is
easy to forget how much of a learning curve it had to get started.&lt;/p>
&lt;p>Autotools are a massive pain to use and has a very steep learning curve. I have
used it in the past to set up build environments and it works fine. pkg-config
is pretty awesome and in a lot of ways, maven does pale in comparison. i.e.
instead of having maven pull in dependencies, you just use your systems package
manager like apt-get or yum and it installs the libraries for you.&lt;/p></description><content:encoded><![CDATA[<p>I am a recent convert to emacs. My vast majority of development is in Java EE
and I have not found an easy way to get the functionality in eclipse into emacs.
So I still use eclipse for this.</p>
<p>However, I like to tinker with C++ and I wanted to get some of the CDT
functionality into emacs. In truth, I have used very little CDT so my
expectations from emacs will be set differently. Considering that emacs has been
used for C/C++ development for decades, I am hopeful that it will be more
feature-rich than eclipse or any of the other IDE&rsquo;s like Anjuta, Code::Blocks
etc (both I have tried to use).</p>
<p>First things first. In the world of Java, I am a massive fan of maven which
makes build management so easy and simple. Having used it now for years, it is
easy to forget how much of a learning curve it had to get started.</p>
<p>Autotools are a massive pain to use and has a very steep learning curve. I have
used it in the past to set up build environments and it works fine. pkg-config
is pretty awesome and in a lot of ways, maven does pale in comparison. i.e.
instead of having maven pull in dependencies, you just use your systems package
manager like apt-get or yum and it installs the libraries for you.</p>
<p>Long story short, I am using <a href="http://www.cmake.org/" title="CMake">cmake</a> which has
the added advantage of being a little more cross-platform (i.e. supported in
Windows as well as *nix). If you haven&rsquo;t used CMake before, let me tell you -
it&rsquo;s a heck of a lot easier to get used to than Autotools. Just go through their
<a href="http://www.cmake.org/cmake/help/cmake_tutorial.html" title="CMake Tutorial">tutorial</a>
and you should be off.</p>
<p>The next thing I wanted to sort out was on the fly syntax checking. This makes
life a lot easier and means that you can write and correct syntax errors etc
without having to build manually.</p>
<p><a href="http://www.emacswiki.org/emacs/FlyMake">Flymake</a> is what you want to use for
this. The later versions of emacs comes with Flymake so you won&rsquo;t necessary need
to install it to get started. However, flymake doesn&rsquo;t (unfortunately) just work
magically out of the box and requires a little configuration.</p>
<p>After hunting around for a bit, and finally from the
<a href="http://www.emacswiki.org/emacs/FlyMake">EmacsWiki Flymake</a> page, found a couple
of options <a href="https://github.com/redguardtoo/cpputils-cmake">cpputils-make</a> and
<a href="https://github.com/alamaison/emacs-cmake-project">cmake-project</a>. Cmake-project
seemed simpler and I opted for that. I also tried installing cpputils-make and
didn&rsquo;t have any issues with that either.</p>
<p>There is one thing you need to be aware of though - both these tools expect you
to do out-of-source-builds. This essentially requires you to create a build
folder (called bin for cmake-project and build for cpputils-make) and generate
the Makefile etc. in there.</p>
<p>This is the preferred way with CMake anyway so it&rsquo;ll be better to do build in
that way. It&rsquo;ll also  make it easier to have different builds (Debug/Release
etc.)</p>
<p>The easiest way to install either of these is through marmalade. If you don&rsquo;t
already have it installed - it is so easy - just follow the instructions on
their <a href="http://marmalade-repo.org/">homepage</a>. You can then install by running</p>
<p><code>M-x package-install cmake-project</code></p>
<p>OR</p>
<p><code>M-x package-install cpputils-cmake</code></p>
<p>add the following to your .emacs file for cmake project. Instructions for
cpputils-make can be found on
<a href="https://github.com/redguardtoo/cpputils-cmake">their github page</a></p>
<p>[(require &lsquo;cmake-project)]{style=&ldquo;font-family:Consolas, Monaco,
monospace;font-size:12px;line-height:18px;&rdquo;}</p>
<p>Do a full build on your sources first by going to the bin or build directory and
generating the makefiles by using cmake (cmake .. or cmake ../src depending on
how you set up cmake) and then make.</p>
<p>You can then initialise the mode within emacs for cmake:</p>
<p><code>M-x cmake-project-mode</code></p>
<p>You may have to also enable flymake;</p>
<p><code>M-x flymake-mode</code></p>]]></content:encoded></item><item><title>WARN - Missing artifact descriptor for XXX</title><link>https://icle.es/2013/01/31/warn-missing-artifact-descriptor-for-xxx/</link><pubDate>Thu, 31 Jan 2013 20:38:48 +0000</pubDate><guid>https://icle.es/2013/01/31/warn-missing-artifact-descriptor-for-xxx/</guid><description>&lt;p>Working on an Arquillian test deployment which had some library changes
recently, I ran into the following error.&lt;/p>
```
WARN - Missing artifact descriptor for org.javassist:javassist:jar:3.16.1-GA
```
&lt;p>The particular library was in the pom.xml dependency hierarchy but it was
resolving to an earlier  version. Maven was switched to offline mode during the
tests and I had never needed this version of the library before. This meant
 that the local version of my maven repository did not have jar and maven emits
it slightly unhelpful error. It would be better if it told us that it could not
find the artifact and since its in offline mode, can't go and retrieve it.&lt;/p></description><content:encoded><![CDATA[<p>Working on an Arquillian test deployment which had some library changes
recently, I ran into the following error.</p>
```
WARN - Missing artifact descriptor for org.javassist:javassist:jar:3.16.1-GA
```
<p>The particular library was in the pom.xml dependency hierarchy but it was
resolving to an earlier  version. Maven was switched to offline mode during the
tests and I had never needed this version of the library before. This meant
 that the local version of my maven repository did not have jar and maven emits
it slightly unhelpful error. It would be better if it told us that it could not
find the artifact and since its in offline mode, can't go and retrieve it.</p>
<p>There are two quick hacks. Add the library and version into the pom.xml and do a
build. This will pull the library into your local repository and maven will be
able to find it offline. You could also just take maven in the tests online by
removing the goOffline() method call.</p>
<p>As for the cause of the issue, it stems from the way maven resolves dependencies
from Arquillian in comparison to building. I had updated a library version,
which now depends on the newer version of javassist. However, in considering all
the other things within the pom.xml, maven brings it down to an earlier version
when building.</p>
<p>However, the dependency resolution within the maven run through Arquillian
considers a slightly different set of requirements and resolves to a later
version of the lib which is not available.</p>
]]></content:encoded></item><item><title>The Death Of a Follower</title><link>https://icle.es/2013/01/14/the-death-of-a-follower/</link><pubDate>Mon, 14 Jan 2013 14:13:34 +0000</pubDate><guid>https://icle.es/2013/01/14/the-death-of-a-follower/</guid><description>&lt;p>&lt;em>Back in 2011, starting from the 11th November, i.e. 11.11.11, I had booked a
weeks holiday. I planned to travel a brand new world and so I did - for a whole
week. Skyrim was released at midnight and I started playing. Over the week, I
spend around 75 hours on the game and I had reached a meagre level 25 and got
only about a third of the way through the main quest. Playing through and
completing a number of games recently got me to thinking about Skyrim and the
&lt;a href="http://www.pcgamer.com/2012/08/09/an-illusionist-in-skyrim-part-1/" title="An Illusionist In Skyrim">Illusionist Diaries&lt;/a>
inspired me to start again.&lt;/em>&lt;/p>
&lt;p>I was in front of the daedric lord Clavicus Vile and it was all slowly coming to
me. I heard Barbas barking away in the background and Jordis the Sword Maiden
was her usual self, prancing about.&lt;/p>
&lt;p>I looked for a way to get out and got to a closed gate. Seeing now way to open
the gate, I took the long way back out the cave. No biggie, it was a good way to
re-acquaint myself with the world again.&lt;/p>
&lt;p>Once I got out of the cave, I re-prioritised. I had enough of traipsing around
doing whatever I fancied. It was time to put an end to the civil war and of
course to put a stop to all this dragon malarkey.&lt;/p></description><content:encoded><![CDATA[<p><em>Back in 2011, starting from the 11th November, i.e. 11.11.11, I had booked a
weeks holiday. I planned to travel a brand new world and so I did - for a whole
week. Skyrim was released at midnight and I started playing. Over the week, I
spend around 75 hours on the game and I had reached a meagre level 25 and got
only about a third of the way through the main quest. Playing through and
completing a number of games recently got me to thinking about Skyrim and the
<a href="http://www.pcgamer.com/2012/08/09/an-illusionist-in-skyrim-part-1/" title="An Illusionist In Skyrim">Illusionist Diaries</a>
inspired me to start again.</em></p>
<p>I was in front of the daedric lord Clavicus Vile and it was all slowly coming to
me. I heard Barbas barking away in the background and Jordis the Sword Maiden
was her usual self, prancing about.</p>
<p>I looked for a way to get out and got to a closed gate. Seeing now way to open
the gate, I took the long way back out the cave. No biggie, it was a good way to
re-acquaint myself with the world again.</p>
<p>Once I got out of the cave, I re-prioritised. I had enough of traipsing around
doing whatever I fancied. It was time to put an end to the civil war and of
course to put a stop to all this dragon malarkey.</p>
<p>It was nice to have to followers - and one of them a pet though it did start to
get very annoying when Barbas would just refuse to stop barking. He'd bark at
anything. If I moved, he'd bark, if I stayed still, he'd bark, ooohhh there's
a butterfly, woof woof. but I put up with it. More aggravating was his desire to
always get in my way - in his own playful way of course, but he always like to
come along an lie down in the door way. I could usually jump over him but it did
sometimes cause me a bruised head with the doorway deciding to take up the same
space as my head..</p>
<p>The three of us, the merry trio, did a lot of good things in Skyrim - defeated a
number of dragons, helped a bunch of people do this and that, sold a lot of
stuff and made a lot of money and even got up the throat of the world and
travelled back in time.</p>
<p>I would often head back to my first home in Whiterun and see Lydia hanging about
and while Jordis too complained about being sworn to carry my burdens, she
somehow simply did not irritate me as much as Lydia did and I was happy.</p>
<p>I would often run off without waiting for the two of them and on more than
one occasion, I would lose them somewhere, but after a while, sure enough, they
would find me. Barbas was usually the first (must be his keen olfactory senses)
and Jordis wouldn't be far behind. It usually brought a smile to my face to see
Barbas - he always seemed so happy to see me &quot;Where did you go - we've been
looking everywhere for you&quot;, he seemed to say. I always imagined Jordis
probably had a similar expression, but she was wearing a helmet that covered her
whole face, so all I could see was cold hard steel - oh well.</p>
<p>After the brief jaunt through history, seeing Barbas still following me around,
made my heart sink with sorrow. He was following me around since I promised to
find an axe and re-unite him with his master... I hadn't done that for a very
long time. I was level 25 when he started to follow me around and I was now
level 31.</p>
<p>I decided it was time to find the axe for Barbas and re-unite him with his
master. So I traipsed around a mountain, into a cave, killed a wizard and got
the axe. That was easy. I returned to Clavicus Vile with no regard for his
nonsense, I left Barbas by his masters side. I took a few moments to say goodbye
and was confounded by the door I could not open again. This time though, I was
used to world and saw the little chain on the right of the door, pulled it and
et voila, I was out.</p>
<p>It was once again, just me and Jordis, the sword-maiden. My heart was heavy with
the absence of Barbas but I shrugged it off and carried on. Helped with more the
civil war stuff and got to Fort Snowhawk. I found these to be a little more
difficult than ordinary missions, partly because, as a mage, I had very little
health so it was easy to get injured. More importantly, I always found it
difficult to be able to tell who was who and as a ranged combatant, it was even
more difficult. I usually kill a few stormcloak  troopers by accident but I
don't pay it much mind. After taking over the fort, I did various other things
and it was time to take over the next fort. Once I got to that fort, I noticed
that Jordis was nowhere to be seen.</p>
<p>I looked around, teleported around, and I still couldn't find her. It dawned on
me that she might have been killed - and that too a while back - so there may be
no way to fix this. I went to Proudspire Manor. Waited a while, and no - she did
not come home and I was sure that she must have been killed. I had to be sure.</p>
<p>I wanted to know so badly that I resorted to otherwordly magic (I uttered the
blasphemous phrase ~player.moveto 000A2C95) and I was in the strangest room I
had ever been in. There were four hallways meeting and each of them led to what
seemed like eternity. On the ground were a bunch of dead bodies and among them
lay Jordis. I was speechless. She was dead! We had spent so much time together
and without even a goodbye, she was dead. She was dead and all her armour was
taken off, and I saw her face again. Calm and Serene.</p>
<p>I thought about resurrecting her with otherworldly magic but decided that would
not be what she would want. I had to do some otherworldly jiggery pokey (load
the previous save) to get out of that place since running out the door would
just drop me back in the room.</p>
<p>Just a little while ago, I had two companions and within a matter of hours, I
was all alone... I knew that if she takes enough damage, she would yield and
 enemies would not attack her. It occurred to me that the last time I saw her
was at Fort Snowhawk and then the realisation dawned on me. It was entirely
possible that she was killed not by enemies, or wildebeasts - it was entirely
possible that it was me who killed her.</p>
<p>I had recently killed a Boethiah cultist and was considering going to her
shrine, where I suspected I would have to kill my companion so had avoided that
mission. I had also gotten out of killing Barbas. Was it possible that Boethiah
and Clavicus Vile were working their twisted magic to get me to kill my own
follower? In any case, it was done! She was dead! and I was the most likely
suspect for her murder.</p>
<p>When we finally crushed the Empire and liberated Skyrim form the Thalmor, when
Ulfric Stormcloak was praising me, I thought of Jordis and Barbas. Fallen
Comrades! Friends!</p>]]></content:encoded></item><item><title>Getting started on seam-security, picketlink IDM and JPAIdentityStore</title><link>https://icle.es/2012/10/17/getting-started-on-seam-security-picketlink-idm-and-jpaidentitystore/</link><pubDate>Wed, 17 Oct 2012 10:40:28 +0000</pubDate><guid>https://icle.es/2012/10/17/getting-started-on-seam-security-picketlink-idm-and-jpaidentitystore/</guid><description>&lt;p>I love how JBoss 7(.1) has everything working out of the box - not much fiddling
with jars or suchlike and with Arquillian, everything really was a treat to get
started on a new project. This was until I had to sort out security with
seam-security.&lt;/p>
&lt;p>To be fair, the main issue was just poor documentation. It took me a day to sort
out what should essentially have taken an hour(or two)&lt;/p>
&lt;p>The documentation you get to from
&lt;a href="http://www.seamframework.org/Seam3/SecurityModule">http://www.seamframework.org/Seam3/SecurityModule&lt;/a> seems to be out of date. The
fact that the page referes to version 3.0.0.Alpha1 and Alpha2 should have tipped
me off but the url for the doc suggested it was the latest.&lt;/p>
&lt;p>The more up to date documentation I found was
at http://docs.jboss.org/seam/3/3.1.0.Final/reference/en-US/html/pt04.html&lt;/p>
&lt;p>I followed
&lt;a href="http://docs.jboss.org/seam/3/3.1.0.Final/reference/en-US/html/security-identitymanagement.html" title="Identity Management">chapter 33&lt;/a>
on there and I won't repeat it here for the sake of brevity.&lt;/p>
&lt;p>What follows are the additional steps I had to take to get it to work.&lt;/p></description><content:encoded><![CDATA[<p>I love how JBoss 7(.1) has everything working out of the box - not much fiddling
with jars or suchlike and with Arquillian, everything really was a treat to get
started on a new project. This was until I had to sort out security with
seam-security.</p>
<p>To be fair, the main issue was just poor documentation. It took me a day to sort
out what should essentially have taken an hour(or two)</p>
<p>The documentation you get to from
<a href="http://www.seamframework.org/Seam3/SecurityModule">http://www.seamframework.org/Seam3/SecurityModule</a> seems to be out of date. The
fact that the page referes to version 3.0.0.Alpha1 and Alpha2 should have tipped
me off but the url for the doc suggested it was the latest.</p>
<p>The more up to date documentation I found was
at http://docs.jboss.org/seam/3/3.1.0.Final/reference/en-US/html/pt04.html</p>
<p>I followed
<a href="http://docs.jboss.org/seam/3/3.1.0.Final/reference/en-US/html/security-identitymanagement.html" title="Identity Management">chapter 33</a>
on there and I won't repeat it here for the sake of brevity.</p>
<p>What follows are the additional steps I had to take to get it to work.</p>
<p>I ran into a javax.enterprise.inject.CreationException, the relevant part of the
stack trace being:</p>
```
    Caused by: java.lang.IllegalArgumentException: targetClass parameter may not be null
        at org.jboss.solder.properties.query.PropertyQuery.(PropertyQuery.java:54) [solder-impl-3.1.0.Final.jar:3.1.0.Final]
        at org.jboss.solder.properties.query.PropertyQueries.createQuery(PropertyQueries.java:39) [solder-impl-3.1.0.Final.jar:3.1.0.Final]
        at org.jboss.seam.security.management.picketlink.JpaIdentityStore.findNamedProperty(JpaIdentityStore.java:441) [seam-security-3.1.0.Final.jar:3.1.0.Final]
        at org.jboss.seam.security.management.picketlink.JpaIdentityStore.configureRoleTypeName(JpaIdentityStore.java:877) [seam-security-3.1.0.Final.jar:3.1.0.Final]
        at org.jboss.seam.security.management.picketlink.JpaIdentityStore.bootstrap(JpaIdentityStore.java:328) [seam-security-3.1.0.Final.jar:3.1.0.Final]
        at org.picketlink.idm.impl.configuration.IdentityConfigurationImpl.createRealmMap(IdentityConfigurationImpl.java:192) [picketlink-idm-core-1.5.0.Alpha02.jar:1.5.0.Alpha02]
        at org.picketlink.idm.impl.configuration.IdentityConfigurationImpl.buildIdentitySessionFactory(IdentityConfigurationImpl.java:147) [picketlink-idm-core-1.5.0.Alpha02.jar:1.5.0.Alpha02]
        ... 109 more
```
<p>To resolve this,  I had to add in the @IdentityEntity Annotation to the
IdentityObjectType class</p>
```java
    @Entity
    @IdentityEntity(EntityType.IDENTITY_ROLE_NAME)
    public class IdentityObjectType {
    ...
```
<p>The next exception was org.picketlink.idm.common.exception.IdentityException:
Error creating identity object. The relevant part of the strack trace being:</p>
```
    Caused by: java.lang.NullPointerException
        at org.jboss.seam.security.management.picketlink.JpaIdentityStore.lookupIdentityType(JpaIdentityStore.java:966) [seam-security-3.1.0.Final.jar:3.1.0.Final]
        at org.jboss.seam.security.management.picketlink.JpaIdentityStore.createIdentityObject(JpaIdentityStore.java:999) [seam-security-3.1.0.Final.jar:3.1.0.Final]
        ... 87 more
```
<p>It turned out that the entitymanager was not being picked up and it was null.
This part was probably in the documentation earlier with regards to configuring
seam but I had skipped directly to the security section so missed it. We need to
define the persistence unit with the beans.xml. I have included my full file
below.</p>
```xml
    <?xml version="1.0"?>
    <beans xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:em="urn:java:javax.persistence"
        xmlns:s="urn:java:ee"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://jboss.org/schema/cdi/beans_1_0.xsd">

        <interceptors>
            <class>org.jboss.seam.security.SecurityInterceptor</class>
        </interceptors>

        <em:EntityManager>
            <s:Produces />
            <em:PersistenceContext unitName="invision-users" />
        </em:EntityManager>
    </beans>
```
<p>This brought us further forward still. The next exception was:</p>
```
    javax.persistence.NoResultException: No entity found for query
        at org.hibernate.ejb.QueryImpl.getSingleResult(QueryImpl.java:286) [hibernate-entitymanager-4.0.1.Final.jar:4.0.1.Final]
        at org.hibernate.ejb.criteria.CriteriaQueryCompiler$3.getSingleResult(CriteriaQueryCompiler.java:264) [hibernate-entitymanager-4.0.1.Final.jar:4.0.1.Final]
        at org.jboss.seam.security.management.picketlink.JpaIdentityStore.lookupCredentialTypeEntity(JpaIdentityStore.java:1112) [seam-security-3.1.0.Final.jar:3.1.0.Final]
        at org.jboss.seam.security.management.picketlink.JpaIdentityStore.updateCredential(JpaIdentityStore.java:1633) [seam-security-3.1.0.Final.jar:3.1.0.Final]
        at org.picketlink.idm.impl.repository.WrapperIdentityStoreRepository.updateCredential(WrapperIdentityStoreRepository.java:310) [picketlink-idm-core-1.5.0.Alpha02.jar:1.5.0.Alpha02]
        at org.picketlink.idm.impl.api.session.managers.AttributesManagerImpl.updatePassword(AttributesManagerImpl.java:563) [picketlink-idm-core-1.5.0.Alpha02.jar:1.5.0.Alpha02]
```
<p>This was related to missing data in the database. It needed a credential type. I
created one for password.</p>
```sql
    INSERT INTO CredentialType(id, name) VALUES (1, 'password');
```
<p>This brought us forward on to the next exception:
org.picketlink.idm.common.exception.IdentityException: Exception creating
relationship</p>
<p>with the relevant part of</p>
```
    Caused by: javax.persistence.NoResultException: No entity found for query
        at org.hibernate.ejb.QueryImpl.getSingleResult(QueryImpl.java:286) [hibernate-entitymanager-4.0.1.Final.jar:4.0.1.Final]
        at org.hibernate.ejb.criteria.CriteriaQueryCompiler$3.getSingleResult(CriteriaQueryCompiler.java:264) [hibernate-entitymanager-4.0.1.Final.jar:4.0.1.Final]
        at org.jboss.seam.security.management.picketlink.JpaIdentityStore.lookupRelationshipType(JpaIdentityStore.java:1127) [seam-security-3.1.0.Final.jar:3.1.0.Final]
        at org.jboss.seam.security.management.picketlink.JpaIdentityStore.createRelationship(JpaIdentityStore.java:1066) [seam-security-3.1.0.Final.jar:3.1.0.Final]
        ... 86 more
```
<p>This was solved by adding in a relationship type</p>
```sql
    INSERT INTO RelationshipType(id, name) VALUES (1, 'JBOSS_IDENTITY_MEMBERSHIP');
```
<p>Both the sql statements were put into import.sql and hibernate is configured to
create tables. My test case is as follows. It was taken
from <a href="https://github.com/seam/seam-example-confbuzz/blob/develop/src/test/java/seam/example/confbuzz/test/integration/LoginIntegrationTest.java">https://github.com/seam/seam-example-confbuzz/blob/develop/src/test/java/seam/example/confbuzz/test/integration/LoginIntegrationTest.java</a> and
modified.</p>
```java
    @RunWith(Arquillian.class)
    public class LoginIntegrationTest {

        @Inject
        private IdentitySession identitySession;

        @Inject
        private Identity identity;

        @Inject
        @DefaultTransaction
        SeamTransaction tx;

        @Deployment(name = "authentication")
        public static Archive createLoginDeployment() {
            // This is the simplest way to test the full archive as you will be
            // deploying it
            final MavenDependencyResolver resolver =
                    DependencyResolvers.use(MavenDependencyResolver.class)
                        .loadMetadataFromPom("pom.xml")
                        .goOffline();

            Archive archive = ShrinkWrap
                    .create(WebArchive.class)
                    .addPackages(true, "uk.co.kraya.test-seam.auth")
                    .addAsResource("META-INF/test-persistence.xml", "META-INF/persistence.xml")
                    .addAsResource("META-INF/beans.xml", "META-INF/beans.xml")
                    .addAsResource("test-import.sql", "import.sql")
                    .addAsLibraries(resolver.artifact("org.jboss.seam.security:seam-security").resolveAsFiles());

            System.out.println(archive.toString(true));

            return archive;

        }

        @Before
        public void setupTestUser() throws IdentityException, SystemException,
                NotSupportedException, RollbackException,
                HeuristicRollbackException, HeuristicMixedException {

            if (!tx.isActive())
                tx.begin();

            final PersistenceManager pm = identitySession.getPersistenceManager();
            final AttributesManager am = identitySession.getAttributesManager();
            final RelationshipManager rm = identitySession.getRelationshipManager();

            // Setup the group we want our user to belong to
            final Group memberGroup = pm.createGroup("member2", "USER2");
            final User user = pm.createUser("test");

            am.updatePassword(user, "password");

            rm.associateUser(memberGroup, user);

            tx.commit();
        }

        @Test
        public void assertUserCanAuthenticate(Credentials credentials) {
            credentials.setUsername("test");
            credentials.setCredential(new PasswordCredential("password"));
            assertEquals(identity.login(), Identity.RESPONSE_LOGIN_SUCCESS);
        }
```
<p>Do comment and let me know if it helped :-D</p>]]></content:encoded></item><item><title>Slow it Down</title><link>https://icle.es/2012/06/12/slow-it-down/</link><pubDate>Tue, 12 Jun 2012 16:13:01 +0000</pubDate><guid>https://icle.es/2012/06/12/slow-it-down/</guid><description>&lt;p>So much of my life was spent running,&lt;br>
away from or to something, I did not know.&lt;/p>
&lt;p>Outside of life itself was existence,&lt;br>
an adrenaline fueled ride into oblivion,&lt;br>
a life envied, hated, admired, reviled!&lt;/p>
&lt;p>A path of self destruction,&lt;br>
collateral damage and catharsis.&lt;/p>
&lt;p>Destroying everything and anything,&lt;br>
that which was, is, could be, will not be..&lt;/p>
&lt;p>Close to few, far from few…&lt;/p>
&lt;p>Living fast, dying young&amp;hellip;&lt;br>
Anywhere but here,&lt;br>
Anywhere but now…&lt;/p></description><content:encoded><![CDATA[<p>So much of my life was spent running,<br>
away from or to something, I did not know.</p>
<p>Outside of life itself was existence,<br>
an adrenaline fueled ride into oblivion,<br>
a life envied, hated, admired, reviled!</p>
<p>A path of self destruction,<br>
collateral damage and catharsis.</p>
<p>Destroying everything and anything,<br>
that which was, is, could be, will not be..</p>
<p>Close to few, far from few…</p>
<p>Living fast, dying young&hellip;<br>
Anywhere but here,<br>
Anywhere but now…</p>
<p>Flying fast… into the mountains…<br>
and captured, not just attracted or found..<br>
but captured.</p>
<p>Slow as a snail now. Days go by too fast&hellip;<br>
Slow it down - anything to slow it down&hellip;</p>
]]></content:encoded></item><item><title>Hold on tight</title><link>https://icle.es/2012/04/20/hold-on-tight/</link><pubDate>Fri, 20 Apr 2012 12:51:30 +0000</pubDate><guid>https://icle.es/2012/04/20/hold-on-tight/</guid><description>&lt;p>Hold on tight,&lt;br>
To everything,&lt;br>
Just hold on tight,&lt;br>
for you don't know when,&lt;br>
when it might all go,&lt;br>
when it might all disappear.&lt;/p>
&lt;p>Hold on tight,&lt;br>
for one day,&lt;br>
it will go,&lt;br>
it will disappear.&lt;/p>
&lt;p>But not today,&lt;br>
Not right now,&lt;br>
not if I can help it.&lt;/p>
&lt;p>For tomorrow,&lt;br>
Realise that it is gone,&lt;br>
and not for a moment or a day,&lt;br>
but for ever.&lt;/p>
&lt;p>And I might resent that which,&lt;br>
that, which,&lt;br>
which slipped away,&lt;br>
while I held on to these hot coals.&lt;/p></description><content:encoded><![CDATA[<p>Hold on tight,<br>
To everything,<br>
Just hold on tight,<br>
for you don't know when,<br>
when it might all go,<br>
when it might all disappear.</p>
<p>Hold on tight,<br>
for one day,<br>
it will go,<br>
it will disappear.</p>
<p>But not today,<br>
Not right now,<br>
not if I can help it.</p>
<p>For tomorrow,<br>
Realise that it is gone,<br>
and not for a moment or a day,<br>
but for ever.</p>
<p>And I might resent that which,<br>
that, which,<br>
which slipped away,<br>
while I held on to these hot coals.</p>
<p>Scars are but I have left,<br>
but not from that,<br>
not that which I desired,<br>
but that I didn't,<br>
that which I couldn't,<br>
I just...</p>
<p>Told once not to,<br>
Don't give life your all,<br>
Not if happiness is seeked,<br>
Not within the confines,<br>
Not happiness in the life,<br>
Not the life you know,<br>
Not the life you understand.</p>
<p>So I held on,<br>
To the net,<br>
The safety nets,<br>
Not just one,<br>
But all I could.</p>
<p>And yet, ripped,<br>
Not just away,<br>
Not just apart,<br>
All the nets,<br>
If not in life,<br>
then in death.</p>
<p>To live a life,<br>
As alone as it will be,<br>
For nobody,<br>
To throw away,<br>
To discard their nets,<br>
Their safety nets,<br>
In life, and walk.</p>
<p>Along the edge,<br>
Where with a hello,<br>
and  a how do,<br>
life meets death.</p>
<p>To die is to live,<br>
To live is life,<br>
But if happiness you seek,<br>
Happiness confined,<br>
Happiness from another,<br>
Don't take the leap.</p>
<p>Just hold on tight...</p>
]]></content:encoded></item><item><title>Restricting Linux Logins to Specified Group</title><link>https://icle.es/2012/03/21/restricting-linux-logins-to-specified-group/</link><pubDate>Wed, 21 Mar 2012 11:36:45 +0000</pubDate><guid>https://icle.es/2012/03/21/restricting-linux-logins-to-specified-group/</guid><description>&lt;p>If you have linux boxes that authenticate over ldap but want logins for specific
boxes to be restricted to a particular group, there is a simple way to achieve
this.&lt;/p>
&lt;p>Firstly, create a new file called &lt;code>/etc/group.login.allow&lt;/code> (it can be called
anything - you just need to update the line below to reflect the name)&lt;/p>
&lt;p>In this file, pop in all the groups that should be able to login&lt;/p>
```
admin
group1
group2
```
&lt;p>Edit &lt;code>/etc/pam.d/common-auth&lt;/code> (in ubuntu), it might be
called &lt;code>/etc/pam.d/system-auth&lt;/code> or something else very similar. At the top of
the file (or at least above other entries, add the following line:&lt;/p></description><content:encoded><![CDATA[<p>If you have linux boxes that authenticate over ldap but want logins for specific
boxes to be restricted to a particular group, there is a simple way to achieve
this.</p>
<p>Firstly, create a new file called <code>/etc/group.login.allow</code> (it can be called
anything - you just need to update the line below to reflect the name)</p>
<p>In this file, pop in all the groups that should be able to login</p>
```
admin
group1
group2
```
<p>Edit <code>/etc/pam.d/common-auth</code> (in ubuntu), it might be
called <code>/etc/pam.d/system-auth</code> or something else very similar. At the top of
the file (or at least above other entries, add the following line:</p>
```
auth required pam_listfile.so onerr=fail item=group sense=allow file=/etc/group.login.allow
```
<p>For the record, found this little tidbit
<a href="https://www.centos.org/modules/newbb/viewtopic.php?topic_id=25940" title="Allow Only Specific LDAP Group Access (CentOS Forums)">over at the centos forums</a>\</p>
]]></content:encoded></item><item><title>Li(f)e</title><link>https://icle.es/2012/03/11/life/</link><pubDate>Sun, 11 Mar 2012 14:41:00 +0000</pubDate><guid>https://icle.es/2012/03/11/life/</guid><description>&lt;p>Tis true. Life is a lie. Some people call it maya (yeah - us - as in Indians)
and it is ever so true but it is not really as one thinks.&lt;/p>
&lt;p>Everything is as is. Nobody, however, can see. A rose flowered in the middle of
a field and everyone sees something different. Amongst these people, there is
someone who sees me in that rose, and someone who sees you. Somebody sees a rose
they saw when they were a child, perhaps one their lover gave them yesterday,
last week or in another life.&lt;/p>
&lt;p>The smell reminds someone of a perfume, of a girl, of an evening, of a dinner
date, The colour reminds someone of blood, of pain, of joy. And yet others see a
rose.&lt;/p>
&lt;p>Nobody, however, can see. Not this rose, with its own uniqueness, with its own
joy and bliss and love. It is expressing itself - unlimited. For that which it
wishes to express is limited and can be expressed in its mere existence. The joy
and greater sadness of consciousness, the way we use it anyway, is that
existence is not enough to express ourselves - not safely (for society) anyway.&lt;/p>
&lt;p>As I trampled on this rose, I realise that I have no regret, no remorse. The
rose expressed itself. It was happy, in bliss, for to express, to be expressed
is enough, more than enough - in fact, it seems to be what all of existence
yearns for.&lt;/p></description><content:encoded><![CDATA[<p>Tis true. Life is a lie. Some people call it maya (yeah - us - as in Indians)
and it is ever so true but it is not really as one thinks.</p>
<p>Everything is as is. Nobody, however, can see. A rose flowered in the middle of
a field and everyone sees something different. Amongst these people, there is
someone who sees me in that rose, and someone who sees you. Somebody sees a rose
they saw when they were a child, perhaps one their lover gave them yesterday,
last week or in another life.</p>
<p>The smell reminds someone of a perfume, of a girl, of an evening, of a dinner
date, The colour reminds someone of blood, of pain, of joy. And yet others see a
rose.</p>
<p>Nobody, however, can see. Not this rose, with its own uniqueness, with its own
joy and bliss and love. It is expressing itself - unlimited. For that which it
wishes to express is limited and can be expressed in its mere existence. The joy
and greater sadness of consciousness, the way we use it anyway, is that
existence is not enough to express ourselves - not safely (for society) anyway.</p>
<p>As I trampled on this rose, I realise that I have no regret, no remorse. The
rose expressed itself. It was happy, in bliss, for to express, to be expressed
is enough, more than enough - in fact, it seems to be what all of existence
yearns for.</p>
<p>Oh you might say - what about the people who might enjoy the rose. Who enjoys
the rose? People, at best enjoy the memories triggered by the beauty of this
creature. They cannot even see it, then how can they appreciate it?</p>
<p>Memories are dead things - the most dead thing in the world - why? what is the
point in keeping something so that someone may further flog the proverbial dead
horse. They cannot let the memories go. They think the memories are them, define
them, mould them. And it does - it creates a mould around them. Stops them,
restricts them - keeps them within the niche, the rut they have created for
themselves.</p>
<p>I do not understand how someone can live, survive as a mere shadow of how they
are supposed to be. How someone can do that so easily is so far beyond me.
Everyone is drenched in misery - dripping in the rain, with barely a rainbow.
Yearning for, waiting for that rainbow and once the rainbow is there, so afraid
of the sunlight which will wipe out the rainbow.</p>
<p>And of course if someone gets as far as the sunlight, they will then be scared
of the rain next.</p>
<p>It would seem that misery and fear is the greatest joy for people. As an
objective viewer, which I am not, I suspect, this may be an easy observation to
make.</p>
<p>You want to be happy - what&rsquo;s stopping you?</p>
<p>Really? What is stopping you?</p>
<p>If the entire world expressed itself fully and fully, it would disintegrate any
semblance of society and anarchy would ensue. The truth is that people are sick.
Once all the sickness is expressed, and out, in the past, as anything expressed
instantly is. Let go of it, move on and you have utopia.</p>
<p>There is no need for technology, no need for power, no need for structure.
Humans as a group, free group, free of the repressed are beautiful creatures
like the rose and death is no longer feared by one who has lived.</p>
<p>Death is not a fear for one who is living. We are all so dead - just
mechanically following the procedure and process book of life - one step at a
time and hoping that the book didn&rsquo;t lie when it said that it would eventually
bring us all to joy - secretly (perhaps unbeknownst even to themselves) more
importantly, freedom. Not freedom <em>to</em> do something but freedom <em>from</em>!
Everything! So that we have a chance, a time, a space to just be ourselves.</p>
<p>I say - why wait? Be YOU. Screw the world!</p>
<p>There is only one rule. Never stop. That which you think is you is a lie. It
will pass. You will find that the things you think you want to do are actually
not the things you actually want to do. They just seem like good ideas from
where you are. It will pass - you will go through it - just pay attention to see
if you still want that. It will change - I promise. Everything does. It will
pass - everything does.</p>
<p>Pay attention. What do you want? What&rsquo;s stopping you? I am not talking about
silly little things. I am talking about expressing yourself. Things that don&rsquo;t
rely on the meagre contribution another human being, or another creature could
make into your life. Why should anyone contribute to your life? How much are you
contributing to your own? and I don&rsquo;t mean surviving. I mean nearly
8,000,000,000 - that&rsquo;s seven BILLION people manage to survive.</p>
<p>Do something different - Be You!</p>]]></content:encoded></item><item><title>Mercy</title><link>https://icle.es/2012/01/12/mercy/</link><pubDate>Thu, 12 Jan 2012 21:18:34 +0000</pubDate><guid>https://icle.es/2012/01/12/mercy/</guid><description>&lt;p>It had been a very long night and we had been travelling for a while. It was a
long and arduous journey. We were all tired. We set up camp at dawn. I could see
that the other creatures of the night, fellow creatures of the dark were
quietening down and the creatures of light were starting to chirp and make their
various sounds as if music.&lt;/p>
&lt;p>We picked a quiet spot to make camp. The strange creature like man they called
Acacius led the group, tending to our severely injured leader, Ta&amp;rsquo;oma with such
care and affection, with such love that it was difficult to believe that he was
not a creature of light.&lt;/p>
&lt;p>As the moon set, the sun rose and its golden rays would soon blind us. We
crawled into the most comfortable spaces that we could find while Acacius stood
watch. Here was a creature who did not know fatigure, nor did his power or
energy wane during darkness of light, a creature akin to a god but I know
better - there is no such thing as gods or demons. Except of course within
ourselves.&lt;/p></description><content:encoded><![CDATA[<p>It had been a very long night and we had been travelling for a while. It was a
long and arduous journey. We were all tired. We set up camp at dawn. I could see
that the other creatures of the night, fellow creatures of the dark were
quietening down and the creatures of light were starting to chirp and make their
various sounds as if music.</p>
<p>We picked a quiet spot to make camp. The strange creature like man they called
Acacius led the group, tending to our severely injured leader, Ta&rsquo;oma with such
care and affection, with such love that it was difficult to believe that he was
not a creature of light.</p>
<p>As the moon set, the sun rose and its golden rays would soon blind us. We
crawled into the most comfortable spaces that we could find while Acacius stood
watch. Here was a creature who did not know fatigure, nor did his power or
energy wane during darkness of light, a creature akin to a god but I know
better - there is no such thing as gods or demons. Except of course within
ourselves.</p>
<p>As my eyelids started to shut weighed heavily by the burden of the night, I saw
Acacius sit in the middle of the camp, in the lotus posture, eyes closed. I had
heard that sight was not necessary in the lives of someone like Acacius or
Ta&rsquo;ome who could simply sense the energy of the universe.</p>
<p>I know not for how long I slept but it felt like mere minutes when I was woken
by the screeching of blades. I reached for my sword and leapt to my feet and I
saw that several others had done the same. I kicked awake the few that were
asleep near me as I witnessed an awe striking image.</p>
<p>Acacius was on his own, hundreds of feet away from the camp, both his hands
holding a sword over his right shoulder about to plunge it into the neck of the
brightest most magnificent creature I had seen. It was an advance scout - a
pegasus, as white as snow and with wings of an angel. As the sword plunged into
its neck, it shrieked and the bright brilliant white had the bright red flowing,
tainting, dripping, screaming, deafening. The hands that I had held up to
prevent myself from being blinded were now covering my ears and my eyes were
closed firmly shut.</p>
<p>As I opened them again, the pegasus was on the ground, and Acacius was knelt in
front the animal, wiping the blood off on his knee. What kind of a creature was
this, that would kill a creature of the light, and yet, pay his respects to it.</p>
<p>As I made my way towards him, he stood up and started walking towards us. I
couldn&rsquo;t help but keep my eyes the magnificence of the creature, now fallen and
motionless.</p>
<p>Acacius paid no attention to me as he walked past. I knelt down to touch the
slain creature and it was the softest fur that I had ever felt. The blood had
dripped along its neck and it was still flowing with gusto. There was now a
growing pool of blood on the ground, seeping into its pure soft white fur.</p>
<p>I heard Acacius talking to Abaddon, the one who had taken charge when Ta&rsquo;ome was
injured. &ldquo;They have been alerted, we have only a few minutes at the most before
they get here. We must prepare to defend ourselves. &quot; I guess neither of them
had to explain our disadvantage that we are weaker in the light and that they
are stronger.</p>
<p>Abaddon shouted orders and I headed back to join the ranks. Once we dispersed to
take our positions, I heard Acacius, whispered to Abaddon, &ldquo;I cannot partake in
this battle.&rdquo; I could see Abaddon thinking about questioning his motives, then
refrained.</p>
<p>As everyone moved to form a circle around the camp, Acacius took his place in
the middle, he sat down again the lotus posture and closed his eyes, his sword
dug deep into the earth right in front of him.</p>
<p>He just killed one of their soldiers, a scout, and yet he refuses to kill more.
If he helped us, we could defeat them easily and make our way to the temple. It
was at most another 12 hours trek. I truly struggled to understand his inability
to join us in this battle which we would like lose without his help. Ta&rsquo;ome
would be killed or captured. Why does he care for his so yet refuse to assist us
to protecting him?</p>
<p>I could not question our orders. These people, creatures, live in ways that I
cannot understand, and I left it at that.</p>
<p>Just in that moment, I heard the screech of a winged creature, it was again, as
white as the snow but it was small, not larger than a small monkey, but it
wielded a sword and moved quicker than my eyes could follow.</p>
<p>I saw as it was struck down by a sword. There was another and yet one more. Then
it was quiet but only for a few seconds before what seemed like a swarm of them
came at us. It was a strange feeling as the brushed past me and flew around me
and my comrades, the softness of their fur in such stark contrast to the
sharpness of their blades.</p>
<p>We fought and we found hard and the vast majority of them lie dead at our feet
and just as an iota of pride started to rise up within my soul, I saw it; This
was again, just an advance guard, the next wave was snow leopards of forms and
other creatures that I could not describe, all white. I guessed that they would
all be soft to touch yet their growls and screams would describe a contradiction
of the most magnificent magnitude.</p>
<p>As we tried to beat back the first wave of what seemed like flying monkeys, they
retreated to join their brethren and it seemed like I just blinked before they
were just a few dozen feet away from me. At this point, I knew that death would
soon be upon me. I closed my eyes for a moment in prayer and cursed Acacius for
not joining us, for not protecting us.</p>
<p>As I opened my eyes, The creatures screeched and charged towards us. I dug my
feet into the ground and in an instant they all hit a barrier that neither I nor
they could see and they fell to the ground. One by one they stood back up and
reached out into the air to touch a glow in the air. It stopped them.</p>
<p>I looked behind and Acacius was on one knee, both his hands on the sword. I
heard a horn and the creatures started to retreat, slowly at first and started
to speed up. I saw the glow in the air now and it was shrinking. I could not
understand why the creatures were running since the glow was getting closer to
us. It quickly covered enough ground to be a feet or two away from us and we
moved backwards into a tighter and tighter circle.</p>
<p>Once we were all as close together as we could be around Acacius, the glow
around us stopped for a few moments, moments that felt like hours and it
exploded. Racing across the land and as it touched the bright, magnificent
creatures, they all fell to the ground. Some of them groaned, some did not.</p>
<p>The ones that were furthest away fell to the ground,  and I could do nothing but
watch in awe. A moment later, Acacius stood, took a cigarette, lit it up,
reached for his hipflask and took a drink out of it. He allowed himself to enjoy
it thoroughly for a few moments.</p>
<p>&ldquo;They will all be out for a few hours; it might be a good idea to not be here
when they wake up&rdquo;</p>]]></content:encoded></item><item><title>Automating Code Analysis with Hudson [1115]</title><link>https://icle.es/2011/12/28/automating-code-analysis-with-hudson-1115/</link><pubDate>Wed, 28 Dec 2011 15:34:52 +0000</pubDate><guid>https://icle.es/2011/12/28/automating-code-analysis-with-hudson-1115/</guid><description>&lt;p>As part of
&lt;a href="http://drone-ah.com/2011/12/28/hudson-jenkins-and-continuous-integration-1114/" title="Hudson / Jenkins and Continuous Integration [1114]">setting up continuous integration and automated builds and source analysis&lt;/a>,
the next step is to integrate in the source analysis parts.&lt;/p>
&lt;p>To this end, I installed the following plugins:&lt;/p>
&lt;p>Task Scanner&lt;br>
FindBugs&lt;br>
CheckStyle&lt;br>
PMD&lt;/p>
&lt;p>After restarting Hudson, there are a few additional configuration bits to
complete.&lt;/p>
&lt;p>I added an additional build step and set the goal as&lt;/p>
```
checkstyle:checkstyle findbugs:findbugs pmd:pmd
```
&lt;p>I then enabled the four plugins, saved, ran a build and et voila... Just like
magic&lt;/p></description><content:encoded><![CDATA[<p>As part of
<a href="http://drone-ah.com/2011/12/28/hudson-jenkins-and-continuous-integration-1114/" title="Hudson / Jenkins and Continuous Integration [1114]">setting up continuous integration and automated builds and source analysis</a>,
the next step is to integrate in the source analysis parts.</p>
<p>To this end, I installed the following plugins:</p>
<p>Task Scanner<br>
FindBugs<br>
CheckStyle<br>
PMD</p>
<p>After restarting Hudson, there are a few additional configuration bits to
complete.</p>
<p>I added an additional build step and set the goal as</p>
```
checkstyle:checkstyle findbugs:findbugs pmd:pmd
```
<p>I then enabled the four plugins, saved, ran a build and et voila... Just like
magic</p>
]]></content:encoded></item><item><title>Hudson / Jenkins and Continuous Integration [1114]</title><link>https://icle.es/2011/12/28/hudson-jenkins-and-continuous-integration-1114/</link><pubDate>Wed, 28 Dec 2011 14:51:31 +0000</pubDate><guid>https://icle.es/2011/12/28/hudson-jenkins-and-continuous-integration-1114/</guid><description>&lt;p>Fair Warning: This is more notes for me to remember and document how to do these
things rather than particularly detailed instructions. Therefore, it might be
missing sections and will assume a reasonable knowledge of hudson/jenkins and
not to mention the benefits of continuous integration and builds.&lt;/p>
&lt;p>Installing hudson / jenkins is easy enough. I deployed as part of a pre-existing
tomcat6 installation so was as simple as popping the war file into the webapps
folder. Tomcat automatically started it up without issues.&lt;/p></description><content:encoded><![CDATA[<p>Fair Warning: This is more notes for me to remember and document how to do these
things rather than particularly detailed instructions. Therefore, it might be
missing sections and will assume a reasonable knowledge of hudson/jenkins and
not to mention the benefits of continuous integration and builds.</p>
<p>Installing hudson / jenkins is easy enough. I deployed as part of a pre-existing
tomcat6 installation so was as simple as popping the war file into the webapps
folder. Tomcat automatically started it up without issues.</p>
<p>I chose to have hudson use /home/hudson as its home directory. Since I am
running an ubuntu system, I added a line into /etc/defaults/tomcat6. There are
various other ways of doing this but it was a quick fix for me.</p>
<p>You of course need to make sure the directory exists. I also popped in a .m2
folder from my home directory to save it from downloading all the various jar
files and included a settings.xml file with appropriate configurations.</p>
<p>Hudson 2.2 uses maven 3 but I use maven 3 locally as well even though the
projects pom files were built for maven 2. There doesn&rsquo;t seem to be any issues
with this setup.</p>
<p>First step is to create a new job from the home page. This asks for which type a
job you want to create. If you use maven and a standard source control, it is as
simple as choosing the first option: Build a free-style software project.</p>
<p>Give it a name and you are brought to the configuration screen. There are a
number of options here and I started with the basic set:</p>
<p>I chose Subversion for the source control management section and gave it the svn
path. There is a checkout strategy as well and I chose the one to revert and
update which I feel to be a bit cleaner.</p>
<p>I chose to poll the scm every fifteen minutes</p>
```cron
*/15 * * * *
```
<p>and saved.</p>
<p>Running the build pulled the code out of svn and stopped there. This was because
I didn&rsquo;t add a step to build / install it.</p>
<p>Go back into configure the job and add a maven 3 build step. This automatically
selected the clean install goals. Save and build now and the project was checked
out and built without issues.</p>
<p>Success!</p>
<p>There are a number of other options you can play with here but this gives you a
solid starting point.</p>
<p>Later on, I will cover the addition of various other plugins for source analysis
including findbugs and pmd.</p>
]]></content:encoded></item><item><title>Looping from the bash commandline [1113]</title><link>https://icle.es/2011/12/20/looping-from-the-bask-commandline-1113/</link><pubDate>Tue, 20 Dec 2011 13:32:08 +0000</pubDate><guid>https://icle.es/2011/12/20/looping-from-the-bask-commandline-1113/</guid><description>&lt;p>I figured this out the other day from idle curiosity. There is occassionally the
need to have a never ending loop to be executed directly from the bash
commandline instead of writing a script.&lt;/p>
&lt;p>I used this to run sl (yes sl, not ls - try it - I love it) repeatedly.&lt;/p>
```
 $ while true; do ; done
```
&lt;p>for example&lt;/p>
```
 $ while true; do sl; done
```
&lt;p>Bear in mind that this loop is infinite and there is no way to cancel out of it
except to kill of the terminal.&lt;/p></description><content:encoded><![CDATA[<p>I figured this out the other day from idle curiosity. There is occassionally the
need to have a never ending loop to be executed directly from the bash
commandline instead of writing a script.</p>
<p>I used this to run sl (yes sl, not ls - try it - I love it) repeatedly.</p>
```
    $ while true; do ; done
```
<p>for example</p>
```
    $ while true; do sl; done
```
<p>Bear in mind that this loop is infinite and there is no way to cancel out of it
except to kill of the terminal.</p>
]]></content:encoded></item><item><title>Expanding glusterfs volumes [1112]</title><link>https://icle.es/2011/12/20/expanding-glusterfs-volumes-1112/</link><pubDate>Tue, 20 Dec 2011 13:26:55 +0000</pubDate><guid>https://icle.es/2011/12/20/expanding-glusterfs-volumes-1112/</guid><description>&lt;p>&lt;a href="https://icle.es/2011/11/24/glusterfs-howto/" title="GlusterFS HOWTO [1108]">Once you have set up a glusterfs volume&lt;/a>,
you might want to expand the volume to add storage. This is an astoundingly easy
task.&lt;/p>
&lt;p>The first thing that you&amp;rsquo;ll want to do is to add in bricks. Bricks are similar
to physical volumes a la LVM. The thing to bear in mind is that depending on
what type of cluster you have (replicated / striped), you will need to add a
certain number of blocks at a time.&lt;/p></description><content:encoded><![CDATA[<p><a href="https://icle.es/2011/11/24/glusterfs-howto/" title="GlusterFS HOWTO [1108]">Once you have set up a glusterfs volume</a>,
you might want to expand the volume to add storage. This is an astoundingly easy
task.</p>
<p>The first thing that you&rsquo;ll want to do is to add in bricks. Bricks are similar
to physical volumes a la LVM. The thing to bear in mind is that depending on
what type of cluster you have (replicated / striped), you will need to add a
certain number of blocks at a time.</p>
<p>Once you have a initialised the nodes, to add in a set of bricks, you need the
following command which adds two more bricks to a cluster which keeps two
replicas.</p>
```bash
$ gluster volume add-brick testvol cserver3:/gdata cserver4:/gdata
```
<p>Once you have done this, you will need to rebalance the cluster, which involves
redistributing the files across all the bricks. There are two steps to this
process, the &ldquo;fixing&rdquo; of the layout changes and the rebalancing of the data
itself. You can perform both tasks together.</p>
<p>As a starting point, to view the status of a rebalance, you can use:</p>
```bash
$ gluster volume rebalance testvol status
```
<p>You can also stop / pause a rebalance with</p>
```bash
$ gluster volume rebalance testvol stop
```
<p>To &ldquo;fix&rdquo; the layout changes, you need to run:</p>
```bash
$ gluster volume rebalance testvol fix-layout start
Starting rebalance on volume test-volume has been successful
```
<p>Rebalancing the volume to migrate the data is easy and can be done using a
similar command:</p>
```bash
$ gluster volume rebalance testvol migrate-data start
```
<p>To complete both in one command, you just need:</p>
```bash
$ gluster volume rebalance testvol start
```
<p>Easy right?</p>
<p>With this mechanism, you have the ability to have storage that can be expanded
on the fly by using additional hardware. You can also remove existing bricks
using:</p>
```bash
$ gluster volume remove-brick testvol cserver2:/gdata
```
<p>This means that you can remove a brick with smaller hard drives, upgrade the
harddrives, and re-integrate into the cluster with bigger hard drives. This
means that you have a cloud like storage solution which you can easily grow as
necessary without worrying about resizing underlying filesystems or hotswapping
hardisks or any of that hassle.</p>
]]></content:encoded></item><item><title>My Thoughts on OCFS2 / Understanding OCFS2 [1110]</title><link>https://icle.es/2011/11/24/my-thoughts-on-ocfs2-understanding-ocfs2-1110/</link><pubDate>Thu, 24 Nov 2011 21:42:09 +0000</pubDate><guid>https://icle.es/2011/11/24/my-thoughts-on-ocfs2-understanding-ocfs2-1110/</guid><description>&lt;p>&lt;a href="https://icle.es/2011/11/24/glusterfs-howto/" title="GlusterFS HOWTO [1108]">As mentioned earlier&lt;/a>,
we have been considered networked filesystems instead of NFS to introduce into a
number of complex environments. OCFS2 was one of the first candidates.&lt;/p>
&lt;p>In fact, we also considered GFS2 but looking around on the net, there seemed to
be a general consensus recommending ocfs2 over gfs2.&lt;/p>
&lt;p>Ubuntu makes it pretty easy to install and manage ocfs2 clusters. You just need
to install ocfs2-tools and ocfs2console. You can then use the console to manage
the cluster.&lt;/p>
&lt;p>What I totally missed in all of my research and understanding, and due to lack
of in depth knowledge on clustered filesystems was that OCFS2 (and GFS2 for that
matter) are shared disk file systems.&lt;/p>
&lt;p>What does this mean?&lt;/p></description><content:encoded><![CDATA[<p><a href="https://icle.es/2011/11/24/glusterfs-howto/" title="GlusterFS HOWTO [1108]">As mentioned earlier</a>,
we have been considered networked filesystems instead of NFS to introduce into a
number of complex environments. OCFS2 was one of the first candidates.</p>
<p>In fact, we also considered GFS2 but looking around on the net, there seemed to
be a general consensus recommending ocfs2 over gfs2.</p>
<p>Ubuntu makes it pretty easy to install and manage ocfs2 clusters. You just need
to install ocfs2-tools and ocfs2console. You can then use the console to manage
the cluster.</p>
<p>What I totally missed in all of my research and understanding, and due to lack
of in depth knowledge on clustered filesystems was that OCFS2 (and GFS2 for that
matter) are shared disk file systems.</p>
<p>What does this mean?</p>
<p><a href="http://en.wikipedia.org/" title="Wikipedia">Wikipedia</a> defines a
<a href="http://en.wikipedia.org/wiki/Shared_disk_file_system" title="Shared Disk File System">shared disk filesystem</a> as
being &ldquo;shared by being
simultaneously <a href="http://en.wikipedia.org/wiki/Mount_%28computing%29" title="Mount
(computing)">mounted</a> on
multiple <a href="http://en.wikipedia.org/wiki/Server_%28computing%29" title="Server (computing)">servers</a>.&rdquo;</p>
<p>This essentially means that the storage medium is mounted on to cluster. The
cluster is a collection of the clients. The storage is traditionally a SAN mount
point. This means that a shared storage space is accessed at high speeds by a
number of clients.</p>
<p>From a simplistic point of view, this is not that different from mounting the
SAN point onto a server and then running an NFS server on it with all the
clients mounting over NFS.</p>
<p>The main difference is that OCFS is distributed. There is no single point of
failure (assuming that the storage medium is redundant which a SAN would be).
The NFS server is a clear single point of failure.</p>
<p>If you do not have access to or want to use a SAN,
<a href="http://www.drbd.org/users-guide/ch-ocfs2.html" title="Using OCFS2 with DRBD">you can also use DRBD.</a></p>
<p>NFS is described as a network filesystem and GlusterFS is described as a NAS
file system.</p>]]></content:encoded></item><item><title>Exporting X11 to Windows [1109]</title><link>https://icle.es/2011/11/24/exporting-x11-to-windows-1109/</link><pubDate>Thu, 24 Nov 2011 21:10:55 +0000</pubDate><guid>https://icle.es/2011/11/24/exporting-x11-to-windows-1109/</guid><description>&lt;p>Playing Skyrim the last week, sometimes I just missed Linux so terribly that I
wanted a piece of it and not just the command line version. I wanted X Windows
on my Windows 7.&lt;/p>
&lt;p>There has been a solution for this for several years and the first time I did
this, I installed &lt;a href="http://www.cygwin.com/" title="cygwin">cygwin&lt;/a> with X11 but there
is a far simpler way to accomplish this.&lt;/p>
&lt;p>Install &lt;a href="http://www.straightrunning.com/XmingNotes/" title="XMing">XMing&lt;/a>. I then used
putty, which has the forward X11 option. Once logged in, running xeyes shows the
window exported onto my Windows 7. Ah.. so much better.&lt;/p></description><content:encoded><![CDATA[<p>Playing Skyrim the last week, sometimes I just missed Linux so terribly that I
wanted a piece of it and not just the command line version. I wanted X Windows
on my Windows 7.</p>
<p>There has been a solution for this for several years and the first time I did
this, I installed <a href="http://www.cygwin.com/" title="cygwin">cygwin</a> with X11 but there
is a far simpler way to accomplish this.</p>
<p>Install <a href="http://www.straightrunning.com/XmingNotes/" title="XMing">XMing</a>. I then used
putty, which has the forward X11 option. Once logged in, running xeyes shows the
window exported onto my Windows 7. Ah.. so much better.</p>
<p>I actually used this to run terminator to connect to a number of servers. Over
local LAN, the windows didn't have any perceptible lag or delay. It was more or
less like running it locally.</p>
<p>It is possible to set up shortcuts to run an application through putty and have
it exported to your desktop. I haven't played with this enough to comment
though.</p>
<p>This of course only worked because I have another box which is running Linux. If
that is not the case for you, then you might want to try
<a href="https://www.virtualbox.org/" title="VirtualBox">VirtualBox</a> but since the linux
kernel developers have described the kernel modules as
<a href="http://www.phoronix.com/scan.php?page=news_item&amp;px=OTk5Mw" title="The VirtualBox Kernel Driver Is Tainted Crap">tainted crap</a>,
you might want to consider <a href="http://www.vmware.com" title="vmware">vmware</a> instead
which is an excellent product.</p>
]]></content:encoded></item><item><title>GlusterFS HOWTO [1108]</title><link>https://icle.es/2011/11/24/glusterfs-howto/</link><pubDate>Thu, 24 Nov 2011 20:53:33 +0000</pubDate><guid>https://icle.es/2011/11/24/glusterfs-howto/</guid><description>&lt;p>So, I  am catching up a bit on the technical documentation. A week taken to play
Skyrim combined with various other bits and pieces made this a little difficult.&lt;/p>
&lt;p>On the bright side, there are a few new things that have been worked on so
hopefully plenty of things to cover soon.&lt;/p>
&lt;p>We manage a number of servers and all over the place and all of them require to
be backed up. We also have a number of desktops all with mirrored disks also
getting backed up.&lt;/p>
&lt;p>I like things to be all nicely efficient and its annoying when one server /
desktop runs out of space when another two (or ten) has plenty of space. We grew
to dislike NFS particularly due to the single point of failure and there were
few other options.&lt;/p>
&lt;p>We had tried &lt;a href="http://www.gluster.org/" title="GlusterFS">glusterfs&lt;/a> a few years ago
(think it was at version 1.3 or something) and there were various issues
particularly around small files and configuration was an absolute nightmare.&lt;/p>
&lt;p>With high hopes that version 3.2 was exactly what we were looking for, we set up
three basic machines for testing&lt;/p></description><content:encoded><![CDATA[<p>So, I  am catching up a bit on the technical documentation. A week taken to play
Skyrim combined with various other bits and pieces made this a little difficult.</p>
<p>On the bright side, there are a few new things that have been worked on so
hopefully plenty of things to cover soon.</p>
<p>We manage a number of servers and all over the place and all of them require to
be backed up. We also have a number of desktops all with mirrored disks also
getting backed up.</p>
<p>I like things to be all nicely efficient and its annoying when one server /
desktop runs out of space when another two (or ten) has plenty of space. We grew
to dislike NFS particularly due to the single point of failure and there were
few other options.</p>
<p>We had tried <a href="http://www.gluster.org/" title="GlusterFS">glusterfs</a> a few years ago
(think it was at version 1.3 or something) and there were various issues
particularly around small files and configuration was an absolute nightmare.</p>
<p>With high hopes that version 3.2 was exactly what we were looking for, we set up
three basic machines for testing</p>
<p>Previously, <a href="http://www.gluster.org/" title="GlusterFS">glusterfs</a> required all the
configuration to be completed manually and with text files. It also required a
fairly detailed knowledge of what they called translators and a lot of tweaking
and fiddling with parameters.</p>
<p>I am very happy to report that this is no longer the case with 3.2.</p>
<p>The three servers(cserver[1-3]) are running Ubuntu and was updated to Natty
(11.10) to get access to glusterfs 3.2 (11.04 only had 3.0). One thing to bear
in mind is that the glusterfs website seemed to only have the 64 bit version but
Ubuntu 11.10 also has the 32bit version.</p>
<p>Installing the server part of glusterfs was simple and straightfoward</p>
```bash
$ sudo aptitude install glusterfs-server
```
<p>Once this was done all three servers
(<a href="http://www.tenshu.net/p/terminator.html" title="Terminator">terminator</a> is a godsend
when doing these things across a number of servers), adding the servers into a
&ldquo;cluster&rdquo; was easy enough.</p>
```bash
shri@cserver1:~$ gluster peer probe cserver2
Probe successful

shri@cserver1:~$ sudo gluster peer probe cserver3
Probe successful
```
<p>The thing to note is that these probe statements are two way. In other words,
all three servers are now part of the same cluster.</p>
```bash
shri@cserver3:~$ sudo gluster peer status
Number of Peers: 2

Number of Peers: 3

Hostname: cserver1
Uuid: 8fe63300-e227-4aec-81f3-69b33f894330
State: Peer in Cluster (Connected)

Hostname: cserver2
Uuid: 275ce612-2dd8-4e2a-8cc8-3115ad18c594
State: Peer in Cluster (Connected)
```
<p>The thing is that if you type in an incorrect hostname, the probe will keep
trying to connect to it. I haven&rsquo;t left it running long enough to know if it
every returns.</p>
```bash
shri@cserver3:~# sudo gluster peer probe does-not-exist
^C
shri@cserver3:~# sudo gluster peer status
Number of Peers: 3

Hostname: cserver1
Uuid: 8fe63300-e227-4aec-81f3-69b33f894330
State: Peer in Cluster (Connected)

Hostname: cserver2
Uuid: 275ce612-2dd8-4e2a-8cc8-3115ad18c594
State: Peer in Cluster (Connected)

Hostname: does-not-exist
Uuid: 00000000-0000-0000-0000-000000000000
State: Establishing Connection (Disconnected)
```
<p>Thankfully, removing host does-not-exist is simple enough</p>
```bash
shri@cserver3:~# sudo gluster peer detach does-not-exist
Detach successful
shri@cserver3:~# sudo gluster peer status
Number of Peers: 2

Hostname: cserver1
Uuid: 8fe63300-e227-4aec-81f3-69b33f894330
State: Peer in Cluster (Connected)

Hostname: cserver2
Uuid: 275ce612-2dd8-4e2a-8cc8-3115ad18c594
State: Peer in Cluster (Connected)
```
<p>Creating a volume is straightforward. There are a number of different types of
volumes which you can find out from the documentation. In this particular
instance, we are creating a distributed replicated</p>
```bash
shri@cserver3:~$ sudo gluster volume create testvol replica 2 transport tcp cserver1:/gdata cserver2:/gdata
Creation of testvol has been successful
Please start the volume to access data
```
<p>The reason I have not included cserver3 in here is that the volume needs a
multiple of the replica number of bricks. In the case, the there needs to be a
muliple of 2 number of bricks.</p>
<p>Additionally, you could use rdma instead of tcp if you are using infiniband</p>
<p>Starting the volume is simple enough</p>
```bash
shri@cserver3:~$ sudo gluster volume start testvol
```
<p>this volume is now accessible from all the boxes in the cluster</p>
```bash
shri@cserver1:~# sudo gluster volume info

Volume Name: testvo
Type: Distributed-Replicate
Status: Started
Number of Bricks: 2 x 1 = 2
Transport-type: tcp
Bricks:
Brick1: cserver1:/gdata
Brick2: cserver2:/gdata
```
<p>Mounting this from another box is easy</p>
```bash
$ sudo aptitude install glusterfs-client
$ sudo mount -t glusterfs /mnt cserver:/testvol
```
<p>If you get the error of &ldquo;endpoint not connected&rdquo; when listing the content of the
mount, it is likely because the volume is not started.</p>
<p>If you are curious, check the gdata folders in the bricks after copying some
files into the mount and you&rsquo;ll find them show up intact and on both bricks in
the above example.</p>]]></content:encoded></item><item><title>Elder Scrolls V: Skyrim [1111]</title><link>https://icle.es/2011/11/11/elder-scrolls-v-skyrim-1111/</link><pubDate>Fri, 11 Nov 2011 02:32:00 +0000</pubDate><guid>https://icle.es/2011/11/11/elder-scrolls-v-skyrim-1111/</guid><description>&lt;p>I realise that this stretches the concept of a technical blog post but you know
what, it doesn&amp;rsquo;t matter. There are several technical elements that are relevant
but I am not necessarily going to focus on. I am going to do what I do best;
ramble&amp;hellip;&lt;/p>
&lt;p>I waited till midnight and on the gong (as it were), I clicked install and
instead of it saying that the game was not released yet (and I do wonder why
they make us wait until midnight when we have the stuff to be able to play it
anyway but that is a whole another blog post), it started to install.&lt;/p>
&lt;p>I felt an anticipation and an elation that I have not felt in a very long time.
My first foray into the world of Elder Scrolls was daggerfall and this was way
after it was released. I had always thought about a world; a living breathing
world that was built upon simple foundations that could grow to envelop your
imagination.&lt;/p>
&lt;p>Elder Scrolls promised to do that and while Daggerfall failed spectacularly to
deliver this, it set an expectation. I for one am glad that Bethesda continued
on and eventually brought us Morrowind which I played for a while and loved but
couldn&amp;rsquo;t quite get the hang of.&lt;/p></description><content:encoded><![CDATA[<p>I realise that this stretches the concept of a technical blog post but you know
what, it doesn&rsquo;t matter. There are several technical elements that are relevant
but I am not necessarily going to focus on. I am going to do what I do best;
ramble&hellip;</p>
<p>I waited till midnight and on the gong (as it were), I clicked install and
instead of it saying that the game was not released yet (and I do wonder why
they make us wait until midnight when we have the stuff to be able to play it
anyway but that is a whole another blog post), it started to install.</p>
<p>I felt an anticipation and an elation that I have not felt in a very long time.
My first foray into the world of Elder Scrolls was daggerfall and this was way
after it was released. I had always thought about a world; a living breathing
world that was built upon simple foundations that could grow to envelop your
imagination.</p>
<p>Elder Scrolls promised to do that and while Daggerfall failed spectacularly to
deliver this, it set an expectation. I for one am glad that Bethesda continued
on and eventually brought us Morrowind which I played for a while and loved but
couldn&rsquo;t quite get the hang of.</p>
<p>Then there was Oblivion which again, I played for a while but couldn&rsquo;t really
get the hang of. In honesty, it is only in replaying it now over the last few
weeks that I started to get the hang of it. The secret was to not
over-specialise which was the mistake that I originally made. The other mistake
I had made as not to be patient. It is tedious and annoying in the earlier parts
of the game because, as a character, you are just not powerful enough.</p>
<p>At level 20, I was finding the game rewarding. The level itself is largely
irrelevant, it was that I was able to fill the skills gap using other
mechanisms. For example, my securiy skill was atrocious but I managed to acquire
the skeleton key (which also ups your security skill). This helped me make leaps
and bounds of progress through the thieves guilds missions.</p>
<p>Alchemy and getting that up to a 100 pretty much changed the game for me as
well. Having picked the Atronach as the birth sign (No Magika regeneration but
spell absorption at 50), I really struggled at the beginning of the game to keep
my Magika replenished.</p>
<p>After about level 16 or so with Alchemy ramped up, I found that I had more
Magika potions that I could throw a stick at. My intelligence was also up at a
100 at that point and the discovery that there was no benefit in increasing my
willpower (since there was no Magika regeneration to worry about) changed the
focus on levelling up as well.</p>
<p>Along the invisibility spell, Oblivion was suddenly much easier to play. I also
got the hang of regularly swapping between and using the various spells. Major
Heal wounds instead of health potions became a habit particularly since my
Magika restoration usually meant that Magika was still going up after I&rsquo;d killed
all the enemies around the extra regen was just going to waste.</p>
<p>In short, Oblivion is a complex game and you cannot over-specialise if you want
to be able to succeed. As the prebuild Sorcerer class, I had an unusual
combination of skills to work with including heavy armour and magic.</p>
<p>I expect the biggest complaint that people might have about Skyrim is that it is
a lot simpler. There is no armourer skill component for maintaining your armour
for example. While this was a nice maintenance thing, particularly when you
levelled up on it and you got that feeling of satisfaction, I believe that
Skyrim is going largely in the right direction.</p>
<p>Fallout 3 based on the same Engine did a great job to kick it up a notch from
Oblivion and Skyrim feels like it has really kicked it up a few notches.</p>
<p>The graphics are absolutely stunning ( in fairness, I am seeing it with the the
quality set to ultra high :-] )and the game just flows so much better. The NPC
interactions (in my limited 90 minutes or so gameplay) is practically sublime.</p>
<p>Let me put it this way, stealing from a family made me actually feel guilty. If
a game can make you feel like the characters in it are real and make you feel
guilt like that, then you are definitely doing something right.</p>
<p>The radiant A.I system with the updates coming into Skyrim is also very
impressive. Actualy gameplay experience does seem to give it much a organic and
fluidic feel.</p>
<p>I expect that my feelings might change over the next few days and dozens of
hours of gameplay. However, I would like to finish by saying the Skyrim has so
far failed to disappoint which if you know me, is a rare and high praise.</p>]]></content:encoded></item><item><title>Directed Acyclic Graphs and Executing Tasks in Order (and in Parallel) Based on Dependencies</title><link>https://icle.es/2011/11/07/directed-acyclic-graphs-and-executing-tasks-in-order-and-in-parallel-based-on-dependencies-1107/</link><pubDate>Mon, 07 Nov 2011 23:44:36 +0000</pubDate><guid>https://icle.es/2011/11/07/directed-acyclic-graphs-and-executing-tasks-in-order-and-in-parallel-based-on-dependencies-1107/</guid><description>&lt;p>A little while ago, there was a requirement to write a tool that could take a
number of tasks each with a set of dependencies and execute them in parallel
while taking the dependencies into account.&lt;/p>
&lt;p>The tasks themselves were meant for data migration but that is not particularly
relevant. We were writing a number of tasks which all had a set of dependencies
(some of the tasks did not have any dependencies or the process could of course
never start).&lt;/p>
&lt;p>It was assumed that there were no cyclic dependencies (which would be error in
this particular case anyway)&lt;/p>
&lt;p>Bearing in mind that this was a quick and dirty tool for use three times, some
of the bits in here could do with tidying up.&lt;/p>
&lt;p>Each task was defined to implement the following interface&lt;/p>
```java
 public interface Task extends Runnable {

 public String getName();

 public Set getDependencies();

 }
```</description><content:encoded><![CDATA[<p>A little while ago, there was a requirement to write a tool that could take a
number of tasks each with a set of dependencies and execute them in parallel
while taking the dependencies into account.</p>
<p>The tasks themselves were meant for data migration but that is not particularly
relevant. We were writing a number of tasks which all had a set of dependencies
(some of the tasks did not have any dependencies or the process could of course
never start).</p>
<p>It was assumed that there were no cyclic dependencies (which would be error in
this particular case anyway)</p>
<p>Bearing in mind that this was a quick and dirty tool for use three times, some
of the bits in here could do with tidying up.</p>
<p>Each task was defined to implement the following interface</p>
```java
    public interface Task extends Runnable {

        public String getName();

        public Set getDependencies();

    }
```
<p>It should all be self explanatory. Extending the Runnable interface ensure that
we can pass it into threads and other relevant bits of code. The getDependencies
is expected to return the name of the tasks that it depends on.</p>
<p>The basic task runner which I describe below does not check if the task
described in any list of dependencies actually exist. If an non-existing
dependency is defined, it will likely just throw a Null Pointer Exception. I
wrote this a long time ago, so don&rsquo;t actually remember.</p>
<p>The BasicTaskRunner which we used to run the tasks implemented the TaskRunner
Interface</p>
```java
    public interface TaskRunner {

        public boolean addTask(Task task);

        public boolean prepare();

        public boolean start();

        public void waitToComplete();
    }
```
<p>The <code>addTask</code> method simply added it to a map from String -&gt; Task and threw an
exception in the event of a duplicate task being added in.</p>
```java
       @Override
        public synchronized boolean addTask(Task task) {
            LOG.info("Adding task: " + task.getName());

            if (tasks.put(task.getName(), task) != null) {
                throw new RuntimeException("Task with same name already exists: " + task.getName());
            }

            return true;
        }
```
<p>the prepare method just calls a method to buildGraph. This uses the jGrapht
library to build a
<a href="http://en.wikipedia.org/wiki/Directed_acyclic_graph" title="Directed Acyclic Graph">Directed Acyclic Graph</a></p>
```java
       private boolean buildGraph() {

            LOG.info("Building DAG of tasks");
            graph = new SimpleDirectedGraph(DefaultEdge.class);

            LOG.info("Adding tasks");

            for (Task task: tasks.values()) {
                graph.addVertex(task);
            }

            LOG.info("Adding Relationships");

            for (Task task: tasks.values()) {

                if (task.getDependencies() != null) {
                    for (String depend: task.getDependencies()) {

                        Task dependOnTask = tasks.get(depend);

                        LOG.info("Adding relationship between " + task.getName() + " and " + dependOnTask.getName());
                        graph.addEdge(dependOnTask, task);

                    }
                }
            }

            return true;
        }
```
<p>So we create a simple directed graph, loop through the tasks, then each of its
dependencies to create an edge, which we then add to the graph. Simple stuff
really.</p>
<p>the start method, which actually executes the task is as follows:</p>
```java
       public boolean start() {

            int cpus = Runtime.getRuntime().availableProcessors();

            executor = new ThreadExecutor(cpus, 60, new LinkedBlockingQueue());

            numTasks = graph.vertexSet().size();
            LOG.info("Starting... Num Tasks: " + numTasks);
            startTime = System.currentTimeMillis();

            scheduleTasks();

            return true;

        }
```
<p>As a basic algorithm, we pick up the number of available processors and use that
many threads. scheduleTasks is a pseudo-recursive function whose role is to add
the currently executable list of tasks into the executor to execute.</p>
```java
       private void scheduleTasks() {
            if (graph.vertexSet().size() == 0) {
                executor.shutdown();
            }

            synchronized (graph ) {
                Iterator iter = new TopologicalOrderIterator(graph);
                Set executing = new HashSet();

                while(iter.hasNext()) {

                    Task task = iter.next();
                    //System.out.println(task.getName());
                    if (graph.incomingEdgesOf(task).size() == 0 && !executing.contains(task)) {
                        executor.execute(task);
                        executing.add(task);
                    }

                }
            }

        }
```
<p>If there are no tasks left to execute, we shut the executor down. All being
well, we add every single task in the graph that has no dependencies to be
executed. The threadpool ensures that any tasks that cannot currently be
executed are queued.</p>
<p>We use a custom version of the threadpool as follows:</p>
```java
       private class ThreadExecutor extends ThreadPoolExecutor {

            public ThreadExecutor(int corePoolSize, long keepAliveSeconds, BlockingQueue workQueue) {
                super(corePoolSize, corePoolSize, keepAliveSeconds, TimeUnit.SECONDS, workQueue);
            }

            @Override
            protected void afterExecute(Runnable runTask, Throwable e) {
                super.afterExecute(runTask, e);

                if (e == null) {
                    completed((Task) runTask);
                } else {
                    failed((Task) runTask, e);
                }
            }

        }
```
<p>The main purpose of this is to use the completed and failed callbacks to ensure
that on complete, dependent tasks can be executed. On fail, we ensure that
dependent tasks are not executed. The code currently does not allow for tasks
that are left behind and will hang indefinitely after executing all tasks it
can.</p>
```java
       public void completed(Task t) {
            LOG.info("Completed Task: " + t.getName());

            synchronized (graph) {
                graph.removeVertex(t);
            }

            long timeTaken = (System.currentTimeMillis() - startTime);
            int tasksComplete = numTasks - graph.vertexSet().size();

            long timePerTask = timeTaken/tasksComplete;

            long totalTime = timePerTask * numTasks;
            long timeToComplete = timePerTask * graph.vertexSet().size();

            LOG.info(" ## Tasks left: " + graph.vertexSet().size()
                   + " ## Elapsed: " + timeTaken/1000
                   + " ## Est. Total " + totalTime/1000
                   + " ## E.T.A : " + timeToComplete/1000);

            scheduleTasks();
        }

        public void failed(Task t, Throwable e) {
            LOG.fatal("Failed Task: " + t.getName(), e);
            scheduleTasks();
        }
```
<p>On completion of a task, we simply remove the task from the graph. The frees up
all its dependencies to be executed. We add these tasks into the list by calling
scheduleTasks again. There is nothing more for us to do when a task fails except
to schedule any other tasks that can be executed. In theory, this call is
redundant since any tasks that could be executed before the failure are already
in the queue. Any tasks that can be completed on the completion of another item
will be initiated on the completion of that task.</p>
<p>I hope the above makes sense and has been helpful. The code for the full class
including further logging statements follows. Please bear in mind that this was
hacked together over a couple of hours for something that was to be executed a
grand total of three times.</p>
```java
    public class BasicTaskRunner implements TaskRunner {

        private static final Logger LOG = Logger.getLogger(BasicTaskRunner.class);

        private Map tasks = new HashMap();

        private DirectedGraph graph;

        private ThreadExecutor executor;

        @Override
        public synchronized boolean addTask(Task task) {
            LOG.info("Adding task: " + task.getName());

            if (tasks.put(task.getName(), task) != null) {
                throw new RuntimeException("Task with same name already exists: " + task.getName());

            }

            return true;
        }

        @Override
        public boolean prepare() {

            LOG.info("Preparing task runner. Num Tasks: " + tasks.size());

            buildGraph();

            return false;
        }

        private boolean buildGraph() {

            LOG.info("Building DAG of tasks");
            graph = new SimpleDirectedGraph(DefaultEdge.class);

            LOG.info("Adding tasks");

            for (Task task: tasks.values()) {
                graph.addVertex(task);
            }

            LOG.info("Adding Relationships");

            for (Task task: tasks.values()) {

                if (task.getDependencies() != null) {
                    for (String depend: task.getDependencies()) {

                        Task dependOnTask = tasks.get(depend);

                        LOG.info("Adding relationship between " + task.getName() + " and " + dependOnTask.getName());
                        graph.addEdge(dependOnTask, task);

                    }
                }
            }

            return true;
        }

        public void waitToComplete() {
            try {
                executor.awaitTermination(3, TimeUnit.DAYS);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        private long startTime;
        private int numTasks;

        @Override
        public boolean start() {

            int cpus = Runtime.getRuntime().availableProcessors();

            executor = new ThreadExecutor(cpus, 60, new LinkedBlockingQueue());

            numTasks = graph.vertexSet().size();
            LOG.info("Starting... Num Tasks: " + numTasks);
            startTime = System.currentTimeMillis();

            scheduleTasks();

            return true;

        }

        private void scheduleTasks() {
            if (graph.vertexSet().size() == 0) {
                executor.shutdown();
            }

            synchronized (graph ) {
                Iterator iter = new TopologicalOrderIterator(graph);
                Set executing = new HashSet();

                while(iter.hasNext()) {

                    Task task = iter.next();
                    //System.out.println(task.getName());
                    if (graph.incomingEdgesOf(task).size() == 0 && !executing.contains(task)) {
                        executor.execute(task);
                        executing.add(task);
                    }

                }
            }

        }

        public void completed(Task t) {
            LOG.info("Completed Task: " + t.getName());

            synchronized (graph) {
                graph.removeVertex(t);
            }

            long timeTaken = (System.currentTimeMillis() - startTime);
            int tasksComplete = numTasks - graph.vertexSet().size();

            long timePerTask = timeTaken/tasksComplete;

            long totalTime = timePerTask * numTasks;
            long timeToComplete = timePerTask * graph.vertexSet().size();

            LOG.info(" ## Tasks left: " + graph.vertexSet().size()
                   + " ## Elapsed: " + timeTaken/1000
                   + " ## Est. Total " + totalTime/1000
                   + " ## E.T.A : " + timeToComplete/1000);

            scheduleTasks();
        }

        public void failed(Task t, Throwable e) {
            LOG.fatal("Failed Task: " + t.getName(), e);
            scheduleTasks();
        }

        private class ThreadExecutor extends ThreadPoolExecutor {

            public ThreadExecutor(int corePoolSize, long keepAliveSeconds, BlockingQueue workQueue) {
                super(corePoolSize, corePoolSize, keepAliveSeconds, TimeUnit.SECONDS, workQueue);
            }

            @Override
            protected void beforeExecute(Thread thread, Runnable runTask) {
                super.beforeExecute(thread, runTask);

                Task task = (Task) runTask;

                LOG.info("Starting task: " + task.getName());
            }

            @Override
            protected void afterExecute(Runnable runTask, Throwable e) {
                super.afterExecute(runTask, e);

                if (e == null) {
                    completed((Task) runTask);
                } else {
                    failed((Task) runTask, e);
                }
            }

        }

    }
```]]></content:encoded></item><item><title>PostgreSQL performing huge updates</title><link>https://icle.es/2011/11/06/postgresql-performing-huge-updates-1106/</link><pubDate>Sun, 06 Nov 2011 12:45:41 +0000</pubDate><guid>https://icle.es/2011/11/06/postgresql-performing-huge-updates-1106/</guid><description>&lt;p>PostgreSQL is a pretty powerful database server and will work with almost any
settings thrown at it. It is really good at making do with what it has and
performing as it is asked.&lt;/p>
&lt;p>We recently found this as we were trying to update every row in a table that had
over eight million entries. We found in the first few tries that the update was
taking over 24 hours to complete which was far too long for an update script.&lt;/p>
&lt;p>Our investigation of this led us to the pgsql_tmp folder and the work_mem
configuration parameter.&lt;/p>
&lt;p>When the query was being executed, we checked the pgsql_tmp folder to see how
was space being utilised in there. We already knew about the pgsql table from
past experience. We had a server running out of disk space and rapidly. We had
narrowed it down into this folder. In cancelling the query referenced by the tmp
files in here, we were able to free up literally gigabytes of disk space...&lt;/p></description><content:encoded><![CDATA[<p>PostgreSQL is a pretty powerful database server and will work with almost any
settings thrown at it. It is really good at making do with what it has and
performing as it is asked.</p>
<p>We recently found this as we were trying to update every row in a table that had
over eight million entries. We found in the first few tries that the update was
taking over 24 hours to complete which was far too long for an update script.</p>
<p>Our investigation of this led us to the pgsql_tmp folder and the work_mem
configuration parameter.</p>
<p>When the query was being executed, we checked the pgsql_tmp folder to see how
was space being utilised in there. We already knew about the pgsql table from
past experience. We had a server running out of disk space and rapidly. We had
narrowed it down into this folder. In cancelling the query referenced by the tmp
files in here, we were able to free up literally gigabytes of disk space...</p>
<p>We had found roughly half a gig of temporary files in here. This led us to
investigate the configuration file.</p>
<p>The one parameter that stuck out was work_mem which was set to a default of 1mb
which I guess might make sense under most circumstances but not in this one.
According to the postgresql documentation</p>
<blockquote>
<p><code>work_mem</code> (<code>integer</code>)</p>
<p>Specifies the amount of memory to be used by internal sort operations and hash
tables before switching to temporary disk files. The value is defaults to one
megabyte (<code>1MB</code>). Note that for a complex query, several sort or hash
operations might be running in parallel; each one will be allowed to use as
much memory as this value specifies before it starts to put data into
temporary files. Also, several running sessions could be doing such operations
concurrently. So the total memory used could be many times the value
of <code>work_mem</code>; it is necessary to keep this fact in mind when choosing the
value. Sort operations are used for <code>ORDER BY</code>, <code>DISTINCT</code>, and merge joins.
Hash tables are used in hash joins, hash-based aggregation, and hash-based
processing of <code>IN</code> subqueries.</p></blockquote>
<p>This would tell us that the total memory usage with work_mem could be several
times the value set here and setting it to half a gig would probably be a
terrible idea for a heavily utilised production server. However, for the
migration process when we need to update over 8,000,000 rows, it might be a good
temporary fix.</p>
<p>After updating the work_mem to 512mb, we found that no more tmp files were
created and the whole thing was done in memory.</p>
<p>When updating so many rows, there area a few other things to consider.</p>
<p>Firstly, autovacuum will likely kick in several times to vacuum the table.
You'll probably want to disable this for the duration of the update statement
and run a vacuum afterwards.</p>
```sql
    --disable auto vacuum
    ALTER TABLE sometable SET (
      autovacuum_enabled = false, toast.autovacuum_enabled = false
    );
```
<p>You can switch autovacuum back on after the update statement has completed</p>
```sql
    --enable auto vacuum
    ALTER TABLE sometable SET (
      autovacuum_enabled = true, toast.autovacuum_enabled = true
    );
```
<p>A few other things you want to take a look at are the</p>
<ul>
<li>fsync parameter (I usually have this set to off anyway since the servers are
pratically fully redundant)</li>
<li>checkpoint_segments: I changed this to roughly 5 times the original value
(check the log to see if it says that its checkpointing too often)</li>
<li>checkpoint_completion_target: I changed this to 0.9</li>
</ul>
<p>With all of these updates, we were able to bring the total time of the update
down to a few hours.</p>]]></content:encoded></item><item><title>Using CXF Interceptors to do some magic around your web service calls [1105]</title><link>https://icle.es/2011/11/06/using-cxf-interceptors-to-do-some-magic-around-your-web-service-calls-1105/</link><pubDate>Sun, 06 Nov 2011 12:24:28 +0000</pubDate><guid>https://icle.es/2011/11/06/using-cxf-interceptors-to-do-some-magic-around-your-web-service-calls-1105/</guid><description>&lt;p>We use JBossWS CXF for a heavily utilised enterprise system. It links into
spring to pick up and execute beans. We have a bunch of exceptions that could
get thrown.&lt;/p>
&lt;p>To simplify it, the code was originally written to create an anonymous class a
la Runnable which is wrapped around a try catch block. The exceptions that are
thrown are then converted to a soap fault and passed back.&lt;/p>
```java
private SOAPFaultException convertToSoapException(ApplicationException e)
{
 try {
 if(null == soapFactory) {
 soapFactory = SOAPFactory.newInstance();
 }
 SOAPFault sf = soapFactory.createFault();
 sf.setFaultString( e.getMessage() );
 sf.setFaultCode( Integer.toString(e.getErrorCode()) );
 return new SOAPFaultException( sf );
 } catch(SOAPException soapException) {
 throw new RuntimeException( soapException );
 }
}
```
&lt;p>Nothing inherently wrong with this. However, there are a couple of issues with
this in that each soap method is set to throw an &lt;em>ApplicationException&lt;/em> and
there is not further documentation of which of the subclasses are actually
relevant to that method.&lt;/p></description><content:encoded><![CDATA[<p>We use JBossWS CXF for a heavily utilised enterprise system. It links into
spring to pick up and execute beans. We have a bunch of exceptions that could
get thrown.</p>
<p>To simplify it, the code was originally written to create an anonymous class a
la Runnable which is wrapped around a try catch block. The exceptions that are
thrown are then converted to a soap fault and passed back.</p>
```java
private SOAPFaultException convertToSoapException(ApplicationException e)
{
    try {
        if(null == soapFactory) {
            soapFactory = SOAPFactory.newInstance();
        }
        SOAPFault sf = soapFactory.createFault();
        sf.setFaultString( e.getMessage() );
        sf.setFaultCode( Integer.toString(e.getErrorCode()) );
        return new SOAPFaultException( sf );
    } catch(SOAPException soapException) {
        throw new RuntimeException( soapException );
    }
}
```
<p>Nothing inherently wrong with this. However, there are a couple of issues with
this in that each soap method is set to throw an <em>ApplicationException</em> and
there is not further documentation of which of the subclasses are actually
relevant to that method.</p>
<p>In a runtime environment, this is not hugely relevant. However, when generating
documentation from the WSDL&rsquo;s, it is.</p>
<p>To resolve this, we changed each method to throw their relevant exception, and
wrote an interceptor to pick up the exception and convert it&hellip;</p>
<p>The first step was to write an interceptor which is surprisingly simple
and straightforward.</p>
```java
public class ExampleSoapFaultInterceptor extends AbstractSoapInterceptor {

    Logger log = Logger.getLogger(getClass());

    public QuarkSoapFaultInterceptor() {
        super(Phase.MARSHAL);
    }

    @Override
    public void handleMessage(SoapMessage message) throws Fault {
        Fault f = (Fault) message.getContent(Exception.class);

        Throwable cause = f.getCause();
        if (cause instanceof ApplicationException) {
            log.info("Exception Thrown", cause);
            QuarkException e = (QuarkException) cause;
            f.setFaultCode(new QName("", String.valueOf(e.getErrorCode())));

        } else {
            log.warn("Unexpected Exception thrown ", cause);
        }

    }
}
```
<p>The class doesn&rsquo;t need to extend the AbstractSoapInterceptor but you would then
have to manually implement a number of mechanisms that the abstract class
provides.</p>
<p>The constructor simply defines where this interceptor should be inserted into as
part of the chain. There is a whole bunch of different places where it can be
inserted based on this. Information can be
<a href="http://cxf.apache.org/docs/interceptors.html" title="Apache CXF Interceptors">found in their documentation.</a></p>
<p>To insert this interceptor into a web service, the service needs to be annotated
as follows:</p>
```java
@WebService(endpointInterface = "uk.co.kraya.example.WebService")
@OutFaultInterceptors(interceptors= {"uk.co.kraya.example.interceptors.ExampleSoapFaultInterceptor"})
public class ExampleWebService implements ExampleAPI {
```
<p>Each exception thrown from the web service will now be intercepted. You can then
include any further further information in the soap fault.</p>
<p>In this particular case, the only thing that gets done is to update the fault
code with the error code set against the exception. Any additional information
can be set against the soap fault at this point. You can also log as we are
doing.</p>
]]></content:encoded></item><item><title>JBossWS CXF - POJO vs Stateless [1104]</title><link>https://icle.es/2011/11/05/jbossws-cxf-pojo-vs-stateless/</link><pubDate>Sat, 05 Nov 2011 15:32:32 +0000</pubDate><guid>https://icle.es/2011/11/05/jbossws-cxf-pojo-vs-stateless/</guid><description>&lt;p>Cleaning up a bunch of code to reduce object instantiations got me thinking
about the webservice layer. We are using POJO based webservices but it got to me
wondering whether useless Stateless web service beans would improve memory
usage. More accurately, whether it would improve garbage collection performance.&lt;/p>
&lt;p>To test this, the plan was to build two versions of the same web service and
load test it to see the memory and cpu utilisation to compare cost /
performance.&lt;/p>
&lt;p>In the process, I also discovered other differences.&lt;/p></description><content:encoded><![CDATA[<p>Cleaning up a bunch of code to reduce object instantiations got me thinking
about the webservice layer. We are using POJO based webservices but it got to me
wondering whether useless Stateless web service beans would improve memory
usage. More accurately, whether it would improve garbage collection performance.</p>
<p>To test this, the plan was to build two versions of the same web service and
load test it to see the memory and cpu utilisation to compare cost /
performance.</p>
<p>In the process, I also discovered other differences.</p>
<p>Both the web services were built against this interface</p>
```java
@WebService
public interface WSTest {

    @WebMethod
    public String greetClient(String name);

}
```
<p>That is of course a simple enough interface.</p>
<p>The EJB version of this is as follows:</p>
```java
@WebService(endpointInterface = "uk.co.kraya.wstest.WSTest")
@Stateless
@Remote(WSTest.class)
public class EJBWebTest implements WSTest {

    public String greetClient(String name) {
        return "EJB Says Hello to " + name;
    }

}
```
<p>Simple and straightforward enough and the POJO version is not that different</p>
```java
@WebService
public class PojoWebTest implements WSTest  {

    @WebMethod
    public String greetClient(String name) {
        return "Pojo Says Hello to " + name;
    }

}
```
<p>I also used the following web.xml. This may not be a necessary step any more.</p>
```xml
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>

        <display-name>Archetype Created Web Application</display-name>
        <servlet>
                <servlet-name>GreetingWebService</servlet-name>
                <servlet-class>uk.co.kraya.wstest.pojo.PojoWebTest</servlet-class>
        </servlet>
       <servlet-mapping>
                <servlet-name>GreetingWebService</servlet-name>
                <url-pattern>/*</url-pattern>
        </servlet-mapping>
</web-app>
```
<p>Now that was complete, there was the deployment to consider. I had assumed that
I could just do one build, package it as an war and just deploy it but as it
turns out, that didn&rsquo;t work. This only deployed the Pojo web service. To deploy
the EJB web service, I had to package it as an EJB (maven) and deploy a jar
file.</p>
<p>This meant that I ended up with two deployments, wstestpojo.war and
wstestejb.jar.</p>
<p>Once deployed, I used SoapUI to load test both the web services and the results
were interesting.</p>
<p>In the grand scheme of things, the difference was pretty minimal between the
two. However, the Stateless EJB web service used a little extra (3% - 5%) CPU
presumably from the pooling of the beans and accessing them.</p>
<p>I used JBoss 5.1 and ran into an issue with log pollution</p>
<p>EJBTHREE-1337: do not get WebServiceContext property from stateless bean
context, it should already have been injected</p>
<p>This was printed every time an EJB based web service was called.</p>
<p>I
<a href="http://idevone.wordpress.com/2009/09/14/howto-suppress-ejbthree-1337-warning/" title="HOWTO: Suppress EJBTHREE-1337 warning">found more information about the EJBTHREE-1337 issue and a workaround for it</a>
The workaround simply involves updating log4j to not log it which is good enough
for me&hellip; :-)</p>
<p>As a final note, I am unsure as to how this would scale with complex web
services that have multiple instantiated objects as part of it.  I have a sneaky
suspicion that web services with expensive object instantiations would perform
better if using Stateless EJB Beans as would web services that are memory
hungry. However, this has not been tested.</p>]]></content:encoded></item><item><title>Gnome Desktop Inaccessible After Screensaver Kicks in [1103]</title><link>https://icle.es/2011/11/03/gnome-desktop-inaccessible-after-screensaver-kicks-in-1103/</link><pubDate>Thu, 03 Nov 2011 11:11:14 +0000</pubDate><guid>https://icle.es/2011/11/03/gnome-desktop-inaccessible-after-screensaver-kicks-in-1103/</guid><description>&lt;p>Yesterday, I
&lt;a href="http://drone-ah.com/2011/11/02/saving-your-workspace-window-configuration-in-linux-1102/" title="Saving your workspace window configuration in Linux [1102]">mentioned a problem that I've been having&lt;/a>
with GNOME 3 on Ubuntu 11.10.&lt;/p>
&lt;p>Essentially what happens is that when I leave my desktop for a while, under
specific circumstances, and often, on returning and moving the mouse or using
the keyboard, the pointer would come back  on screen. However, this only works
on one of my two screens.&lt;/p>
&lt;p>The unlock dialog does not show up and it seems that there is no way to get back
in.&lt;/p>
&lt;p>In the past, I would log into the terminal (Ctrl-Alt-F1 or any function key
through to F5 or so) and&lt;/p>
```bash
$ kill -9 -1
```</description><content:encoded><![CDATA[<p>Yesterday, I
<a href="http://drone-ah.com/2011/11/02/saving-your-workspace-window-configuration-in-linux-1102/" title="Saving your workspace window configuration in Linux [1102]">mentioned a problem that I've been having</a>
with GNOME 3 on Ubuntu 11.10.</p>
<p>Essentially what happens is that when I leave my desktop for a while, under
specific circumstances, and often, on returning and moving the mouse or using
the keyboard, the pointer would come back  on screen. However, this only works
on one of my two screens.</p>
<p>The unlock dialog does not show up and it seems that there is no way to get back
in.</p>
<p>In the past, I would log into the terminal (Ctrl-Alt-F1 or any function key
through to F5 or so) and</p>
```bash
$ kill -9 -1
```
<p>This would of course kill all processes owned by me and is therefore unpleasant
at best and have you losing a bunch of work at worst.</p>
<p>After a brainwave yesterday (as detailed in the aforementioned post), I decided
to check the status of the screensaver and killed just those processes. Happily,
this gives me my desktop back. However, my gnome-shell had given up which I had
to restart</p>
```bash
$ gnome-shell --replace
```
<p>Unfortunately, I did not get the windows into the original workspaces since
everything just got dumped into the one workspace but it is better than having
to kill everything off.</p>
<p>EDIT: I just realised that the screen saver of course no longer kicks in and I
had to restart it</p>
```bash
$ gnome-screensaver --no-daemon
```]]></content:encoded></item><item><title>Saving your workspace window configuration in Linux [1102]</title><link>https://icle.es/2011/11/02/saving-your-workspace-window-configuration-in-linux-1102/</link><pubDate>Wed, 02 Nov 2011 23:57:22 +0000</pubDate><guid>https://icle.es/2011/11/02/saving-your-workspace-window-configuration-in-linux-1102/</guid><description>&lt;p>I am usually working on a good half a dozen things at any given time and this
means that I usually have a good ten or twenty windows open. My chromium
currently has a 134 tabs and this is after I  cleaned up and closed all the tabs
I no longer need.&lt;/p>
&lt;p>Luckily, working in Linux means that I can spread each stream of work into the
various workspaces.&lt;/p>
&lt;p>Now GNOME 3 makes things a little more complicated with the dynamic workspaces
but I&amp;rsquo;m learning to use it to my advantage&lt;/p>
&lt;p>However, with Ubuntu 11.10 Oneiric Ocelot and GNOME 3, I seem to be running into
an issue regularly&amp;hellip;If I leave my computer for a while, it doesn&amp;rsquo;t unlock
correctly. The screen remains black and I can&amp;rsquo;t move the mouse to my second
screen and the unlock screen doesn&amp;rsquo;t show up.&lt;/p></description><content:encoded><![CDATA[<p>I am usually working on a good half a dozen things at any given time and this
means that I usually have a good ten or twenty windows open. My chromium
currently has a 134 tabs and this is after I  cleaned up and closed all the tabs
I no longer need.</p>
<p>Luckily, working in Linux means that I can spread each stream of work into the
various workspaces.</p>
<p>Now GNOME 3 makes things a little more complicated with the dynamic workspaces
but I&rsquo;m learning to use it to my advantage</p>
<p>However, with Ubuntu 11.10 Oneiric Ocelot and GNOME 3, I seem to be running into
an issue regularly&hellip;If I leave my computer for a while, it doesn&rsquo;t unlock
correctly. The screen remains black and I can&rsquo;t move the mouse to my second
screen and the unlock screen doesn&rsquo;t show up.</p>
<p>Thinking about it, it seems like there might be two screen savers being started
but I shall investigate that tomorrow. I have the same issue at both work and
home so it is more likely to be related to Ubuntu + GNOME 3 or something about
the way I set things up.</p>
<p>I  usually resolve this by logging into the console and here a neat trick for
killing all our processes in one fell swoop.</p>
```bash
$ kill -9 -1
```
<p>Another thing I have been doing a bit more of recently is gaming which involves
rebooting in Windows.</p>
<p>Both of the above leaves me with a restarted workspace. Starting up the
applications pops them all into the same workspace. Chrome is especially a
nightmare. I might have 135 open tabs but they are in about 6 windows spread
across four workspaces.</p>
<p>It is annoying to have to distribute these things out each time.</p>
<p>After having done much research, I have not been able to find a clean automated
solution.</p>
<p>There are two half solution that I have found however.</p>
<p>The first one is <a href="http://live.gnome.org/DevilsPie" title="Devil&#39;s Pie">Devil&rsquo;s Pie</a>
and for a graphical interface
<a href="http://code.google.com/p/gdevilspie/" title="gdevilspie">gdevilspie</a>. According the
website for Devil&rsquo;s Pie, it is &ldquo;A totally crack-ridden program for freaks and
weirdos who want precise control over what windows do when they appear. If you
want all XChat windows to be on desktop 3, in the lower-left, at 40%
transparency, you can do it.&rdquo;</p>
<p>Unfortunately, that is exactly what it is. If you pre-determine where you want
your windows to be, you can use this very useful application. However, that is
not quite what I want. I want the current configuration to be remember. Exactly
like how Chromium remembers which tabs are in which order in which windows and
their position on the workspace, but for multiple workspaces.</p>
<p>Unfortunately, I couldn&rsquo;t find any way to save the current state.</p>
<p>There is however, another tool
<a href="http://thialfihar.org/projects/window_position_session/" title="Window Position Session">I found scouring the web.</a></p>
<p><a href="http://thialfihar.org/projects/window_position_session/" title="Window Position Session"></a>libwnck-3-dev
is what I installed on my Ubuntu box. There are two key commands here</p>
```bash
$ wnckprop --list
```
<p>This will list all the windows across all the workspaces. To get more
information on a specific Window,</p>
```bash
wnckprop --xid [XID]
```
<p>The XID is the number returned next to each window from the first command. The
post that I  mentioned above has a nifty tool attached that saves the window
positions and can also restore them using wnckprop.</p>
<p>However, it saves them based on the Window title. This of course doesn&rsquo;t work
for Chromium or such Windows that changes the title each time you change the
tab.</p>
<p>However, if the save is the last command you run and the restore is the first
command you run after opening up the windows, it can restore the windows into
the correct workspaces.</p>
<p>With the idea of the dynamic workspaces in GNOME 3, you might have to initialise
the workspaces first but it is better than spending five minutes after logging
in each time re-arranging windows&hellip;</p>]]></content:encoded></item><item><title>Tracking progress of an update statement</title><link>https://icle.es/2011/11/02/tracking-progress-of-an-update-statement-1101/</link><pubDate>Wed, 02 Nov 2011 19:59:02 +0000</pubDate><guid>https://icle.es/2011/11/02/tracking-progress-of-an-update-statement-1101/</guid><description>&lt;p>Sometimes there is a need to execute a long running update statement. This
update statement might be modifying millions of rows as was the case when we
went hunting for a way to track the progress of the update. Hunting around took
us to &lt;a href="http://archives.postgresql.org/pgsql-admin/2002-07/msg00286.php">http://archives.postgresql.org/pgsql-admin/2002-07/msg00286.php&lt;/a> In our
particular case, we are using postgresql but this should work with any database
server that provides sequences. Our original sql was of the form:&lt;/p>
```sql
update only table1 t1
set amount = t2.price
from table2 t2
where t1.id = t2.id;
```
&lt;p>There is of course now way of figuring out how many rows had been updated
already. The first step was to create a sequence&lt;/p>
```sql
CREATE TEMPORARY SEQUENCE seq_progress START 1;
```</description><content:encoded><![CDATA[<p>Sometimes there is a need to execute a long running update statement. This
update statement might be modifying millions of rows as was the case when we
went hunting for a way to track the progress of the update. Hunting around took
us to <a href="http://archives.postgresql.org/pgsql-admin/2002-07/msg00286.php">http://archives.postgresql.org/pgsql-admin/2002-07/msg00286.php</a> In our
particular case, we are using postgresql but this should work with any database
server that provides sequences. Our original sql was of the form:</p>
```sql
update only table1 t1
set amount = t2.price
from table2 t2
where t1.id = t2.id;
```
<p>There is of course now way of figuring out how many rows had been updated
already. The first step was to create a sequence</p>
```sql
CREATE TEMPORARY SEQUENCE seq_progress START 1;
```
<p>We can then use this sequence in the update statement to ensure that each row
updated also increments the sequence</p>
```sql
update only table1 t1
set amount = t2.price
from table2 t2
where nextval('seq_progress') != 0
and t1.id = t2.id;
```
<p>Once the query is running, you can open another connection to the database. To
get an indication of how far it has got, you can just run the following</p>
```sql
select nextval('seq_progress');
```
<p>Bear in mind that this will also increment it by 1 but if you have millions of
rows which is really the only case in which this would be useful, a few
additional increments is hardly going to make a difference.</p>
<p>Good luck and have fun!</p>]]></content:encoded></item><item><title>A Life Lived</title><link>https://icle.es/2011/10/13/a-life-lived/</link><pubDate>Thu, 13 Oct 2011 16:13:01 +0000</pubDate><guid>https://icle.es/2011/10/13/a-life-lived/</guid><description>&lt;p>I remember a time,&lt;br>
Not that long ago,&lt;br>
To be driven by misery,&lt;br>
by sorrow and pain.&lt;/p>
&lt;p>Walking a world covered in snow,&lt;br>
A cold heart cracked.&lt;/p>
&lt;p>Stayed up late&amp;hellip; and every night,&lt;br>
The lights flickering and switching,&lt;br>
Faster than I could keep up,&lt;br>
music, louder, louder and louder still.&lt;/p>
&lt;p>I can feel the bass, pumping through me,&lt;br>
Existence, but a mere memory,&lt;br>
drinking, and drinking,&lt;br>
till the memories fade.&lt;/p></description><content:encoded><![CDATA[<p>I remember a time,<br>
Not that long ago,<br>
To be driven by misery,<br>
by sorrow and pain.</p>
<p>Walking a world covered in snow,<br>
A cold heart cracked.</p>
<p>Stayed up late&hellip; and every night,<br>
The lights flickering and switching,<br>
Faster than I could keep up,<br>
music, louder, louder and louder still.</p>
<p>I can feel the bass, pumping through me,<br>
Existence, but a mere memory,<br>
drinking, and drinking,<br>
till the memories fade.</p>
<p>Walking out into the cold,<br>
the snow falling, <br>
forever falling,<br>
walking with the snow crunching,<br>
and it felt like forever.</p>
<p>Dragging on yet another cigarette,<br>
on yet another dark day,<br>
yet another grey day,<br>
The sun might be brightening up the day,<br>
but it wasn’t making a dent on mine.</p>
<p>Wandering back into the darkness,<br>
the noise screams at me,<br>
People shout and I wave,<br>
I push and trudge my way to the bar,<br>
for yet another drink, <br>
with yet another drunk.</p>
<p>I smiled, I laughed,<br>
and inside, I didn’t cry,<br>
for all that would make me cry,<br>
was being forgotten,</p>
<p>was a lifetime away,<br>
and yet never quite forgotten.</p>
<p>But in this pain,<br>
in this misery,<br>
in these grey days,<br>
I lived a life.</p>
<p>A life fuelled by drugs and passion<br>
a strong desire to run<br>
far far far away.</p>
<p>Today, now, looking back,<br>
music, louder, louder and louder still.</p>
<p>A drink in my hand,<br>
but no cigarette to my mouth.</p>
<p>Pain, misery and suffering but a memory,<br>
something I barely remember,<br>
not forgotten. A life that was.</p>
<p>All it took was a beat, a song, a lyric,<br>
and in an opening, <br>
the memories come back,<br>
flooding as if they were always there.</p>
<p>It is yet different,<br>
They are calm, <br>
no longer running.</p>
<p>And I let the music seep in<br>
and into my existence,<br>
enjoy my existence <br>
as I once myself was lost<br>
in the music, in the bass, in the beat.</p>
<p>What I once stumbled across,<br>
only while running away,<br>
I now experience,<br>
not entirely by accident,<br>
but by choice.</p>
<p>The power,<br>
the charisma,<br>
the love,<br>
the passion,<br>
the bass,<br>
the melody,<br>
the lyrics,<br>
the drugs,<br>
the passion,<br>
the love&hellip;.</p>
<p>life&hellip; :-)</p>
]]></content:encoded></item><item><title>Boundless</title><link>https://icle.es/2011/07/14/boundless/</link><pubDate>Thu, 14 Jul 2011 16:13:01 +0000</pubDate><guid>https://icle.es/2011/07/14/boundless/</guid><description>&lt;p>Swimming around gently,&lt;br>
for what seemed like an eternity,&lt;br>
in what was but a moment,&lt;br>
in her big brown eyes.&lt;/p>
&lt;p>Time, with its ever changing moods,&lt;br>
never stopping, &lt;br>
always without mercy,&lt;br>
I have myself a stay of execution.&lt;/p>
&lt;p>Swimming in this warmth,&lt;br>
this glow of love and affection,&lt;br>
I am nary a man,&lt;br>
nary a being of time or space.&lt;/p>
&lt;p>In infinity I bask, in the glory,&lt;br>
of simply being loved,&lt;br>
by a heart that knows not,&lt;br>
not what bounds are.&lt;/p></description><content:encoded><![CDATA[<p>Swimming around gently,<br>
for what seemed like an eternity,<br>
in what was but a moment,<br>
in her big brown eyes.</p>
<p>Time, with its ever changing moods,<br>
never stopping, <br>
always without mercy,<br>
I have myself a stay of execution.</p>
<p>Swimming in this warmth,<br>
this glow of love and affection,<br>
I am nary a man,<br>
nary a being of time or space.</p>
<p>In infinity I bask, in the glory,<br>
of simply being loved,<br>
by a heart that knows not,<br>
not what bounds are.</p>
]]></content:encoded></item><item><title>Thoughts</title><link>https://icle.es/2011/07/14/thoughts/</link><pubDate>Thu, 14 Jul 2011 16:13:01 +0000</pubDate><guid>https://icle.es/2011/07/14/thoughts/</guid><description>&lt;p>The thoughts that fill us daily&lt;br>
Are the stone, the rocks&lt;br>
The pavement, the roads.&lt;/p>
&lt;p>These roads we pave,&lt;br>
Sometimes with shit,&lt;br>
Sometimes with silver,&lt;br>
Perhaps with gold.&lt;/p>
&lt;p>We pave these roads&lt;br>
  With whatever we find,&lt;br>
    With whatever we can afford.&lt;br>
&lt;br>
  But nothing changes the fact&lt;br>
    The simple fact,&lt;br>
      The destination is hell.&lt;br>
&lt;br>
  You can plant trees,&lt;br>
    You can have butterflies,&lt;br>
      Deer, birds and bees.&lt;/p>
&lt;p>You can move in a car,&lt;br>
  You can walk and&lt;br>
    You can cycle there&lt;/p></description><content:encoded><![CDATA[<p>The thoughts that fill us daily<br>
Are the stone, the rocks<br>
The pavement, the roads.</p>
<p>These roads we pave,<br>
Sometimes with shit,<br>
Sometimes with silver,<br>
Perhaps with gold.</p>
<p>We pave these roads<br>
  With whatever we find,<br>
    With whatever we can afford.<br>
<br>
  But nothing changes the fact<br>
    The simple fact,<br>
      The destination is hell.<br>
<br>
  You can plant trees,<br>
    You can have butterflies,<br>
      Deer, birds and bees.</p>
<p>You can move in a car,<br>
  You can walk and<br>
    You can cycle there</p>
<p>It can be midday,<br>
  Or a moonlit night,<br>
Dusk or dawn,<br>
  It matters not.</p>
<p>The road you are on,<br>
It takes you to hell.</p>
]]></content:encoded></item><item><title>Words</title><link>https://icle.es/2011/07/14/words/</link><pubDate>Thu, 14 Jul 2011 16:13:01 +0000</pubDate><guid>https://icle.es/2011/07/14/words/</guid><description>&lt;p>There was once a time,&lt;br>
when words flowed,&lt;br>
like a river, yearning for the sea.&lt;br>
simple words conveyed great meaning.&lt;/p>
&lt;p>Words as simple as love,&lt;br>
had a power&amp;hellip;&lt;br>
a power that was indescribable.&lt;/p>
&lt;p>In the world of words, it was easy,&lt;br>
easy to get lost and forget,&lt;br>
forget the feelings,&lt;br>
the memories,&lt;br>
the thoughts,&lt;br>
that they embodied.&lt;/p>
&lt;p>I was never prepared,&lt;br>
A time when words would fail me.&lt;/p>
&lt;p>For there are some things,&lt;br>
some rare things in life,&lt;br>
that words cannot describe,&lt;br>
words have not been created for,&lt;br>
words cannot be created.&lt;/p></description><content:encoded><![CDATA[<p>There was once a time,<br>
when words flowed,<br>
like a river, yearning for the sea.<br>
simple words conveyed great meaning.</p>
<p>Words as simple as love,<br>
had a power&hellip;<br>
a power that was indescribable.</p>
<p>In the world of words, it was easy,<br>
easy to get lost and forget,<br>
forget the feelings,<br>
the memories,<br>
the thoughts,<br>
that they embodied.</p>
<p>I was never prepared,<br>
A time when words would fail me.</p>
<p>For there are some things,<br>
some rare things in life,<br>
that words cannot describe,<br>
words have not been created for,<br>
words cannot be created.</p>
<p>I would read the the dictionaries,<br>
not just of this tongue,<br>
but all that has ever been writ,<br>
to find but one word,</p>
<p>one word,<br>
to come close,<br>
to how much,<br>
just how much,<br>
I love you&hellip;</p>
]]></content:encoded></item><item><title>Register / Attach Service to JMX</title><link>https://icle.es/2011/06/21/register-attach-service-to-jmx/</link><pubDate>Tue, 21 Jun 2011 14:40:45 +0000</pubDate><guid>https://icle.es/2011/06/21/register-attach-service-to-jmx/</guid><description>&lt;p>Registering a Bean within JMX (at least in JBoss) is very straightforward. It
requires an interface with attributes (getters and setters) and operations.&lt;/p>
```java
MBeanServer server = org.jboss.mx.util.MBeanServerLocator.locateJBoss();
ObjectName objectName = new ObjectName("jboss.cache:service=TcpCacheServer");
server.registerMBean(objectToAttach, objectName);
```
&lt;p>objectToAttach is an object with a JMX'able interface.&lt;/p></description><content:encoded>&lt;p>Registering a Bean within JMX (at least in JBoss) is very straightforward. It
requires an interface with attributes (getters and setters) and operations.&lt;/p>
```java
MBeanServer server = org.jboss.mx.util.MBeanServerLocator.locateJBoss();
ObjectName objectName = new ObjectName("jboss.cache:service=TcpCacheServer");
server.registerMBean(objectToAttach, objectName);
```
&lt;p>objectToAttach is an object with a JMX'able interface.&lt;/p>
</content:encoded></item><item><title>First Love</title><link>https://icle.es/2011/06/08/first-love/</link><pubDate>Wed, 08 Jun 2011 00:13:25 +0000</pubDate><guid>https://icle.es/2011/06/08/first-love/</guid><description>&lt;p>The first phone I ever got was a Motorola Startac 70. This was back in 98 or so,
a flip phone and I still remember it ever so clearly!&lt;/p>
&lt;p>I&amp;rsquo;m pretty sure my next phone was an A1000, a pretty awesome phone. This was
around the time that Motorola started releasing the razr which I was far from
impressed with. I jumped ship to Sony Erricson and I went through a couple of
those before heading over to the wonderful world of HTC. I wanted a smart phone
to help with work and so on and I got the HTC HD (If I recall) which was a
Windows phone.&lt;/p>
&lt;p>While this make me uncomfortable, I was still more comfortable with this than a
blackberry. I couldn&amp;rsquo;t get an iPhone for some reason that I cannot remember.&lt;/p>
&lt;p>The phone was pretty laggy and I got pretty sick of it pretty quick. I then got
myself my first android phone - an HTC Hero&amp;hellip; Most people didn&amp;rsquo;t like the curvy
bit at the bottom but I didn&amp;rsquo;t care. I was happy to be on the cutting edge. It
was also a bit slow and laggy. but it was android and I loved it. It started a
brand new love affair&amp;hellip; Let me tell you more&amp;hellip;&lt;/p></description><content:encoded><![CDATA[<p>The first phone I ever got was a Motorola Startac 70. This was back in 98 or so,
a flip phone and I still remember it ever so clearly!</p>
<p>I&rsquo;m pretty sure my next phone was an A1000, a pretty awesome phone. This was
around the time that Motorola started releasing the razr which I was far from
impressed with. I jumped ship to Sony Erricson and I went through a couple of
those before heading over to the wonderful world of HTC. I wanted a smart phone
to help with work and so on and I got the HTC HD (If I recall) which was a
Windows phone.</p>
<p>While this make me uncomfortable, I was still more comfortable with this than a
blackberry. I couldn&rsquo;t get an iPhone for some reason that I cannot remember.</p>
<p>The phone was pretty laggy and I got pretty sick of it pretty quick. I then got
myself my first android phone - an HTC Hero&hellip; Most people didn&rsquo;t like the curvy
bit at the bottom but I didn&rsquo;t care. I was happy to be on the cutting edge. It
was also a bit slow and laggy. but it was android and I loved it. It started a
brand new love affair&hellip; Let me tell you more&hellip;</p>
<p>This phone got rooted and got all forms on interesting things installed on
there. I eventually wanted a phone that was faster and more responsive and opted
for the HTC Desire.</p>
<p>Another phone that I loved and it was quicker&hellip; and that was nice. I finally
had a phone that could compete with the iPhone and did a lot of the things that
I wanted it to do. It provided me with the level of flexibility that I wanted.</p>
<p>Unsurprisingly, I rooted that one too and loved it. I wanted the HTC Desire HD
when it came out but I had to wait out contract expiries and a good thing it was
too.</p>
<p>By the time I was able to get a new phone, I was sick of HTC for various
reasons. I had installed a ROM on my phone without HTC Sense and I was surprised
at just how much I just did not miss it.</p>
<p>On doing some research, I also realised that HTC had fallen behind and had no
&ldquo;superphones&rdquo; available.</p>
<p>This led me to the Samsung Galaxy S2 and the Motorola Atrix. It was a tough call
for me and for a while, I was seriously considering the S2.</p>
<p>Throwing the question around the office, the Atrix was recommended and without a
second thought as such, I went for it. Motorola deserved another shot and I
loved their high resolution screens.</p>
<p>My mum got the S2 and someone else I know had it too. I had gotten used to other
people having the same phone as me with the Desire, but I love to have something
different. The S2 seemed a little flimsy and I like the more solid feeling from
the Atrix.</p>
<p>The phone arrived today along with a keyboard, mouse, remote control and a dock.
I had a very busy day, so didn&rsquo;t really have a change to play around with the
accessories. I&rsquo;ll tell you what though - I remember why I loved Motorola in the
first place.</p>
<p>I feel sophisticated and techy with the phone. I love the fingerprint scanner.
Its a little laggy and its arguable as to whether its any quicker than punching
in the passcode but that&rsquo;s not relevant. Its bloody cool.</p>
<p>And the screen - the screen in absolutely beautiful. Better than HD.</p>
<p>It also come jam packed with some cool applications like Swype. I used to have
it a long time ago when it could be purchased.</p>
<p>After it was pulled, I switched to SlideIT which I thoroughly enjoyed. Going to
back to Swype though has been beautiful. It just looks and feels much nicer.</p>
<p>There is one complaint in that it still runs Android 2.2. instead of 2.3.
Perhaps I shall learn patience. I am not sure if I will root this one. Hopefully
I don&rsquo;t quite get bored of it as I did with the HTC&rsquo;s.</p>
<p>I like the bunch of additional stuff that comes with the Motorola phone
including the Swype. I&rsquo;m not convinced that I&rsquo;ll want to give that up.</p>
<p>I&rsquo;ll just wait patiently for the gingerbread upgrade. Until then, I&rsquo;ll just
enjoy the other ways in which the device is awfully sweeeeet!</p>]]></content:encoded></item><item><title>Lana - Acacius</title><link>https://icle.es/2011/05/11/lana-acacius/</link><pubDate>Wed, 11 May 2011 01:47:59 +0000</pubDate><guid>https://icle.es/2011/05/11/lana-acacius/</guid><description>&lt;p>There are things I haven&amp;rsquo;t told you. I wander these streets my head in the
clouds and my feet barely on the ground&amp;hellip; Rain pours heavily and my hair is
sticking to my face. My coat soaked and yet my throat as parched as the desert
at high noon.&lt;/p>
&lt;p>I stumble into a bar in the darkest of night and order a scotch - neat! The
redhead that serves me bring back faintly vivid memories of a blonde I once
knew. Her shoulder length hair draped over a bright purple low cut top. Cleavage
like mountains.&lt;/p>
&lt;p>The bar as quiet as the night herself and the lights as dim as the ones that
light my heart. I take off my coat and drape it over the bar and I can&amp;rsquo;t help
but sense the love with which the coat clings on to the bar is if so frightened
of falling - perhaps in love. Why did no one bother to tell it that it is not
the falling but that sudden stop that will kills!&lt;/p>
&lt;p>She brings the drink over and I take a wholesome sip. It burns my mouth, my
tongue with a passion I haven&amp;rsquo;t felt in recent memory. I let it envelop my whole
mouth. I can feel it burning through every part of my mouth that it can taste.&lt;/p></description><content:encoded><![CDATA[<p>There are things I haven&rsquo;t told you. I wander these streets my head in the
clouds and my feet barely on the ground&hellip; Rain pours heavily and my hair is
sticking to my face. My coat soaked and yet my throat as parched as the desert
at high noon.</p>
<p>I stumble into a bar in the darkest of night and order a scotch - neat! The
redhead that serves me bring back faintly vivid memories of a blonde I once
knew. Her shoulder length hair draped over a bright purple low cut top. Cleavage
like mountains.</p>
<p>The bar as quiet as the night herself and the lights as dim as the ones that
light my heart. I take off my coat and drape it over the bar and I can&rsquo;t help
but sense the love with which the coat clings on to the bar is if so frightened
of falling - perhaps in love. Why did no one bother to tell it that it is not
the falling but that sudden stop that will kills!</p>
<p>She brings the drink over and I take a wholesome sip. It burns my mouth, my
tongue with a passion I haven&rsquo;t felt in recent memory. I let it envelop my whole
mouth. I can feel it burning through every part of my mouth that it can taste.</p>
<p>I ask for another one as it just rolls down my throat burning everything in its
path and the rest of it is poured in my mouth.</p>
<p>She looks at me quizzically! I tap twice on the bar and say &ldquo;please!&rdquo;.. and she
trots off to pour me another one. As she walks away, I can&rsquo;t help but admire her
from behind. She reminds of a brunette I knew once, not so long ago. The vividly
faint memories flush my mind as the scotch burns my mouth and my throat with
eager anticipation.</p>
<p>I can feel the peat thunder through my palate, burning all in its path. I close
my eyes to listen to the music for the first time and it tells that the I&rsquo;ve got
to believe.. little does it know&hellip;</p>
<p>Brought back to reality by the drink being put in front of me, I decide to
savour it a bit more this time. I take a big sniff of it and I can feel it rush
up my nostrils and try to pierce through to my brain&hellip;</p>
<p>I only take a sip this time and turn around to look at the blonde that asks me
if the seat next to me is taken. I wave my arm as if to say - &ldquo;all yours!&rdquo;&hellip; As
she sits down, I realise that she reminds me of a redhead I once knew&hellip; With
curves like a dagger, brutal yet somehow alluring. Inviting me to surrender to
death.</p>
<p>She orders a scotch and I wave to the redhead and ask her to put it on my tab.</p>
<p>The blonde turns around and thanks me. I point out to her how rare it is to meet
a girl who drinks scotch.</p>
<p>I finish my scotch and order another two as I introduce myself. She tells me
that her name is Lana.  I tell her about this girl I once knew by that name&hellip; I
look into her deep green eyes, caress her face, with skin so soft and tell her
that the Lana I knew had eyes bluer than the ocean and her soul as kind as the
oceans are deep paled in comparison to how danced&hellip; in the darkest of night, in
the heaviest of rain, with the full moon the only light&hellip;</p>
<p>The next thing I know, I am being thrown up against the wall of my apartment.
The glass walls looking out into an ocean. She rips my shirt apart as if it was
made of paper and her lips finds its way around my face, my neck and my chest..</p>
<p>I push her away, which fails, so I pick her up and throw her on the couch.
Softest leather in the world. I grab the remote and put on the stereo and it
blares out screaming to the world that a seven nation army could not hold me
back. I head over the bar and pour two drinks - of scotch. She comes over and
holds me from behind, running her fingers, as soft as silk on my chest and
kissing my neck with her lips softer than her skin.</p>
<p>I turn around and hand her a drink which she promptly puts down to kiss me. I
don&rsquo;t let her - I lean back and make her work harder&hellip; I enjoy watching her get
a little frustrated&hellip; and try harder to get closer.</p>
<p>I take her hand and lead her over to the sofa and I sit down with her looking
over me. She leans over to kiss me and I let her kiss me this time, but only
briefly. I run my fingers up her soft naked legs, under her dress.</p>
<p>I run my hands softly up her inner thighs kissing her gently, like butterflies
with my fingers. She breathes in sharply as I reach round and slowly, gently, up
behind her and slowly reach into her pants. I pull them down, again, with my
fingers gently kissing her skin all the way down. She lifts up one leg after the
other as I take it off and throw it into the distance.</p>
<p>She bends on knee and places it alongside me and then the other to straddle me.
She leans forward to kiss me and I let her kiss me&hellip; little butterfly kisses to
start.</p>
<p>My hands run up her back and into her hair and down her back again. I run my
fingers on her shoulders and and across her chest - pushing her away gently to
visually absorb her stunning red dress tightly caressing her curves like blood
on a dagger&hellip;</p>
<p>I look into her eyes and lean forward to kiss her. She tries to lean back to
tease me but my arms hold her tight and there is no room for her to move&hellip;</p>
<p>My lips touch her lips and I can feel them making love, but her lips are not
enough. I kiss her lower lip, her chin and her neck as my hands take hers and
wrap it behind her.. trapped but only for a moment, before I run my fingers up
them to find her shoulders again. It seems my fingers cannot get enough of her
soft soft skin.</p>
<p>She pushes me back. Looks into my eyes, and her fingers run down to undo my belt
and I realise that as much I would love to tell you what happens next.. it will
have to wait&hellip; :-P</p>]]></content:encoded></item><item><title>The Urn - Acacius</title><link>https://icle.es/2011/05/10/the-urn-acacius/</link><pubDate>Tue, 10 May 2011 16:25:14 +0000</pubDate><guid>https://icle.es/2011/05/10/the-urn-acacius/</guid><description>&lt;p>I take a quick swig from my hipflash, put it back safely, quick sprint and I
leap off. I turn back to look at the plane and I see my two lieutenants leap out
of the airship as well saddled with parachutes. I relax into the air, and time
slows down to an absolute crawl. The wind whistles past my ears and I watch as
they release their parachutes. I take a moment and turn around to face the
ground for impact. I crash into the ground, and as I open my eyes, I see the
dust slowly settle around me. I&amp;rsquo;m surround by an army.&lt;/p>
&lt;p>I briefly look up and spot the two parachutes landing close by. I look around
and spot my target - he is amidst them all in a chariot, heading towards me. The
two parachutes land in flanking positions amidst the army. I unsheathe my sword
and can&amp;rsquo;t help but smile.&lt;/p>
&lt;p>The soldiers come at me in swathes and I cut them down. I feel my sword enter
the heart of one and the lungs of another. As I begin to feel myself get bored
of the bloodshed, I clear a circle around me. I leap on to the shoulders of the
ones closest to me and run across on the shoulders of the soldiers before they
have a moment to realise what is happening or react. I run across and when the
chariot is but a stones throw away, I leap and land on one side of the chariot.
My lieutenants are not here yet but I see that they have seen what I did and
they do the same thing. I turn around and roar.&lt;/p></description><content:encoded><![CDATA[<p>I take a quick swig from my hipflash, put it back safely, quick sprint and I
leap off. I turn back to look at the plane and I see my two lieutenants leap out
of the airship as well saddled with parachutes. I relax into the air, and time
slows down to an absolute crawl. The wind whistles past my ears and I watch as
they release their parachutes. I take a moment and turn around to face the
ground for impact. I crash into the ground, and as I open my eyes, I see the
dust slowly settle around me. I&rsquo;m surround by an army.</p>
<p>I briefly look up and spot the two parachutes landing close by. I look around
and spot my target - he is amidst them all in a chariot, heading towards me. The
two parachutes land in flanking positions amidst the army. I unsheathe my sword
and can&rsquo;t help but smile.</p>
<p>The soldiers come at me in swathes and I cut them down. I feel my sword enter
the heart of one and the lungs of another. As I begin to feel myself get bored
of the bloodshed, I clear a circle around me. I leap on to the shoulders of the
ones closest to me and run across on the shoulders of the soldiers before they
have a moment to realise what is happening or react. I run across and when the
chariot is but a stones throw away, I leap and land on one side of the chariot.
My lieutenants are not here yet but I see that they have seen what I did and
they do the same thing. I turn around and roar.</p>
<p>The soldiers around my leap up on to me and drag me to the floor. I use my sword
to block the attacks and push back hard as I leap to my feet. I fight them off
and it doesn&rsquo;t take long for my lieutenants to get to me. Once they distract the
soldiers, I use some of the dead soldiers and the shoulders of some who are
alive to help me leap on to the top of the chariot. My sword cuts through the
top of and I use my hands to rip it apart and drop inside.</p>
<p>I see the fear on his face as my swords softly touches his neck and a smile
creeps into my face. I use my other hand to reach for my hip flash and take a
sip.</p>
<p>His hands tremble as he reaches under his seat and brings out the urn. I can
hear the screaming and fighting outside. As my fingers grasp the urn, I hear the
whistling was was faint earlier rapidly get louder and turn into
a shrieking whine.</p>
<p>The next thing I know I&rsquo;m flying through the air riding a shock wave of epic
proportions. I crash into the ground and I look around and see that my
lieutenants have also landed nearby. Immortality is cool but it can be painful
at times. I pick myself up and look behind to see that most of the army is
decimated. In the air however, I spot airships - and they&rsquo;re not mine. They
glide low and drop soldiers to the ground and they amass like ants and they rush
towards us.</p>
<p>I signal to the other two and turn around to spot our airship in the distance
gliding towards us. Its bay doors opening slowly. The hot air hits me like a
wall as I leap up and grab on to the hanging door along with the other two. I
lift myself up into the bay along with the others and hit the door close button.
For an instant, I felt the g-force spike as the air ship made an abrupt stop mid
air and lifted off directly into the stratosphere.</p>
<p>I pat them both on the shoulders, grab my flash and finish it off as I head to
the cockpit&hellip;</p>]]></content:encoded></item><item><title>“Love” - Ta’oma</title><link>https://icle.es/2011/04/27/love-taoma/</link><pubDate>Wed, 27 Apr 2011 10:37:41 +0000</pubDate><guid>https://icle.es/2011/04/27/love-taoma/</guid><description>&lt;p>The say tis better to have loved and lost than never to have loved at all. Those
who say this clearly never lost, were clearly never betrayed but then, I have to
agree with them on one count though - tis better to have loved and lost.&lt;/p>
&lt;p>As I look at her picture, the flashes of memories tug at my heart and I  can
feel the love that I once felt slowly stew and burn and turn into rage and
bitter bitter revenge.. It has been a very slow process and it has been months,
but there was a lot of love to turn into bitter rage.&lt;/p>
&lt;p>I could still taste the bile in my mouth - I don&amp;rsquo;t know how Acacius manages to
keep drinking that stuff as it was water. I&amp;rsquo;ve thrown up more of that stuff
today that he drinks in an hour. It actually tastes worse coming back up than it
does going down and that is quite a feat.&lt;/p>
&lt;p>I lie back and look up at the all too familiar ceiling and I have stared at each
of those specks of dirt, those marks for so many hours, trying to figure out
what the hell happened. How did I miss it? How can she have betrayed me so?&lt;/p></description><content:encoded><![CDATA[<p>The say tis better to have loved and lost than never to have loved at all. Those
who say this clearly never lost, were clearly never betrayed but then, I have to
agree with them on one count though - tis better to have loved and lost.</p>
<p>As I look at her picture, the flashes of memories tug at my heart and I  can
feel the love that I once felt slowly stew and burn and turn into rage and
bitter bitter revenge.. It has been a very slow process and it has been months,
but there was a lot of love to turn into bitter rage.</p>
<p>I could still taste the bile in my mouth - I don&rsquo;t know how Acacius manages to
keep drinking that stuff as it was water. I&rsquo;ve thrown up more of that stuff
today that he drinks in an hour. It actually tastes worse coming back up than it
does going down and that is quite a feat.</p>
<p>I lie back and look up at the all too familiar ceiling and I have stared at each
of those specks of dirt, those marks for so many hours, trying to figure out
what the hell happened. How did I miss it? How can she have betrayed me so?</p>
<p>I turn over and reach across to the pack of cigarettes lying about - its easy to
spot it cos its bright red.</p>
<p>The only thing separating me from those smokes were these empty bottles of
various types and brands of alcohol vibrating gently from the loud reverberating
music - if you can call it that. It just screams and beats away, tugging at my
heart strings - if I still have a heart that is - a debate I&rsquo;ll leave to other
people to discuss and decide, for I simply care not, not in the slightest.</p>
<p>As I knock those bottles out of the way, I wonder how I managed to go through so
much in just the last couple of days. Fuck, this is an empty pack, oh it&rsquo;s ok,
there&rsquo;s another pack, also empty. I crumple them up and throw them into the
corner of the room, atop the pile of crumpled up empty cigarette packets.</p>
<p>Ah, here&rsquo;s a pack - it only has a few left, but I only need one&hellip;</p>
<p>I take one out, and barely drape it over my lips&hellip; The lighter, where the hell
did I put the lighter - I pat myself down and notice I had left one in my
trouser pocket. There benefits to having a number of lighters scattered all over
the place, I think to myself.</p>
<p>I take it out and light the cigarette. As I take a deep draw from the cigarette,
I feel like the smoke penetrates not just my poor little lungs struggling to
keep oxygen circulated around my body, but it is draping a huge smog around my
brain.</p>
<p>As I breathe out, I feel free, but only for a moment. My mind is clear, and all
is well with the world. but only for a moment. Before I  can put the cigarette
back on my lips, the door bleeps at me.</p>
<p>I knew this was inevitable. I wouldn&rsquo;t get to sulk down here forever. I&rsquo;m
surprised it took them this long, but hey ho, I guess it&rsquo;s time. I contemplate
dragging myself up when the door beeps again. I giggle to myself.. wimps - if
only they had balls, they would have been more insistent. They should have come
in here weeks ago and dragged me out. Just can&rsquo;t get good people these days.</p>
<p>I  manage to get myself on to a chair and door bleeps again. I wonder if I
should sit here for a few minutes and test them - will they keep beeping? will
they just come in? or will they give up and go away?</p>
<p>&ldquo;Sir!&rdquo;, I hear through the door, the voice trembling ever so slightly.. Poor
lad - he&rsquo;s trying atleast&hellip; I realise there is another way to make this fun, so
I stand myself up, walk across to the door and just before he is about to put
the button again, I open the door and scream &ldquo;What?&rdquo;</p>
<p>The boy nearly jumped out of his skin. I giggled in my head. The look of fear on
his face was so precious, so adorable. The track changed and it felt like my
heart was ripped out of for it brought a flashback with vivid details and the
searing pain of it being ripped away. I wondered why I put that fucking thing on
random going through all the tracks. That&rsquo;s just suicide and then I remembered -
that&rsquo;s exactly why.</p>
<p>I didn&rsquo;t realise this boy could be any more scared when I noticed that he was -
he was truly terrified - petrified. I realised that my facial expression must
have changed markedly when the track changed.</p>
<p>&ldquo;What is it boy?&rdquo; I howled. Trembling, he reaches into his pocket and hands me
an envelope.</p>
<p>As I open the envelope, the tracks softly notes:</p>
<p>&ldquo;Oh her eyes, her eyes<br>
Make the stars look like they&rsquo;re not shining&rdquo;\</p>
<p>In the envelope, there is a small folded piece of paper. I unfold it and it
simply says.</p>
<p>&ldquo;We found it!&rdquo;\</p>
<p>The next thing I notice is the track screaming:<br>
&ldquo;When I see your face<br>
There&rsquo;s not a thing that I would change<br>
Cause you&rsquo;re amazing<br>
Just the way you are&rdquo;\</p>]]></content:encoded></item><item><title>“Life” - Valentin.</title><link>https://icle.es/2011/04/27/life-valentin/</link><pubDate>Wed, 27 Apr 2011 09:00:47 +0000</pubDate><guid>https://icle.es/2011/04/27/life-valentin/</guid><description>&lt;p>We&amp;rsquo;re only a few minutes away from our destination. What they expect us to find
in the middle of the ocean is beyond me but that is not for me to worry about.
I&amp;rsquo;m just doing as told. No more than a boat for hire - the money is good and it
seems like a simple enough retrieval job.&lt;/p>
&lt;p>I giggled to myself at their people stumbling about, trying to keep their
stomach. They seem to have calmed down a bit over the last hour..&lt;/p>
&lt;p>As I take another bite off my sandwich, Alvin heads over and tells me that we&amp;rsquo;re
within range of the dive site. The science team were already taking measurements
and confirming assumptions. This meant that there was probably only another 10 -
15 minutes to get ready and into the bathyscaphe.&lt;/p>
&lt;p>The idea of diving to the bottom of one of the deepest surveyed points in the
oceans filled me with excitement and anxiety. It would take me a two to three
hours to get down there and it should be an uneventful descent.&lt;/p></description><content:encoded><![CDATA[<p>We&rsquo;re only a few minutes away from our destination. What they expect us to find
in the middle of the ocean is beyond me but that is not for me to worry about.
I&rsquo;m just doing as told. No more than a boat for hire - the money is good and it
seems like a simple enough retrieval job.</p>
<p>I giggled to myself at their people stumbling about, trying to keep their
stomach. They seem to have calmed down a bit over the last hour..</p>
<p>As I take another bite off my sandwich, Alvin heads over and tells me that we&rsquo;re
within range of the dive site. The science team were already taking measurements
and confirming assumptions. This meant that there was probably only another 10 -
15 minutes to get ready and into the bathyscaphe.</p>
<p>The idea of diving to the bottom of one of the deepest surveyed points in the
oceans filled me with excitement and anxiety. It would take me a two to three
hours to get down there and it should be an uneventful descent.</p>
<p>I finish the rest of the sandwich and head over to the bathyscape. The crew, nod
and wish me luck as they walk past carrying out their duties and I nod and
smile, nod and smile.</p>
<p>I climb in and the door is closed and secured from the outside. Pushing down on
the communicator button, I check with the science team before starting the
pre-dive sequence. They take a moment to reply but come back with an all-clear.</p>
<p>As I run through the checks, the floor seems to fly away as I am lifted up and
brought over the ocean and gently placed on the surface. Since so much of the
craft is transparent, it is almost as if I am floating in a bubble.</p>
<p>I let the team know that the craft is ready for descent and they confirm dive.
As I hit the release button, there is a brief dipping sensation before it
settles, and slowly, slowly sinks. I push the button to dive and it sinks
faster, and faster but still at a fairly leisurely pace.</p>
<p>As the darkness sets in, I switch on the lights and spot various fish swimming
past and I open a bar of chocolate and lean back. I knew there was a reason I
had the extra comfortable chair installed on here..</p>
<p>I consider drifting off into sleep, but only for a moment - for the beeping of
the detector kicked in. We must be less than 10km away. I make some minor
adjustments and lean back enjoying the chocolate bar.</p>
<p>A number of adjustments, a magazine, another chocolate bar and a nap or two
later and were just a few hundred metres away. As I take my eyes away from the
controls and to look at where we are going, I notice a faint glow in the
distance.</p>
<p>I switch off the lights to check that it&rsquo;s not just a reflection and sure
enough, there is something emitting light down here. This is unbelievable, we
are 10km or so under an ocean. What the hell could be emitting light?</p>
<p>As I get closer, I see that there are no specific sources of light - it is as if
the whole ocean is floor is glowing. Closer yet and I notice that it&rsquo;s the
plants and indeed some of the fish and it is quite a sight to behold. I felt a
bit as if I had accidentally wandered into a brightly lit city under the ocean.</p>
<p>There it is - in the middle of it all, in the highest point of the ocean floor,
the brightest of them all, and it&rsquo;s not a plant or a fish, it looks like a flask
of some form. The ocean life is richest and wildest and brightest near this
flask and very slowly fades into the distance.</p>
<p>I let the team know that I have spotted the artifact and that I was en-route to
retrieve it. As I get closer, I notice that the craft is slowing down. Checking
the controls and guages, I am not able to discern anything wrong with the
engines or the craft.</p>
<p>As I look around, I notice something odd - there seems to be a plant like thing
stuck on the rotors on the back of the craft. I reverse the engines and forward
again to jettison the plant. Easy enough.</p>
<p>A few seconds later, the craft gets slower again. I lean over to the engine
controls and look behind to check when to my surprise, there is nothing wrong
with the engine. Nothing in the rotors and then I see it - a creeper of some
form had gotten a hold of a bar on the outside of the craft and was slowing it
down.</p>
<p>There was nothing I could do. Oh well, if it takes a little longer - so be it.
As I turn around, i am greeted by the sight of a fish barrelling itself again
the craft. What the hell? Are they blind down here?</p>
<p>As I get closer to the artifact, I notice that there are more and more fish
heading towards me. The plants seems to moving towards me too. They are not
blind - they are protecting the artifact.</p>
<p>I let the team know and ramp up the engines to get me there quicker. This spurs
on the ocean life to attach the craft with even more gusto.</p>
<p>As I reach the artifact and engage the arm to pick up the artifact, half the
craft is covered by some form of plant life and the other side is mostly just
various forms of fish and other animals. I can barely see the arm pick up the
artifact.</p>
<p>This craft was built to survive under very high pressures but these creatures
and attacking with an aggression that I had never seen before.</p>
<p>As soon as I had the artifact, I hit the surface button and I start to surface
slowly before being dragged back down by the increased weight of the craft.</p>
<p>If that wasn&rsquo;t bad enough, alarms start to go off. Hull integrity was being
compromised - these creatures were causing drag and increasing the weight of the
craft making it difficult, if not impossible for me to surface.</p>
<p>I only had one choice left and that was to engage the emergency protocols for
rapid surfacing. It was a dangerous manoeuvre, even more so with all these
creatures attached to the craft. I had no choice. I  hesitate for a moment, then
hit the button.</p>
<p>For a moment, it seems like it had no impact, and then there was an explosion. I
saw large chunks of the plant life attached to the craft disintegrate and drift
off.</p>
<p>I smile in relief and turn around to see the largest fish I had ever seen in my
life heading towards me. I jump onto the controls and swerve wildly to avoid the
behemoth and almost narrowly averted it before it flicked its tail fin off the
front.</p>
<p>Even though the structural integrity needle was not all the way in the red, we
were still intact.</p>
<p>I leaned back into the chair and confirmed that the artifact was still secure. I
informed the team about the situation and leaned over to the controls to pick up
another chocolate bar when I spot this tiny cute little glowing fish right in
front.</p>
<p>It was matching the craft&rsquo;s ascent and and it looked like it was inspecting me.
I waved at it and it came towards me and tapped on the glass. There it was - a
tiny little crack.</p>
<p>As it swam away, I thanked the gods that this craft was built to withstand a
crack here or there. As I  lean back on my seat, I see the little guy is back
and he&rsquo;s brought friends. There are only a half a dozen or so of them, but they
surround me. In a moment of pure operatic genius, they dance, in a beautiful
co-ordinated fashion towards the glass and hit it on all sides at the same time.</p>
<p>The see the cracks on each side, and they slowly creep and start to join
together. I am surrounded entirely by cracks. Then the first one, the one that I
first saw, takes another, one final stab and I close my eyes.</p>]]></content:encoded></item><item><title>“Sleep” - Acacius</title><link>https://icle.es/2011/04/27/sleep-acacius/</link><pubDate>Wed, 27 Apr 2011 00:35:20 +0000</pubDate><guid>https://icle.es/2011/04/27/sleep-acacius/</guid><description>&lt;p>Huh? What the? Where? and as I let my eyes adjust to the lighting and look
around, I just see a note in front of me - it tells me to &amp;ldquo;Relax&amp;hellip;&amp;rdquo;..&lt;/p>
&lt;p>What? Is that all? I mean seriously.. who the hell am I and more importantly?
where the hell am i? The frustration, the rage and the panic was starting to
build up and.. &amp;ldquo;beep&amp;rdquo;, &amp;ldquo;beep beep&amp;rdquo;..&lt;/p>
&lt;p>I  turn around to where the sound was coming from and there is a terminal
beeping and flashing away&amp;hellip; I get out of the bed and make my way over.&lt;/p>
&lt;p>The screen tells to me to press any key&amp;hellip; so I do, I press the biggest easiest
key on the keyboard to press and it doesn&amp;rsquo;t do anything.. I try again - nothing!&lt;/p>
&lt;p>I can feel the frustration and rage building up again, and then I pay attention
to the keyboard and there it is a key that is marked &amp;ldquo;any&amp;rdquo;.&lt;/p>
&lt;p>You know how everyone imagines that when you die, you entire life flashes before
you&amp;hellip; Well, that is nothing compared to what happened at that very moment. I
had memories of what seemed like a million years compressed into a few seconds
get pumped into my brain..&lt;/p></description><content:encoded><![CDATA[<p>Huh? What the? Where? and as I let my eyes adjust to the lighting and look
around, I just see a note in front of me - it tells me to &ldquo;Relax&hellip;&rdquo;..</p>
<p>What? Is that all? I mean seriously.. who the hell am I and more importantly?
where the hell am i? The frustration, the rage and the panic was starting to
build up and.. &ldquo;beep&rdquo;, &ldquo;beep beep&rdquo;..</p>
<p>I  turn around to where the sound was coming from and there is a terminal
beeping and flashing away&hellip; I get out of the bed and make my way over.</p>
<p>The screen tells to me to press any key&hellip; so I do, I press the biggest easiest
key on the keyboard to press and it doesn&rsquo;t do anything.. I try again - nothing!</p>
<p>I can feel the frustration and rage building up again, and then I pay attention
to the keyboard and there it is a key that is marked &ldquo;any&rdquo;.</p>
<p>You know how everyone imagines that when you die, you entire life flashes before
you&hellip; Well, that is nothing compared to what happened at that very moment. I
had memories of what seemed like a million years compressed into a few seconds
get pumped into my brain..</p>
<p>Typical - of course it is. The trigger had to be something so inane, and of
course there were no clues - where would the fun be in THAT!</p>
<p>Even the frustration and rage was explained, as I opened the top draw of the
desk, took out the pack of cigarettes, lit one up&hellip; looked around for a bottle;
and there it is&hellip; As it burns down my throat and the smoke destroys my lungs, I
can feel the sense of utter calm overwhelm me.. ah, to experience the bliss that
is this moment and this unique moment&hellip; never to be repeated&hellip; never to be
outdone.. and there is nothing quite like the first draw&hellip;</p>
<p>Alright&hellip; back to this fragile reality and I hit the any key to be greeted with
the loud beat reverberating through what feels like each and every single cell
of my body right through to my soul&hellip; and I shall leave the trivialities of
whether I have one, whether indeed anyone has one to those who find it a
fascinating realm of study&hellip;</p>
<p>I wonder how long I have been asleep and more importantly what the weather is
like outside. I head on over to the lift, hit the button and it opens up. I get
in and the light is flashing at a negative 50. and it goes all the way up to
50.. &ldquo;Ah, I love symmetry&rdquo;, I think to myself as I push the 50. I&rsquo;m glad the 50
above ground is still there and it hasn&rsquo;t been decimated in the time that I&rsquo;ve
been asleep in here. Perhaps, I&rsquo;ve only been asleep for a few days or months -
who knows?</p>
<p>The lift rockets to the top and it opens on to the penthouse suite and it&rsquo;s well
maintained - just the way I like it. I make a mental note to thank whoever has
taken over the maintenance duties.</p>
<p>I head over to the coffee table, pick up the remote and push the button for the
curtains which promptly opens, all the way to reveal the glass walls that is now
looking onto a landscape that is as white as the clouds or a brides gown and oh
so beautiful. My heart skips a beat as I draw on the cigarette.. You know - I
don&rsquo;t care about the reason for being woken - this view is reason enough; and I
have to take a moment to admire the view. I look down on all the landscape, all
the snow, all that white - &ldquo;is it Christmas?&rdquo; I wonder.. nah - it can&rsquo;t be -
there would be presents and tree in here if it was..</p>
<p>I glance across to the calendar on the wall and its only November&hellip; The 30th,
but nevertheless, only November.</p>
<p>As I wander back to the lift, a thought wandered through my mind that the reason
for me being woken should be investigated. I hit zero in the lift and let that
thought wander along. It waited all these years; it can wait a few more hours.</p>
<p>The lift plummets to the ground floor and once it lets me out, I head to the
garage. It is always such a touch choice as to what I want to take out on such a
beautiful day. As I walk past all the cars and the bikes, my mind reels
nostalgically to the memories in each of them, but only for a moment for I spot
the perfect one for this occasion. The dark horse. yes&hellip; on a day of snow, a
day when everything is white, the dark horse is the prefect companion to go out
into the world with.</p>
<p>I walk over. It is jet black with beautiful sleep chrome lines. I hit the button
to open the doors, and as all the others disappear into the ground and the doors
open, the chilly air envelops me and I  don the leathers.</p>
<p>I take one last draw from the cigarette and flick it into the distance. I put on
the helmet and giggle a little, in victory, as the music gets transferred from
the room to my helmet and admire my own technical ingenuity as I get onto the
bike.</p>
<p>As I start it I can feel it purring and I  can&rsquo;t help but smile. I rev the bike
just to hear it purr, to feel it purr. I can feel the cold air caressing me as I
blaze out of the garage and slide on to the road and speed away.</p>
<p>The world hasn&rsquo;t changed much. There are still roads, although there isn&rsquo;t any
traffic - hey, all the more road for me&hellip; I accelerate and I watch the speed
climb rapidly past a 100 when the track in my head is interrupted with ringing..
&ldquo;ring&hellip; ring ring&hellip; ring.. ring ring..&rdquo;</p>
<p>I guess it was inevitable&hellip; so I hit the answer button and ask.. &ldquo;what up?&rdquo;&hellip;</p>]]></content:encoded></item><item><title>"Vengeance" - Agent Maccabee</title><link>https://icle.es/2011/04/27/vengeance-agent-maccabee/</link><pubDate>Wed, 27 Apr 2011 00:32:06 +0000</pubDate><guid>https://icle.es/2011/04/27/vengeance-agent-maccabee/</guid><description>&lt;p>I sip my coffee as Frank fills me in on the current situation while the lift
zooms up to the 23rd Floor. After the fatalities the last time we identified a
base of the enigmatic organisation that we only know as A, we are taking things
a little slower, bit more carefully.&lt;/p>
&lt;p>First things first - &amp;ldquo;is it confirmed?&amp;rdquo; I ask him. &amp;ldquo;Yup, source checks out, as
does satellite images and local intelligence&amp;rdquo;. I have an overwhelming desire to
order a chopper, fly over with a few teams and just blow the place up. I lost 15
people to them last time - eye for an eye, but that&amp;rsquo;s not what we are about. We
are the good guys - right?&lt;/p>
&lt;p>As we go our separate ways and I head to my office, I ask frank to prepare a
briefing for the team in 15 minutes. I start to imagine the ways in which we
could get payback. I wonder if I want to kill the people in there slowly or
whether a high yield detonation would be more appropriate.&lt;/p>
&lt;p>I log into the computer and quickly check through the emails, nothing
interesting - no voicemails. I have another five minutes and imagine how nice it
would be have a cigarette but I quit a few months ago. Darn those health
kicks&amp;hellip;&lt;/p></description><content:encoded><![CDATA[<p>I sip my coffee as Frank fills me in on the current situation while the lift
zooms up to the 23rd Floor. After the fatalities the last time we identified a
base of the enigmatic organisation that we only know as A, we are taking things
a little slower, bit more carefully.</p>
<p>First things first - &ldquo;is it confirmed?&rdquo; I ask him. &ldquo;Yup, source checks out, as
does satellite images and local intelligence&rdquo;. I have an overwhelming desire to
order a chopper, fly over with a few teams and just blow the place up. I lost 15
people to them last time - eye for an eye, but that&rsquo;s not what we are about. We
are the good guys - right?</p>
<p>As we go our separate ways and I head to my office, I ask frank to prepare a
briefing for the team in 15 minutes. I start to imagine the ways in which we
could get payback. I wonder if I want to kill the people in there slowly or
whether a high yield detonation would be more appropriate.</p>
<p>I log into the computer and quickly check through the emails, nothing
interesting - no voicemails. I have another five minutes and imagine how nice it
would be have a cigarette but I quit a few months ago. Darn those health
kicks&hellip;</p>
<p>I sit back, pop my feet on the desk and just sip on my coffee. I wonder if I&rsquo;ll
be able to get my hands on a sniper rifle and find a location high enough - if
the building catches fire, they would all have to evacuate. I could then pick
them off one by one. I wonder if they would panic.</p>
<p>Frank knocks once, open the door and tells me that they are ready. I walk over
and can&rsquo;t stop thinking about more interesting ways to kill these people. Where
could we place the C4 charges, all around the building?</p>
<p>Frank fills us all in on the intel that we have on the base called &ldquo;Glaucia&rdquo; -
who picks these names I wonder? As the deeper recesses of my mind imagine
watching as a napalm bomb is dropped on the base, we are told that the base is
surrounded by acres of woods, lakes and plains. &ldquo;Well, that removes the option
to sniper&rdquo;, I think to myself. The boundary of the base is marked by a tall
electrical fence that detects intrusion and reports it. It will take roughly
five minutes for drive from the gate to the base. It is likely that road is
booby trapped with explosives although we haven&rsquo;t been hit by that before.</p>
<p>The satellite imagery shows a few hundred people in the base so it certainly
looks in use. We&rsquo;ll need to manage a strike that is lightning fast to avoid
alerting them to our presence since we want to get in quickly so they don&rsquo;t have
an opportunity to destroy all the data as they have done every time we hit them
before.</p>
<p>Frank then tells me something that I didn&rsquo;t expect, and it makes me smile. &ldquo;We
have an in this time&hellip;, The west wing of the building is in disrepair and they
have contracted a local company to make the repairs. We have intercepted them
and will take their place.&rdquo;</p>
<p>There is a problem with the plan. It has to be a smaller team, about twelve
people in total. I interrupt and tell him that there will be thirteen - I am
going! Frank starts to object and I make it clear that it was not up for debate.</p>
<p>I tune out as Frank goes through the rest of the plan&hellip;</p>
<p>I am in the lead car, Frank is driving. There are three cars in total, two with
four people, and mine with five. As we drive the quiet streets, I take a moment
to enjoy the snow and imagine it covered in blood spatter&hellip; Not long now.</p>
<p>As we approach the gate, Frank lowers the window and shows his fake id to open
the gate. This is the first time that we have entered one of the bases without
alerting them. This is going to be a success for us. The first one. I can feel
my skin tingling with anticipation. I pick up a cigarette from Frank&rsquo;s pack and
light it up - screw quitting. Life is too short! As I take the first draw, I
feel the nicotine pulsing through and it calms me down. I forcibly ignore the
guilt and just push it to one side. I&rsquo;ll deal with it later.</p>
<p>Its hard to ignore the beauty of this place and even harder to imagine the snow
covered in blood spatter - it somehow feels wrong - just plain wrong. I want to
turn around and leave - I don&rsquo;t want a part of destroying such beauty and we
keep moving.</p>
<p>I can see the building now, like a fairytale, majestic and so beautiful. It
towers so high that it makes me feel like an ant and it looks so old that I have
to wonder how they would have taken that heavy stone all the way up to the top.
I  wonder how much blood was spilt in making this beautiful and powerful
structure. I wonder how much C4 it would take to blow it into the ground -
probably a lot more than we have.  In front of the building, there is a statue,
which looks to be very old but well kept.</p>
<p>The car comes to a stop behind the statue and I feel the cold air caress my skin
softly and the snow crunches under my feet. The cold air fills my lungs with
enthusiasm. I take a moment and close the door. I close my eyes while filling my
lungs slowly drawing on the cigarette as the rest of me fills up on the cold,
crisp beauty and serenity of the place. By the time I open my eyes the teams
have already scouted the perimeter and come back to report.</p>
<p>I take a moment examining the statue, it is difficult to tell if it is merely
hundreds of years old or thousands but a lot of care has gone into maintaining
its glory. It starts to snow softly  as I look up to see the face of the statue,
a man with a look of power and serenity and I can sense a feeling of deep peace,
but only for a moment as it is interrupted by quiet radio chatter.</p>
<p>There are no other entry points. The windows are inaccessible and there are no
other doors. The main door is the only way in. I notice that there is music
coming from the building. I can&rsquo;t make it out yet - I wonder why they might be
playing music, and so loudly.</p>
<p>Two of the teams line up either side of the door. My whole body is tingling, I
can feel my heart beating, I concentrate on my breathing to help me focus and
relax. They count down silently from three, open the door and enter, I can hear
the radio repeating &ldquo;clear&rdquo; which the music in the background. I was expecting
gunfire but heard none. I go in with the two other teams coming in behind me and
the music is louder. The rooms to each side are cleared and we enter a hall
which must be the center of the building and it is massive. There are murals
over the walls and the ceilings, of gods, angels, demons, war, peace, beauty,
everything. I keep focussed. In the center of the room, there is a display
panel.</p>
<p>The music is coming from here. The size and the acoustics of the room makes the
sound so imposing and majestic. It feels like we should all kneel on the floor
and submit to the music but we keep moving, spread through the room. I hear the
door close and click behind me. I see a couple of the team try to get it open
again with no luck.</p>
<p>As I walk over the panel in the center of the room, I look up and spot someone
standing on a balcony looking down. He hasn&rsquo;t spotted me. I can get him and I 
have two choices - shoot him or trace him. As he looks over to spot me, I grab
my tracer pistol as quickly as I can, aim and fire. He spots me, smiles, walks
to wall, opens it and walks out.</p>
<p>I radio central to pick up the trace as I  spot what it says on the panel - its
counting down from 5. By the time I realise what is happening, its already at
three. Fuck! If im going down, I&rsquo;m taking at least that one bastard with me! I
radio central to fire the rockets - they ask for confirmation - and I confirm.
After 1, it shows a smiley face and in that moment, I felt time slow down,
almost stop. I saw my life. The crush on Lisa in school, joining the force, my
wedding, the birth of my son, the divorce, the sadness on my sons face when he
asked me not to go to work today, the way he wrapped his whole hand around my
little finger the day he was born and I can&rsquo;t help but smile.</p>]]></content:encoded></item><item><title>Good Intentions</title><link>https://icle.es/2011/04/27/good-intentions/</link><pubDate>Wed, 27 Apr 2011 00:11:22 +0000</pubDate><guid>https://icle.es/2011/04/27/good-intentions/</guid><description>&lt;p>And I walk down this path,&lt;br>
Paved with gold and silver,&lt;br>
And the colours of the rainbow,&lt;br>
Paved with nothing but good,&lt;br>
No, the best... of intentions...&lt;/p>
&lt;p>Right into the heart of hell.&lt;/p>
&lt;p>It takes time to walk this path,&lt;br>
Patience, they say is a virtue,&lt;br>
But one day, I will get there...&lt;/p>
&lt;p>I watch the butterflies,&lt;br>
The red roses,&lt;br>
And the chrysanthemums,&lt;br>
As they lean towards me,&lt;br>
As if to try and get away,&lt;br>
From the gates of hell...&lt;/p></description><content:encoded><![CDATA[<p>And I walk down this path,<br>
Paved with gold and silver,<br>
And the colours of the rainbow,<br>
Paved with nothing but good,<br>
No, the best... of intentions...</p>
<p>Right into the heart of hell.</p>
<p>It takes time to walk this path,<br>
Patience, they say is a virtue,<br>
But one day, I will get there...</p>
<p>I watch the butterflies,<br>
The red roses,<br>
And the chrysanthemums,<br>
As they lean towards me,<br>
As if to try and get away,<br>
From the gates of hell...</p>
<p>With each step, as I get closer,<br>
I taste the air get more arid,<br>
Just a little, only a little,<br>
Barely noticeable...</p>
<p>And I watch the butterflies<br>
As beautiful as a rainbow,<br>
Glimmering ever so bright,<br>
As they turn into moths,<br>
As dark as the night sky,<br>
Not even a sliver of light,<br>
For there is no moon.</p>
<p>The flowers,<br>
With the undeniable,<br>
Their unforgettable fragrance,<br>
Swaying softly in the breeze,<br>
Inviting me into a world,<br>
A world of joy and fun,<br>
As they turn into death,<br>
Into the dark branches,<br>
That once held the beauty of all life...</p>
<p>The gate ever so wide open,<br>
One does wonder, but quietly,<br>
Why there are gates at all...</p>
<p>Walking through, I feel the heat,<br>
It burns my skin, but slowly,<br>
I feel myself burn away,<br>
Slowly and with passion...</p>
<p>Sparks fly enthusiastically,<br>
Playing with each other,<br>
like lovers on a moonlit night.</p>
<p>When all is done and said,<br>
I see that my heart is still beating,<br>
The fires licking it, kissing it tenderly,<br>
Coaxing it, ever so quietly and gently.</p>
<p>Join us, in this eternal fire,<br>
This fire of infinity,<br>
Burn, and for ever,<br>
Be for ever a part of this.</p>
<p>But my heart denies the fire,<br>
An eternity, an instant,<br>
A drop of blood,</p>
<p>A small drop of blood,<br>
floats down into the fire.</p>
]]></content:encoded></item><item><title>Dreams</title><link>https://icle.es/2011/04/26/dreams/</link><pubDate>Tue, 26 Apr 2011 01:38:16 +0000</pubDate><guid>https://icle.es/2011/04/26/dreams/</guid><description>&lt;p>For I once did fall,&lt;br>
For an angel of darkness and light,&lt;br>
As my knees touched rock bottom,&lt;br>
With the  heavy burden of this love I felt..&lt;/p>
&lt;p>I realise that she does not love me,&lt;br>
For what she felt me for me,&lt;br>
There is not a word,&lt;br>
Not a phrase, a paragraph or a book.&lt;/p>
&lt;p>Even all the words ever written,&lt;br>
All the words ever spoken or thought,&lt;br>
Yet could not describe that...&lt;/p></description><content:encoded><![CDATA[<p>For I once did fall,<br>
For an angel of darkness and light,<br>
As my knees touched rock bottom,<br>
With the  heavy burden of this love I felt..</p>
<p>I realise that she does not love me,<br>
For what she felt me for me,<br>
There is not a word,<br>
Not a phrase, a paragraph or a book.</p>
<p>Even all the words ever written,<br>
All the words ever spoken or thought,<br>
Yet could not describe that...</p>
<p>That undeniable, irrevocable feeling,<br>
That fundamental fibre of her being,<br>
Indescribable to any mortal,<br>
Perhaps even the immortals...</p>
<p>My knees feel the rocks underneath,<br>
And as I look up, I see her,<br>
In all the glory that my mortal eyes can behold...</p>
<p>Her wings spread out,<br>
In all their multicoloured glory...</p>
<p>Her bare feet, with her golden skin,<br>
Glistening in the moonlight...</p>
<p>Her soft golden curls,<br>
Draped over her kind  face,<br>
Glistening in the glory of the sun...</p>
<p>You might know her as life,<br>
Others describe her as fate,<br>
Others yet might know her as destiny...</p>
<p>I did, but only once hear,<br>
That she was described,<br>
As nothing less than the universe...</p>
<p>Walking on this earth,<br>
On the same streets each day,<br>
Past the same buildings,<br>
The same stores,<br>
And the same concrete sidewalks...</p>
<p>The same as I walked on yesterday,<br>
And the day before...</p>
<p>I straighten my back,<br>
And I look up at the sky...</p>
<p>And I feel the tingling sensation,<br>
Just very faintly on my back...</p>
<p>As it grows and sprouts,<br>
Like a tree that has been watered and fed,<br>
Not just for a day or three but decades and centuries...</p>
<p>I feel the wings on my back,<br>
In all its multi-coloured glory...</p>
<p>In a moment of clarity, I realise,<br>
To fly, all one has to do is dream...</p>
<p>And then simply reach for them...</p>
]]></content:encoded></item><item><title>The Circle Of Life</title><link>https://icle.es/2011/04/25/the-circle-of-life/</link><pubDate>Mon, 25 Apr 2011 17:23:43 +0000</pubDate><guid>https://icle.es/2011/04/25/the-circle-of-life/</guid><description>&lt;p>The circle of life,&lt;br>
starting at zero,&lt;br>
and all the way to infinity,&lt;br>
to find that you are merely back,&lt;br>
right at the beginning,&lt;br>
at zero.&lt;/p>
&lt;p>For I am nothing,&lt;br>
Yet, I find that I am,&lt;br>
I am everything.&lt;/p>
&lt;p>In your soft hands,&lt;br>
I am putty,&lt;br>
Malleable, changeable, to be moulded,&lt;br>
I could be everything,&lt;br>
and I can be nothing.&lt;/p>
&lt;p>Bend me,&lt;br>
Break me,&lt;br>
Shape me.&lt;/p>
&lt;p>Breathe life into me,&lt;br>
With but a simple kiss.&lt;/p></description><content:encoded><![CDATA[<p>The circle of life,<br>
starting at zero,<br>
and all the way to infinity,<br>
to find that you are merely back,<br>
right at the beginning,<br>
at zero.</p>
<p>For I am nothing,<br>
Yet, I find that I am,<br>
I am everything.</p>
<p>In your soft hands,<br>
I am putty,<br>
Malleable, changeable, to be moulded,<br>
I could be everything,<br>
and I can be nothing.</p>
<p>Bend me,<br>
Break me,<br>
Shape me.</p>
<p>Breathe life into me,<br>
With but a simple kiss.</p>
<p>And I shall be yours,<br>
Not just for now,<br>
or this life,</p>
<p>For all life,<br>
All lifetimes,<br>
For eternity.</p>
]]></content:encoded></item><item><title>Java Object Size In Memory</title><link>https://icle.es/2011/04/25/java-object-size-in-memory/</link><pubDate>Mon, 25 Apr 2011 15:58:00 +0000</pubDate><guid>https://icle.es/2011/04/25/java-object-size-in-memory/</guid><description>&lt;p>Anyone who has worked with java in a high end application will be well aware of
the double edged sword that is java garbage collection. When it works - it is
awesome but when it doesn&amp;rsquo;t - it is an absolute nightmare. We work on a
ticketing system where it is imperative that the system is as near real-time as
possible. The biggest issue that we have found is the running of memory in the
JVM which causes a stop the world garbage collection. This then results in
cluster failures since an individual node is inaccessible for long enough that
it is kicked out of the cluster.&lt;/p>
&lt;p>There are various ways to combat this issue and the first instinct would be
suggest that there is a memory leak. After eliminating this as a possibility,
the next challenge was to identify where the memory was being taken up. This
took some time and effort and the hibernate second level cache was identified.
We were storing far too much in the second level cache.&lt;/p>
&lt;p>This is another double edged sword. The hibernate second level cache is
absolutely imperative to a high performance system. It does however, come with a
price. The cache needs to be managed carefully to ensure that balance between
performance and memory requirements.&lt;/p></description><content:encoded><![CDATA[<p>Anyone who has worked with java in a high end application will be well aware of
the double edged sword that is java garbage collection. When it works - it is
awesome but when it doesn&rsquo;t - it is an absolute nightmare. We work on a
ticketing system where it is imperative that the system is as near real-time as
possible. The biggest issue that we have found is the running of memory in the
JVM which causes a stop the world garbage collection. This then results in
cluster failures since an individual node is inaccessible for long enough that
it is kicked out of the cluster.</p>
<p>There are various ways to combat this issue and the first instinct would be
suggest that there is a memory leak. After eliminating this as a possibility,
the next challenge was to identify where the memory was being taken up. This
took some time and effort and the hibernate second level cache was identified.
We were storing far too much in the second level cache.</p>
<p>This is another double edged sword. The hibernate second level cache is
absolutely imperative to a high performance system. It does however, come with a
price. The cache needs to be managed carefully to ensure that balance between
performance and memory requirements.</p>
<p>To this end, it was important to be able to identify what was taking up all the
memory in the cache. Each object might only take a couple of hundred bytes, but
with our second level cache set to store hundreds of thousands of items, this
quickly takes up hundreds of megabytes. With the metadata of the cache, this
could easily hike it up near a gigabyte of memory usage. This gets substantially
worse with cache evictions and the adding of new items into the cache.</p>
<p>The correct way to resolve this is to identify specific object types that
&ldquo;overload&rdquo; the cache. i.e. items that have an large number of instances stored
in the cache. Identifying classes that store a large number of items is easy
enough - we just traverse the cache and count up the number of items. However,
there might be a class that stores a smaller number of items but take a sizeable
amount of memory. For this reason, it is important to understand the object
sizes in memory as well.</p>
<p>If you have ever tried to find a way to identify object sizes, you will know
that this is no easy task. You can calculate to some degree of accuracy the size
of an object based on the data it stores but this is a manual process.</p>
<p>The only real way to get this information is to use a java agent and use that to
calculate a more accurate memory usage. For this purpose, we used the
<a href="http://www.javamex.com/classmexer/" title="ClassMexer Java Profiling Agent">classmexer agent</a>
which requires a simple installation step of adding the following parameter to
java <code>-javaagent:classmexer.jar</code>. You can then figure out the memory utilisation
of an object by calling</p>
```java
MemoryUtil.deepMemoryUsageOf(objectInstance)
```
<p>You can also pass in a collection of objects:</p>
```java
MemoryUtil.deepMemoryUsageOfAll(objectInstanceCollection)
```
<p>This was the simple part.</p>
<p>Traversing the node structure of jboss cache and collating a collection
statistics with regards to the number of each type of object and its memory
utilisation was a little more interesting.</p>
<p>I will cover this separately</p>]]></content:encoded></item><item><title>While i was sleeping</title><link>https://icle.es/2011/04/11/while-i-was-sleeping/</link><pubDate>Mon, 11 Apr 2011 20:44:44 +0000</pubDate><guid>https://icle.es/2011/04/11/while-i-was-sleeping/</guid><description>&lt;p>As I draw in this breath&lt;br>
I think to myself&lt;br>
By my next breath,&lt;br>
I shall love you&lt;br>
Not any more&lt;br>
For ever more.&lt;/p>
&lt;p>As I see the sun set,&lt;br>
I think to myself&lt;br>
By the time the sun rises,&lt;br>
I shall love you,&lt;br>
Not any more&lt;br>
For ever more.&lt;/p>
&lt;p>As I see the sun rise,&lt;br>
I think to myself,&lt;br>
By the time the sun sets,&lt;br>
I shall love you,&lt;br>
Not any more,&lt;br>
For ever more.&lt;/p></description><content:encoded><![CDATA[<p>As I draw in this breath<br>
I think to myself<br>
By my next breath,<br>
I shall love you<br>
Not any more<br>
For ever more.</p>
<p>As I see the sun set,<br>
I think to myself<br>
By the time the sun rises,<br>
I shall love you,<br>
Not any more<br>
For ever more.</p>
<p>As I see the sun rise,<br>
I think to myself,<br>
By the time the sun sets,<br>
I shall love you,<br>
Not any more,<br>
For ever more.</p>
<p>One more week,<br>
I think to myself,<br>
When this week is over,<br>
I shall love you,<br>
Not any more,<br>
For ever more.</p>
<p>One more month,<br>
I think to myself,<br>
When this month is over,<br>
I shall love you,<br>
Not any more,<br>
For ever more,</p>
<p>Just a year,<br>
I think to myself,<br>
By next year,<br>
I shall love you,<br>
Not any more,<br>
For ever more,</p>
<p>And now I lay,<br>
My life spent,<br>
In trying<br>
Not to love you,<br>
As I draw my last breath,<br>
I think to myself,<br>
When I am born again,<br>
I shall love you,<br>
Not any more,<br>
For ever more.</p>
<p>As I draw my first breath<br>
My first breath of new life,<br>
I think to myself,<br>
I shall love you,<br>
For ever more.</p>
]]></content:encoded></item><item><title>Flying</title><link>https://icle.es/2011/04/10/flying/</link><pubDate>Sun, 10 Apr 2011 13:04:50 +0000</pubDate><guid>https://icle.es/2011/04/10/flying/</guid><description>&lt;p>As you hold me tight&lt;br>
Ever so tight&lt;br>
That I can&amp;rsquo;t breathe&lt;br>
I feel like a bird&lt;br>
Flying, soaring, &lt;br>
Ever so high.&lt;/p>
&lt;p>Icarus, such a fool,&lt;br>
With his wings&lt;br>
Of wax and feathers&lt;/p>
&lt;p>Close to the sun,&lt;br>
He could not go&lt;/p>
&lt;p>For he did not fly &lt;br>
Simply as I do,&lt;/p>
&lt;p>Soar, as I do,&lt;/p>
&lt;p>Ever higher I go,&lt;br>
Closer to the sun,&lt;/p>
&lt;p>And all because you love me!&lt;/p></description><content:encoded><![CDATA[<p>As you hold me tight<br>
Ever so tight<br>
That I can&rsquo;t breathe<br>
I feel like a bird<br>
Flying, soaring, <br>
Ever so high.</p>
<p>Icarus, such a fool,<br>
With his wings<br>
Of wax and feathers</p>
<p>Close to the sun,<br>
He could not go</p>
<p>For he did not fly <br>
Simply as I do,</p>
<p>Soar, as I do,</p>
<p>Ever higher I go,<br>
Closer to the sun,</p>
<p>And all because you love me!</p>
]]></content:encoded></item><item><title>"Escape" - Ta'oma'</title><link>https://icle.es/2011/04/10/escape-taoma/</link><pubDate>Sun, 10 Apr 2011 12:38:11 +0000</pubDate><guid>https://icle.es/2011/04/10/escape-taoma/</guid><description>&lt;p>The cliff is steep and the lands below are bustling in this early spring
afternoon. Soon, very soon, I shall rule this world, and with it the universe.
It has been over two hundred years since we were woken by Acacius, but  there is
still no sign of him Without him, the armies of Aelia will fall and the children
of light will be corrupted with the blood of the innocent.&lt;/p>
&lt;p>I can taste the iron in the blood of victory in the cool air. My armies are
committed and it won&amp;rsquo;t be long before the final battle starts.&lt;/p></description><content:encoded><![CDATA[<p>The cliff is steep and the lands below are bustling in this early spring
afternoon. Soon, very soon, I shall rule this world, and with it the universe.
It has been over two hundred years since we were woken by Acacius, but  there is
still no sign of him Without him, the armies of Aelia will fall and the children
of light will be corrupted with the blood of the innocent.</p>
<p>I can taste the iron in the blood of victory in the cool air. My armies are
committed and it won&rsquo;t be long before the final battle starts.</p>
<hr>
<p>After I awoke, I realised that the world had changed in the two thousand years
that I was asleep. Humanity had kept itself busy fighting between themselves -
we were never even missed. If only we weren&rsquo;t restricted to meagre fractions of
our armies if we are to fight on this world or they could have witnessed a real
war.</p>
<p>My awakening meant that the other nine was also awake. The only wildcard was
Acacius and his complete absence meant that this was, for the first time in all
eternity truly between the armies of the dark and the armies of the light. I
knew that to achieve victory, I needed allies.</p>
<p>More importantly, I needed a strong human presence since this was would be
fought on all fronts. I sent my team of four to the four corners of the world to
build an empire, to make allies and be ready for my call. This works as a good
deterring tactic, particularly since Aelia and her armies will be reluctant to
spill the blood of the innocent.</p>
<p>It doesn&rsquo;t matter how powerful my army is if I don&rsquo;t know where the enemy is.
This was for me to tackle.</p>
<p>It took a lot of hunting and a few years ago, I discovered the location of one
of their offices. The head of the corporation, Lucia reports directly to Aelia.
She was my target, and I got to know her. We fell in love and together, we will
rule all that is, and all that ever will be&hellip;</p>
<hr>
<p>As I turn around, I can see Lucia&rsquo;s airship in the distance dropping her off. I
changed the plans to move the final battle a few weeks earlier and she is not
going to like that. Our preparations finished ahead of schedule and there is no
point in waiting.</p>
<p>I don&rsquo;t like having to walk all the way over there but there is far too much at
stake and it is not worth the risk of being spotted by anyone. The ground starts
to turn muddy as rain starts to pour down, as if sad to see the world as it is
come to an end. I can feel the rain dripping down my face and I can see her now,
her long black hair clinging to her face and her black coat. She is walking
slowly towards me. I smile. She smiles back, but its different. I haven&rsquo;t seen
this smile before.</p>
<p>She knows something that I do not&hellip; not yet and I can feel my gut twisting - I
am not going to like what is to come.</p>
<p>The clouds clear, the rain stops and sun&rsquo;s brightness streams through with a
vengeance. She takes her coat off to reveal white, pure pure white silk clinging
close to her soft skin. Her hair changes, the water in her hair glistens, for a
moment, and slowly changes to gold, dry, and flowing the soft the soft breeze.</p>
<p>The irony of the betrayal made me smile. This was Aelia herself. Now, we are
both equal, just on opposite sides, so I know that she would not be here unless
her armies were nearby. I know that her lieutenants are engaged elsewhere
through my other sources.</p>
<p>She kneels on one knee and picks up the sand, which is now dry and throws it
into the air. From it springs beautiful creatures of gold and platinum,
thousands of them. in the blink of an eye, there was an army.</p>
<p>The beauty of it brought a tear to my eye as it always did and I took my stand.
She smiled a confident smile and I laughed. She mistook it for arrogance and her
smile widened in preparation for victory.</p>
<p>Just as they prepared to attack, there was a loud boom through the sky. It was
one of my transports, zooming along at mach something or other. Too fast to pick
me up but not to drop something off. Not something, but two of my lieutenants.
Before Aelia and her army recovered from the surprise, the two lieutenants had
already thrown their daggers at her. The three of us are charging towards them
all.</p>
<p>I can see the two daggers join together in front of me to form a sword and going
right for Aelia. I&rsquo;m glad there are rules against using &ldquo;modern&rdquo; weapons -
swordplay is just more fun. The satisfaction you get from plunging it into a
body is unparalleled - where is the joy in squeezing a trigger?</p>
<p>Before the sword finds its intended target, one of her soldiers get in front On
impact, it turns to dust, but before the sword has a chance to fall to the
ground, my hand is around the hilt. She has her sword out as well. To either
side, I can see my lieutenants fighting through hordes of the creatures.</p>
<p>As I raise my sword to strike, I can feel the rage, the anger and the desire for
destruction boiling to the surface. I want to slowly place this sword into her
heart. Just as I was about to bring the sword down and strike, I hear something
metallic drop down at my feet. It beeps and sends out a powerful shockwave
throwing us all apart. It takes me a second to recover and I hear the noise of a
car - who still drives those?</p>
<p>It screams through the space between the three of us and the army. It spins
around to separate the army from us. I can see from his manner, the cigarette in
his hand, the hipflask on the passenger seat, and the music blaring loudly that
its Acacius. He gestures for us to pop in and without hesitation, we jump in. He
turns around and speeds to the cliff&hellip;</p>
<p>The army of Aelia following behind. Before I can ask him what the hell he was
doing, we are flying off the edge of the cliff. Just as I wonder if I should
have just stayed and fought, I see an airship - its docking bays are open. We
land, race to the end and come to a stop. He switches the car off and after I
take a moment to catch my breath, ask him why he still drives around in one of
these. &ldquo;Meh, I still like them&rdquo;, he tells me.</p>]]></content:encoded></item><item><title>"Bzzzzz" - Acacius</title><link>https://icle.es/2011/04/10/bzzzzz-acacius/</link><pubDate>Sun, 10 Apr 2011 12:32:08 +0000</pubDate><guid>https://icle.es/2011/04/10/bzzzzz-acacius/</guid><description>&lt;p>Ah, of course, now is the perfect time for the phone to ring. My tongue is in
her mouth, my right hand on her bra buckle, a step away from unclasping it, my
left hand is in her hair. She is sitting on my lap with her hands frolicking
through my hair. The curtains are open, its snowing heavily outside.&lt;/p>
&lt;p>We&amp;rsquo;ve both had some nice red wine, the music is perfect, it is tugging at my
heart strings, this is perfect, romantic with a hint of filth, the perfect time
for my phone to buzzzzz. I know that I have a few seconds before the ringtone
kicks in. My right hand changes it mind, moves over to the phone and pushes
ignore. At least it doesn&amp;rsquo;t call for anything more than one arm.&lt;/p>
&lt;p>My right arm makes it way to her back. I like the way her tongue feels,
particularly the stud she has in there&amp;hellip; not sure exactly what word to use to
describe it. My right hands feels along the bra strap onto the buckle again&amp;hellip;&lt;/p>
&lt;p>bzzzzzzttt. Perfect! its important, but thats ok, its not urgent. I wonder what
it could be, for a moment, as my right arm repeats the ignore.&lt;/p></description><content:encoded><![CDATA[<p>Ah, of course, now is the perfect time for the phone to ring. My tongue is in
her mouth, my right hand on her bra buckle, a step away from unclasping it, my
left hand is in her hair. She is sitting on my lap with her hands frolicking
through my hair. The curtains are open, its snowing heavily outside.</p>
<p>We&rsquo;ve both had some nice red wine, the music is perfect, it is tugging at my
heart strings, this is perfect, romantic with a hint of filth, the perfect time
for my phone to buzzzzz. I know that I have a few seconds before the ringtone
kicks in. My right hand changes it mind, moves over to the phone and pushes
ignore. At least it doesn&rsquo;t call for anything more than one arm.</p>
<p>My right arm makes it way to her back. I like the way her tongue feels,
particularly the stud she has in there&hellip; not sure exactly what word to use to
describe it. My right hands feels along the bra strap onto the buckle again&hellip;</p>
<p>bzzzzzzttt. Perfect! its important, but thats ok, its not urgent. I wonder what
it could be, for a moment, as my right arm repeats the ignore.</p>
<p>My right hand just strokes her back this time round. I need to allow for a
minute or two. The phone could ring again but it didn&rsquo;t. My body is in automatic
pilot, keeping the pace but does not have my attention, not yet. One&hellip; Two&hellip;
Three&hellip; Four&hellip; Five&hellip; I could as slowly as possible&hellip; Six&hellip; Seven&hellip;
Eight&hellip; and there it was, not a ring, not a bzzzzzzzzttt, but a short burst
bzztt. I know what is coming&hellip; One&hellip; Two&hellip; and my phone screams&hellip; Its not a
call, its not urgent, not at all, its an emergency.</p>
<p>Why is my tongue still in her mouth? I roll her over, take my tongue out, grab
my shirt, my phone and walk out as I start to button up, As I close the door, I
hear her shouting out for me to call her. Ring up the lift, put my headphones on
and click play. Music screams. I need to make a phone call but it&rsquo;ll have to
wait until I&rsquo;m out of the building.</p>
<p>The lift pings, the door opens and reveals and rugged man with long-ish, ruffled
hair, tight shirt, jeans. Can&rsquo;t help but admire the man and his animal
magnetism. Now that is why there should be more mirrors in the world!</p>
<p>I have a minute maybe 90 seconds before I need to deal with this, so I dissolve
into the music - auto-pilot.</p>
<p>As I scream out of the garage, I call. It rings only once. &ldquo;The coordinates for
the base Aquila has been compromised&rdquo;, he tells me. &ldquo;How long do we have&rdquo;, I
ask. &ldquo;ten to fifteen minutes. The base is already in auto-destruct with 9
minutes&rdquo;, &ldquo;I&rsquo;ll be there in five!&rdquo;.</p>
<p>Heavy snow, nicely under the influence of a bottle of nice red wine and I have
five minutes when it takes me 20 on a good day. Now, this is what I live for. As
the phone disconnects, music comes back, loud and and the whole car reverberates
with the bass as the pedal hits the metal - blitzing through the traffic, I can
feel the adrenaline hit as I reach over to the passenger street to grab the hip
flash and bring it to my lips. I can feel it tingling down my throat. The only
thing that could make this better is a cigarette, but I have to wait until the
next stretch of straight, and then I spot the lights.</p>
<p>Hmmm, I wasn&rsquo;t expecting them for another 30 seconds. Oh well! as I turn into a
corner, I notice the the lights are red and cars a backed up ahead, opposite
traffic is trickling down slowly, so I take the only sensible choice I  have, to
weave through oncoming traffic and slide through the lights turning right. I
pick up the pack of cigarettes from the passenger seat, flick it open and bring
it to my mouth so I can fish one out. Grab the lighter and light it.</p>
<p>As I look in the rear-view mirror, the lights are already so far behind that I
can&rsquo;t seem them through the snow.</p>
<p>I have a minute, 90 seconds to relax.</p>
<p>I can see the door opening - no matter how many times I have enjoyed the
automations, I still appreciate it - the less I have to do the better.</p>
<p>The car screeches to a halt as I open the door and get out. The door open
automatically - you gotta love technology. I walk through and there is a
countdown timer projected onto the wall with a cartoon of a smiley face
exploding instead of the zero&rsquo;s. That still cracks me up! Four minutes and
thirty seconds to go. I&rsquo;m thirty seconds early. I hang a right into the kitchen,
straight to the fridge to pick up a nice cold bottle of beer.</p>
<p>Walking back into the main space, with just over four minutes to go, I am drawn
to the conference table in the middle with all the pizza - I need to pick up a
slice.</p>
<p>As I walk over to the table, I am told that the outer perimeter sensors have
detected an incursion. That gives us another seven minutes. An extra three
minutes. They are getting faster. I&rsquo;m glad for the pizza - I wonder if they knew
that I was in the mood for some pepperoni.</p>
<p>As I enjoy the pizza, I ponder on whether to delay the auto-destruct by a few
minutes so that I can catch a few more of them in the blast. My train of though
is interrupted by an informational dialog popping up on the wall to tell me that
all sensitive information has been wiped clean and that all personnell has been
evacuated. Two minutes to go - we are also getting faster, but we are down by
one minute.</p>
<p>&ldquo;Delay the auto-destruct by four minutes&rdquo;, I tell them. &ldquo;But that&rsquo;ll give them a
full minute&rdquo; responded Arin. &ldquo;Good!&rdquo; I thought and nodded to arin. The timer was
reset.</p>
<p>As the countdown went down to a minute, the building sensors told us that they
were just entering the building and I imagined it. They think that they are
early, that they beat the timer. I know that they will see the welcome banner as
they walk in - I like that! This is the first time that they will have seen
that. They have never gotten this far before.</p>
<p>And they&rsquo;ll meander in, with all their gear, signalling and shouting, the room
must be filled with adrenalin and testosterone. For a moment, I feel bad for
them, but let it go. As the timer goes down to ten seconds, I wonder if they got
to the heart of the building, whether they are trying to bring the dead machines
back to life, opening up filing cabinets with cute little messages on them. I
wonder if they smiled.</p>
<p>As the counter hit zero, I know that they heard it, the explosion right outside
the building. There are no longer any exits to this building. The building no
longer has any windows - the explosion blew them out. It&rsquo;ll take them a second
to figure out what happened. All of the walls of the building now light up with
a timer, and the soft sweet voice of a girl counting down&hellip; Five&hellip; Four&hellip;
Three.. Two&hellip; One&hellip; Zero&hellip; The zero is a smiley face. It teases them for a
second, then explodes, and so does the whole building. They didn&rsquo;t stand a
chance.</p>]]></content:encoded></item><item><title>An Accidental Life</title><link>https://icle.es/2011/04/01/an-accidental-life/</link><pubDate>Fri, 01 Apr 2011 16:13:01 +0000</pubDate><guid>https://icle.es/2011/04/01/an-accidental-life/</guid><description>&lt;p>My past that I didn&amp;rsquo;t want to face&lt;br>
and I kept running and running&lt;br>
looking back now,&lt;br>
I ran across some very unusual people&lt;br>
some very unusual circumstances&lt;br>
I never stopped&lt;br>
Even when I smelled the roses, the coffee&lt;br>
I still kept running&lt;/p>
&lt;p>And now I see that I have reached the top,&lt;br>
the very top of a mountain,&lt;br>
and I see it all,&lt;br>
the life I led.&lt;/p>
&lt;p>Standing here looking down on the life I led,&lt;br>
I can&amp;rsquo;t help but feel a little envious,&lt;br>
of the life that I myself led,&lt;br>
looking back with the sun shining on my back&lt;br>
and a tear in my eye&lt;/p></description><content:encoded><![CDATA[<p>My past that I didn&rsquo;t want to face<br>
and I kept running and running<br>
looking back now,<br>
I ran across some very unusual people<br>
some very unusual circumstances<br>
I never stopped<br>
Even when I smelled the roses, the coffee<br>
I still kept running</p>
<p>And now I see that I have reached the top,<br>
the very top of a mountain,<br>
and I see it all,<br>
the life I led.</p>
<p>Standing here looking down on the life I led,<br>
I can&rsquo;t help but feel a little envious,<br>
of the life that I myself led,<br>
looking back with the sun shining on my back<br>
and a tear in my eye</p>
<p>This accidental life that I led,<br>
all because I was running,<br>
away from something.</p>
<p>It might just be time.<br>
Time to face that past, <br>
that which I&rsquo;m running from,<br>
And stop running</p>
<p>Standing at the edge of this cliff,<br>
perhaps its time,<br>
to let it all go.\</p>
<p>Perhaps its time to take a leap of faith,<br>
and let myself fly<br>
and for the first time,<br>
not away from something,<br>
not towards something&hellip;<br>
but to just fly..</p>
]]></content:encoded></item><item><title>hook_theme doesn't get called</title><link>https://icle.es/2011/02/22/hook_theme-doesnt-get-called/</link><pubDate>Tue, 22 Feb 2011 20:20:11 +0000</pubDate><guid>https://icle.es/2011/02/22/hook_theme-doesnt-get-called/</guid><description>&lt;p>I was developing a new module in drupal and it needed a theme function to be
implemented.&lt;/p>
&lt;p>As per the instructions, it was implemented as follows (to use a template)&lt;/p>
```phg
/**
 * Implementation of hook_theme().
 */
function my_module_results_theme($existing, $type, $theme, $path) {

 return array(
 'my_block' => array(
 'template' => 'my_block',
 'arguments' => array(
 'var1' => NULL
 )
 )
 );
}
```
&lt;p>However, when trying to apply the theme, it didn&amp;rsquo;t work. I tried various things
and identified that the hook above was just not being called. A little bit of
digging helped me discover that themes are cached. This happens even in the dev
mode. To resolve this, go to&lt;/p></description><content:encoded><![CDATA[<p>I was developing a new module in drupal and it needed a theme function to be
implemented.</p>
<p>As per the instructions, it was implemented as follows (to use a template)</p>
```phg
/**
 * Implementation of hook_theme().
 */
function my_module_results_theme($existing, $type, $theme, $path) {

    return array(
        'my_block' => array(
            'template' => 'my_block',
            'arguments' => array(
                'var1' => NULL
            )
        )
    );
}
```
<p>However, when trying to apply the theme, it didn&rsquo;t work. I tried various things
and identified that the hook above was just not being called. A little bit of
digging helped me discover that themes are cached. This happens even in the dev
mode. To resolve this, go to</p>
<p><code>Administer  -&gt; Performance -&gt; Clear Cached Data</code> (right at the bottom of the
page)</p>
<p>and et voila my theme was now being utilised.</p>
]]></content:encoded></item><item><title>Hope</title><link>https://icle.es/2011/02/16/hope/</link><pubDate>Wed, 16 Feb 2011 16:13:01 +0000</pubDate><guid>https://icle.es/2011/02/16/hope/</guid><description>&lt;p>a tiny little window of friendliness &amp;hellip; &lt;br>
overflowing with hope and dreams,&lt;br>
of just a possibility,&lt;br>
a possibility that hearts shall not be torn apart.&lt;br>
It leads us astray.. :-(&lt;br>
try as we might to hold on to it.. &lt;br>
it only ever seems to last but a moment.&lt;br>
To live in that moment,&lt;br>
it would be bliss&amp;hellip;&lt;br>
Even such a life would sure lead to boredom&amp;hellip;&lt;br>
and won&amp;rsquo;t be long, will it be..?&lt;br>
before the desire for a little more comes into play..&lt;br>
only a little more..&lt;br>
just a little more..&lt;br>
cos now is never enough..&lt;br>
for it is to be drunk&lt;br>
to its fill&lt;br>
and no more there is&amp;hellip; to be drunk..&lt;br>
and yet, I&amp;rsquo;m still thirsty..&lt;br>
this unquenchable thirst.&lt;br>
that shall be the kink in my armour&lt;br>
my Achilles heel&lt;br>
my weakest link&lt;br>
but in the depths of your heart,&lt;br>
your sweet tender soul&lt;br>
runs a tiny a little stream&lt;br>
and it flows into the an ocean &lt;br>
an ocean so large and never-ending&lt;br>
the universe itself sits quietly envious&lt;br>
and I waited at the gates&lt;br>
these tiny little gates&lt;br>
so easy to jump over&lt;br>
and yet I waited, patiently,&lt;br>
and it seems like a thousand years have passed,&lt;br>
or is it a thousand lifetimes,&lt;br>
a million years, billion tears, a memory, a dream,&lt;br>
I waited for you to open these gates&lt;br>
and let me in so I may take a drink&lt;br>
from this infinite and never ending ocean&lt;br>
this ocean of love, joy, bliss and love&lt;br>
that it may quench my unquenchable,&lt;br>
infinite and never-ending thirst&lt;br>
my thirst for more&amp;hellip;&lt;/p></description><content:encoded><![CDATA[<p>a tiny little window of friendliness &hellip; <br>
overflowing with hope and dreams,<br>
of just a possibility,<br>
a possibility that hearts shall not be torn apart.<br>
It leads us astray.. :-(<br>
try as we might to hold on to it.. <br>
it only ever seems to last but a moment.<br>
To live in that moment,<br>
it would be bliss&hellip;<br>
Even such a life would sure lead to boredom&hellip;<br>
and won&rsquo;t be long, will it be..?<br>
before the desire for a little more comes into play..<br>
only a little more..<br>
just a little more..<br>
cos now is never enough..<br>
for it is to be drunk<br>
to its fill<br>
and no more there is&hellip; to be drunk..<br>
and yet, I&rsquo;m still thirsty..<br>
this unquenchable thirst.<br>
that shall be the kink in my armour<br>
my Achilles heel<br>
my weakest link<br>
but in the depths of your heart,<br>
your sweet tender soul<br>
runs a tiny a little stream<br>
and it flows into the an ocean <br>
an ocean so large and never-ending<br>
the universe itself sits quietly envious<br>
and I waited at the gates<br>
these tiny little gates<br>
so easy to jump over<br>
and yet I waited, patiently,<br>
and it seems like a thousand years have passed,<br>
or is it a thousand lifetimes,<br>
a million years, billion tears, a memory, a dream,<br>
I waited for you to open these gates<br>
and let me in so I may take a drink<br>
from this infinite and never ending ocean<br>
this ocean of love, joy, bliss and love<br>
that it may quench my unquenchable,<br>
infinite and never-ending thirst<br>
my thirst for more&hellip;</p>
]]></content:encoded></item><item><title>rich:calendar and the onchange event</title><link>https://icle.es/2010/06/01/richcalendar-and-the-onchange-event/</link><pubDate>Tue, 01 Jun 2010 20:20:20 +0000</pubDate><guid>https://icle.es/2010/06/01/richcalendar-and-the-onchange-event/</guid><description>&lt;p>I wanted to trigger some validation based re-renders with the &lt;code>rich:calendar&lt;/code>
component. I was scratching my head for a while trying to figure out why it
wasn&amp;rsquo;t working.&lt;/p>
&lt;p>Then it happened, its supposed to be onchange&lt;strong>d&lt;/strong>. This particular component
requires the extra d at the end&amp;hellip; and it worked and everyone lived happily ever
after&amp;hellip;&lt;/p></description><content:encoded><![CDATA[<p>I wanted to trigger some validation based re-renders with the <code>rich:calendar</code>
component. I was scratching my head for a while trying to figure out why it
wasn&rsquo;t working.</p>
<p>Then it happened, its supposed to be onchange<strong>d</strong>. This particular component
requires the extra d at the end&hellip; and it worked and everyone lived happily ever
after&hellip;</p>
]]></content:encoded></item><item><title>Android - Parcel data to pass between Activities using Parcelable classes</title><link>https://icle.es/2010/04/26/android-parcel-data-to-pass-between-activities-using-parcelable-classes/</link><pubDate>Mon, 26 Apr 2010 21:46:31 +0000</pubDate><guid>https://icle.es/2010/04/26/android-parcel-data-to-pass-between-activities-using-parcelable-classes/</guid><description>&lt;p>Passing data between activities on android is unfortunately, not as simple as
passing in parameters. What we need to to do is tag these onto the intent. If
the information we need to pass across is a simple object like a String or
Integer, this is easy enough.&lt;/p>
```java
String strinParam = "String Parameter";
Integer intParam = 5;

Intent i = new Intent(this, MyActivity.class);
i.putExtra("uk.co.kraya.stringParam", stringParam);
i.putExtra("uk.co.kraya.intParam", intParam);

startActivity(i);
```</description><content:encoded><![CDATA[<p>Passing data between activities on android is unfortunately, not as simple as
passing in parameters. What we need to to do is tag these onto the intent. If
the information we need to pass across is a simple object like a String or
Integer, this is easy enough.</p>
```java
String strinParam = "String Parameter";
Integer intParam = 5;

Intent i = new Intent(this, MyActivity.class);
i.putExtra("uk.co.kraya.stringParam", stringParam);
i.putExtra("uk.co.kraya.intParam", intParam);

startActivity(i);
```
<p>Passing in custom objects is a little more complicated. You could just mark the
class
as <a href="http://java.sun.com/javase/6/docs/api/java/io/Serializable.html">Serializable</a>
and let Java take care of this. However, on the android, there is a serious
performance hit that comes with using Serializable. The solution is to
use <a href="http://developer.android.com/reference/android/os/Parcelable.html">Parcelable</a>.</p>
```java
package uk.co.kraya.android.demos.Parcelable;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * @author Shriram Shri Shrikumar
 *
 * A basic object that can be parcelled to
 * transfer between objects
 *
 */
public class ObjectA implements Parcelable {

    private String strValue;
    private Integer intValue;

    /**
     * Standard basic constructor for non-parcel
     * object creation
     */
    public ObjectA() { ; };

    /**
     *
     * Constructor to use when re-constructing object
     * from a parcel
     *
     * @param in a parcel from which to read this object
     */
    public ObjectA(Parcel in) {
        readFromParcel(in);
    }

    /**
     * standard getter
     *
     * @return strValue
     */
    public String getStrValue() {
        return strValue;
    }

    /**
     * Standard setter
     *
     * @param strValue
     */
    public void setStrValue(String strValue) {
        this.strValue = strValue;
    }

    /**
     * standard getter
     *
     * @return
     */
    public Integer getIntValue() {
        return intValue;
    }

    /**
     * Standard setter
     *
     * @param intValue
     */
    public void setIntValue(Integer intValue) {
        this.intValue = intValue;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {

        // We just need to write each field into the
        // parcel. When we read from parcel, they
        // will come back in the same order
        dest.writeString(strValue);
        dest.writeInt(intValue);
    }

    /**
     *
     * Called from the constructor to create this
     * object from a parcel.
     *
     * @param in parcel from which to re-create object
     */
    private void readFromParcel(Parcel in) {

        // We just need to read back each
        // field in the order that it was
        // written to the parcel
        strValue = in.readString();
        intValue = in.readInt();
    }

    /**
     *
     * This field is needed for Android to be able to
     * create new objects, individually or as arrays.
     *
     * This also means that you can use use the default
     * constructor to create the object and use another
     * method to hyrdate it as necessary.
     *
     * I just find it easier to use the constructor.
     * It makes sense for the way my brain thinks ;-)
     *
     */
    public static final Parcelable.Creator CREATOR =
        new Parcelable.Creator() {
            public ObjectA createFromParcel(Parcel in) {
                return new ObjectA(in);
            }

            public ObjectA[] newArray(int size) {
                return new ObjectA[size];
            }
        };

}
```
<p>The intricacies of the class is described in the code above. There is now one
more special case. What if you have an object that references another object.
Clearly, they would both need to be Parcelable, but how would be integrate them.
ObjectB shows a parcelable embedded in another parcelable&hellip;</p>
```java
package uk.co.kraya.android.demos.Parcelable;

import android.os.Parcel;
import android.os.Parcelable;

public class ObjectB implements Parcelable {

    private ObjectA obj;
    private Long longVal;

    public ObjectB() { ; }

    public ObjectA getObj() {
        return obj;
    }

    /**
     *
     * Constructor to use when re-constructing object
     * from a parcel
     *
     * @param in a parcel from which to read this object
     */
    public ObjectB(Parcel in) {
        readFromParcel(in);
    }

    public void setObj(ObjectA obj) {
        this.obj = obj;
    }

    public Long getLongVal() {
        return longVal;
    }

    public void setLongVal(Long longVal) {
        this.longVal = longVal;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {

        // The writeParcel method needs the flag
        // as well - but thats easy.
        dest.writeParcelable(obj, flags);

        // Same as in ObjectA
        dest.writeLong(longVal);
    }

    /**
     *
     * Called from the constructor to create this
     * object from a parcel.
     *
     * @param in parcel from which to re-create object
     */
    private void readFromParcel(Parcel in) {

        // readParcelable needs the ClassLoader
        // but that can be picked up from the class
        // This will solve the BadParcelableException
        // because of ClassNotFoundException
        obj = in.readParcelable(ObjectA.class.getClassLoader());

        // The rest is the same as in ObjectA
        longVal = in.readLong();
    }

    /**
     *
     * This field is needed for Android to be able to
     * create new objects, individually or as arrays.
     *
     * This also means that you can use use the default
     * constructor to create the object and use another
     * method to hyrdate it as necessary.
     *
     * I just find it easier to use the constructor.
     * It makes sense for the way my brain thinks ;-)
     *
     */
    public static final Parcelable.Creator CREATOR =
        new Parcelable.Creator() {
            public ObjectB createFromParcel(Parcel in) {
                return new ObjectB(in);
            }

            public ObjectB[] newArray(int size) {
                return new ObjectB[size];
            }
        };
}
```
<p>When writing the parcel, we need to pass in the flags - which is easy enough.
When reading the parcel, we need the classloader, which can be picked up from
destination class of the parcelable. Again easy!</p>
<p>Finally, passing a parcelable object to an intent</p>
```java
ObjectA obj = new ObjectA();

// Set values etc.

Intent i = new Intent(this, MyActivity.class);
i.putExtra("com.package.ObjectA", obj);

startActivity(i);
```
<p>Almost too easy - right?</p>
<p>and to read the values,</p>
```java
public class MyActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Bundle b = getIntent().getExtras();
        ObjectA obj =
            b.getParcelable("com.package.ObjectA");
    }

}
```
<p>It it was any easier - we&rsquo;d all be out of a job ;-)</p>]]></content:encoded></item><item><title>Android - Managing Global Configuration</title><link>https://icle.es/2010/04/25/android-managing-global-configuratio/</link><pubDate>Sun, 25 Apr 2010 00:39:13 +0000</pubDate><guid>https://icle.es/2010/04/25/android-managing-global-configuratio/</guid><description>&lt;p>&lt;strong>The Problem&lt;/strong>&lt;/p>
&lt;p>Accessing preferences / configuration / settings from Android is actually pretty
straightforward as long as you are in an
&lt;a href="http://developer.android.com/reference/android/app/Activity.html" title="Activity">Activity&lt;/a>.
To read:&lt;/p>
```java
// PREFS_FILENAME = "nameOfPrefsFile";

SharedPreferences pref = getSharedPreferences(PREFS_FILENAME,
 Context.MODE_PRIVATE);

String string = pref.getString("key", "default");
// 1 is the default if key isn't set
int intValue = pref.getInt("intKey", 1);

// and so on
```
&lt;p>&lt;a href="http://developer.android.com/reference/android/content/SharedPreferences.html" title="SharedPreferences">SharedPreference&lt;/a>s
is the key class. To write, you also need the
[SharedPreferences.Editor](&lt;a href="http://developer.android.com/reference/android/content/SharedPreferences.Editor.html%7b">http://developer.android.com/reference/android/content/SharedPreferences.Editor.html{&lt;/a>
class, as follows:&lt;/p>
```java
// PREFS_FILENAME = "nameOfPrefsFile";
SharedPreferences pref = getSharedPreferences(PREFS_FILENAME,
 Context.MODE_PRIVATE);
Editor editor = pref.edit();
editor.putString("key", "value");
editor.putInt("intKey", 5);

// Until you call commit, the changes will not
// be written, so don't forget this step
editor.commit();
```
&lt;p>In general however, you will need access to settings in more than one activity
and it seems a bit wasteful to get these bits littered through the application.
Since I am lazy and like to write things just once, I  separated all the prefs
stuff into one class called Settings.&lt;/p></description><content:encoded><![CDATA[<p><strong>The Problem</strong></p>
<p>Accessing preferences / configuration / settings from Android is actually pretty
straightforward as long as you are in an
<a href="http://developer.android.com/reference/android/app/Activity.html" title="Activity">Activity</a>.
To read:</p>
```java
// PREFS_FILENAME = "nameOfPrefsFile";

SharedPreferences pref = getSharedPreferences(PREFS_FILENAME,
                              Context.MODE_PRIVATE);

String string = pref.getString("key", "default");
// 1 is the default if key isn't set
int intValue = pref.getInt("intKey", 1);

// and so on
```
<p><a href="http://developer.android.com/reference/android/content/SharedPreferences.html" title="SharedPreferences">SharedPreference</a>s
is the key class. To write, you also need the
[SharedPreferences.Editor](<a href="http://developer.android.com/reference/android/content/SharedPreferences.Editor.html%7b">http://developer.android.com/reference/android/content/SharedPreferences.Editor.html{</a>
class, as follows:</p>
```java
// PREFS_FILENAME = "nameOfPrefsFile";
SharedPreferences pref = getSharedPreferences(PREFS_FILENAME,
                              Context.MODE_PRIVATE);
Editor editor = pref.edit();
editor.putString("key", "value");
editor.putInt("intKey", 5);

// Until you call commit, the changes will not
// be written, so don't forget this step
editor.commit();
```
<p>In general however, you will need access to settings in more than one activity
and it seems a bit wasteful to get these bits littered through the application.
Since I am lazy and like to write things just once, I  separated all the prefs
stuff into one class called Settings.</p>
<p>It has a constructor which takes a
<a href="http://developer.android.com/reference/android/content/Context.html" title="Context">Context</a>
(We need this to access the SharedPreferences Object). It also has setters and
getters for each property being saved. This example, just saves/retrieves a
username and password.</p>
```java
import uk.co.kraya.HelloWS;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;

/**
 * @author Shriram Shri Shrikumar
 *
 * This class stores and manages all the preferences
 * for the application.
 *
 */
public class Settings {

    private static final String USERNAME_KEY = "username";
    private static final String PASSWORD_KEY = "password";

    private static final String USERNAME_DEFAULT = "username";
    private static final String PASSWORD_DEFAULT = "password";

    private final SharedPreferences settings;

    /**
     * @param act The context from which to pick SharedPreferences
     */
    public Settings (Context act) {
         settings = act.getSharedPreferences(HelloWS.PREFS_NAME, Context.MODE_PRIVATE);
    }

    /**
     * Set the username in the preferences.
     *
     * @param username the username to save into prefs
     */
    public void setUsername(String username) {
        Editor editor = settings.edit();
        editor.putString(USERNAME_KEY, username);
        editor.commit();
    }

    /**
     * @return the username from the prefs
     */
    public String getUsername() {
        return settings.getString(USERNAME_KEY, USERNAME_DEFAULT);
    }

    /**
     *
     * Set the password in the preferences.
     *
     * @param password password to save into prefs
     */
    public void setPassword(String password) {
        Editor editor = settings.edit();
        editor.putString(PASSWORD_KEY, password);
        editor.commit();
    }

    /**
     * @return the password stored in prefs
     */
    public String getPassword() {
        return settings.getString(PASSWORD_KEY, PASSWORD_DEFAULT);
    }

        // Check if there are any stored settings.
        // can be used to automatically load the settings page
        // where necessary
    public boolean hasSettings() {
        // We just check if a username has been set
        return (!settings.getString(USERNAME_KEY, "").equals(""));
    }

}
```
<p>Nothing particularly exciting. Now, how do we access this. The Android framework
has a neat little feature that is not very well documented and it involved the
use of the
<a href="http://developer.android.com/reference/android/app/Application.html">Application</a>
class. If you inherit from this class, and point to it in the manifest file, it
will get initialised first before any other objects. This is an ideal place for
bits that need global access. You could use Singletons or static fields but this
works with the framework.</p>
<p>There are two parts to making this work</p>
<p>The application class:</p>
```
public class MyApp extends Application {

    private Settings settings;

    @Override
    public void onCreate() {
        settings = new Settings(this);

    }

    public Settings getSettings() {
        return settings;
    }

}
```
<p>The <code>onCreate</code> method on <code>MyApp</code> will be called before <code>onCreate</code> on any of the
Activities. The Settings class described above, needs a Context to be passed in.
Lucky for us ;-) Application is also a Context.</p>
<p>You also need to wire it into the <code>AndroidManifest.xml</code>. You need to add the
<a href="http://developer.android.com/guide/topics/manifest/application-element.html#nm">android:name</a>
element into the
<a href="http://developer.android.com/guide/topics/manifest/application-element.html">application tag</a>{</p>
```xml
<application android:name="com.package.MyApp" android:icon="@drawable/icon" android:label="@string/app_name">
```
<p>Now that is all wired in, accessing the settings object from any activity is
simple:</p>
```java
MyApp app = (MyApp) getApplicationContext();

Settings settings = app.getSettings();
```
<p>Easy - right? While you won&rsquo;t be able to access the application subclass outside
of a context, the Setting class, with its local context variable can be passed
around with impunity :-D</p>]]></content:encoded></item><item><title>Linux bulk search and replace</title><link>https://icle.es/2010/04/21/linux-bulk-search-and-replace/</link><pubDate>Wed, 21 Apr 2010 13:47:26 +0000</pubDate><guid>https://icle.es/2010/04/21/linux-bulk-search-and-replace/</guid><description>&lt;p>Doing a bulk search and replace across a set of files is actually surprisingly
easy. sed is the key. It has a flag - i that will modify the files passed to it
in-place.&lt;/p>
```bash
$ sed -e 's/TextToFind/Replacement/' -i file1 file2 file3
```
&lt;p>Tie this power with either grep &lt;code>-l&lt;/code>. (Thanks to Steve for pointing out a
mistake in the following, now corrected)&lt;/p>
```bash
$ grep -l TextToFind * |xargs sed -e 's/TextToFind/Replacement/' -i
```
&lt;p>or find&lt;/p></description><content:encoded><![CDATA[<p>Doing a bulk search and replace across a set of files is actually surprisingly
easy. sed is the key. It has a flag - i that will modify the files passed to it
in-place.</p>
```bash
$ sed -e 's/TextToFind/Replacement/' -i file1 file2 file3
```
<p>Tie this power with either grep <code>-l</code>. (Thanks to Steve for pointing out a
mistake in the following, now corrected)</p>
```bash
$ grep -l TextToFind * |xargs sed -e 's/TextToFind/Replacement/' -i
```
<p>or find</p>
```bash
$ find . -exec sed -e 's/TextToFind/Replacement' -i {} ;
```
<p>If there are multiple changes you want to make, just put them all into a file
and pass it in via the -f flag.</p>
<p>file: replacements.patterns</p>
```sed
s/TextToFind1/Replacement1/
s/TextToFind2/Replacement2/
s/TextToFind3/Replacement3/
```
<p>and the command, using find to iterate through all files in the current
directory and subdirectories.</p>
```bash
find . -exec sed -f replacements.patterns -i {} ;
```
<p>et voila - hope it helps.</p>
]]></content:encoded></item><item><title>Android - Multi-line Select List</title><link>https://icle.es/2010/04/19/android-multi-line-select-list/</link><pubDate>Mon, 19 Apr 2010 23:37:54 +0000</pubDate><guid>https://icle.es/2010/04/19/android-multi-line-select-list/</guid><description>&lt;p>It turns out that it is surprisingly easy to add a multi line select list to the
UI. There are four main parts to it. The layout file, a subclass to the adapter,
the activity and of course the data itself.&lt;/p>
&lt;p>Lets start with the data. For the sake of this demo, lets use a simple contact
list:&lt;/p>
```java
package uk.co.kraya.android.demos.MultiLineList.domain;

public class Contact {

 private String firstName;

 private String lastName;

 private String mobile;

 public String getFirstName() {
 return firstName;
 }

 public void setFirstName(String firstName) {
 this.firstName = firstName;
 }

 public String getLastName() {
 return lastName;
 }

 public void setLastName(String lastName) {
 this.lastName = lastName;
 }

 public String getMobile() {
 return mobile;
 }

 public void setMobile(String mobile) {
 this.mobile = mobile;
 }

}
```</description><content:encoded><![CDATA[<p>It turns out that it is surprisingly easy to add a multi line select list to the
UI. There are four main parts to it. The layout file, a subclass to the adapter,
the activity and of course the data itself.</p>
<p>Lets start with the data. For the sake of this demo, lets use a simple contact
list:</p>
```java
package uk.co.kraya.android.demos.MultiLineList.domain;

public class Contact {

    private String firstName;

    private String lastName;

    private String mobile;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

}
```
<p>Some straightforward fields and getters/setters</p>
<p>Next, we need an Adapter. For this one, we will use
a <a href="http://developer.android.com/reference/android/widget/ArrayAdapter.html">ArrayAdapter</a>.
We will extend it so that we can override
the <a href="http://developer.android.com/reference/android/widget/Adapter.html#getView%28int,%20android.view.View,%20android.view.ViewGroup%29">getView method.</a></p>
```java
package uk.co.kraya.android.demos.MultiLineList.domain.adapters;

import java.util.List;

import uk.co.kraya.android.demos.MultiLineList.domain.Contact;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TwoLineListItem;

public class ContactArrayAdapter extends ArrayAdapter {

    private final int resourceId;

    public ContactArrayAdapter(Context context, int textViewResourceId, List objects) {
        super(context, textViewResourceId, objects);
        resourceId = textViewResourceId;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        Contact c = getItem(position);

        // if the array item is null, nothing to display, just return null
        if (c == null) {
            return null;
        }

        // We need the layoutinflater to pick up the view from xml
        LayoutInflater inflater = (LayoutInflater)
                        getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        // Pick up the TwoLineListItem defined in the xml file
        TwoLineListItem view;
        if (convertView == null) {
            view = (TwoLineListItem) inflater.inflate(resourceId, parent, false);
        } else {
            view = (TwoLineListItem) convertView;
        }

        // Set value for the first text field
        if (view.getText1() != null) {
            view.getText1().setText(c.getFirstName() + " " + c.getLastName());
        }

        // set value for the second text field
        if (view.getText2() != null) {
            view.getText2().setText("mobile: " + c.getMobile());
        }

        return view;
    }

}
```
<p>The key bit here is the getView method. We pick up
the <a href="http://developer.android.com/reference/android/view/LayoutInflater.html">LayoutInflater</a>
which we can use to pick up the view that defines
the <a href="http://developer.android.com/reference/android/widget/TwoLineListItem.html">TwoLineListItem</a>
view. This allows us to use two different snippets of text as part of the list.
We then pick up the each of
the <a href="http://developer.android.com/reference/android/widget/TextView.html">TextView</a>
items and set the text against them. The formatting of these item are defined in
the xml file.</p>
<p>The TwoLineListItem class also defines a placeholder for the selectedIcon. Check
the documentation for more info.</p>
<p>The xml file for the layout goes as follows:</p>
```xml
<?xml version="1.0" encoding="utf-8"?>
<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">

    <TextView android:id="@android:id/text1"
        android:layout_marginTop="1dip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="15sp"
        android:textStyle="bold" />

    <TextView android:id="@android:id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@android:id/text1"
        android:layout_alignLeft="@android:id/text1"
        android:paddingBottom="4dip"
        android:includeFontPadding="false"
        android:textSize="15sp"
        android:textStyle="normal" />

</TwoLineListItem>
```
<p>As you can see, we are just defining a TwoLineListItem element with two embedded
TextItems. Tthe android:id parts are important. It ensures that the getText1()
and getText2() methods work as expected!</p>
<p>Finally, we have the activity. In fact, we will be using a
<a href="http://developer.android.com/reference/android/app/ListActivity.html">ListActivity</a>
as follows:</p>
```java
package uk.co.kraya.android.demos.MultiLineList;

import java.util.ArrayList;
import java.util.List;

import uk.co.kraya.android.demos.MultiLineList.domain.Contact;
import uk.co.kraya.android.demos.MultiLineList.domain.adapters.ContactArrayAdapter;
import android.app.ListActivity;
import android.os.Bundle;

public class MultiLineListDemo extends ListActivity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setListAdapter(new ContactArrayAdapter(this, R.layout.main, getContacts()));
    }

    private List getContacts() {

        List contacts = new ArrayList();

        Contact c;

        c = new Contact();
        c.setFirstName("Shriram");
        c.setLastName("Shrikumar");
        c.setMobile("07777777777");

        contacts.add(c);

        c = new Contact();
        c.setFirstName("John");
        c.setLastName("Doe");
        c.setMobile("MOBILE.NUMBER");

        contacts.add(c);

        return contacts;

    }
}
```
<p>getContacts clearly just creates a couple of contacts for the sake of the demo.
its the setListAdapter that is the key here. It creates a new
ContactArrayAdapter that we have written, passes in the context (which is just
the current activity), a resource ID and a List of items to display.</p>
<p>Run it and you should see something like:</p>
<p>
  <img src="/assets/2010/04/multilineselectdemo.png" alt="">

</p>
<p>So easy when you know how. I believe could use a View or a ViewGroup as needed
instead of the TwoLineListItem but I shall leave that to you to discover.</p>
<p>I have included all the files that I created/modified but if you want the whole
project tarred up, just drop me a note ;-)</p>]]></content:encoded></item><item><title>Synergy with Linux Server &amp;amp; Mac Client</title><link>https://icle.es/2010/04/18/synergy-with-linux-server-mac-client/</link><pubDate>Sun, 18 Apr 2010 15:41:03 +0000</pubDate><guid>https://icle.es/2010/04/18/synergy-with-linux-server-mac-client/</guid><description>&lt;p>I  borrowed a mac to try and play
with &lt;a href="http://en.wikipedia.org/wiki/IPhone">iPhone&lt;/a> development. I already have a
linux box (running Ubuntu 9.10). Anyone who has used two computers
simultaneously know how annoying it is to have two keyboards/mice plugged. I
originally anticipated just using X11 forwarding. However, it is
an &lt;a href="http://en.wikipedia.org/wiki/IMac">iMac&lt;/a>{with a big beautiful screen. It
would be an absolute waste to not use it.&lt;/p>
&lt;p>I installed &lt;a href="http://en.wikipedia.org/wiki/Synergy%20%28software%29">synergy&lt;/a> on
both ends, with the linux one as the server&lt;/p></description><content:encoded><![CDATA[<p>I  borrowed a mac to try and play
with <a href="http://en.wikipedia.org/wiki/IPhone">iPhone</a> development. I already have a
linux box (running Ubuntu 9.10). Anyone who has used two computers
simultaneously know how annoying it is to have two keyboards/mice plugged. I
originally anticipated just using X11 forwarding. However, it is
an <a href="http://en.wikipedia.org/wiki/IMac">iMac</a>{with a big beautiful screen. It
would be an absolute waste to not use it.</p>
<p>I installed <a href="http://en.wikipedia.org/wiki/Synergy%20%28software%29">synergy</a> on
both ends, with the linux one as the server</p>
```bash
$ sudo aptitude install synergy
```
<p>and the mac as the client</p>
<p><a href="http://sourceforge.net/projects/synergykm/">http://sourceforge.net/projects/synergykm/</a></p>
<p>and it worked.</p>
<p>There was just one very very annoying problem. The Ctrl key and Cmd keys were
different. This really messed with my muscle memory. After some hunting around,
I just had to update my .synergy.conf file in linux. Here is the relevant
section</p>
```
section: screens
    linux-desktop:
    imac:
    ctrl=alt
    alt=ctrl
    meta=alt
end
```
<p>et voila. It now works a charm. I  have neglected the configuration of the
synergykm and synergys but these can be figured out easily ;-)</p>
]]></content:encoded></item><item><title>Perfect Linux</title><link>https://icle.es/2009/12/15/perfect-linux/</link><pubDate>Tue, 15 Dec 2009 16:03:03 +0000</pubDate><guid>https://icle.es/2009/12/15/perfect-linux/</guid><description>&lt;p>According to &lt;a href="http://lunduke.com/?page_id=2">Brian Lunduke&lt;/a>{
&lt;a href="http://lunduke.com/?p=815" title="Ubuntu 9.10 - almost perfect">Ubuntu 9.10 is almost perfect&lt;/a>{
and I concur.&lt;/p>
&lt;p>Being a bit of a purist, I ran &lt;a href="http://www.debian.org">Debian&lt;/a>{for very many
years but found their stable releases lagging behind far too much. This was
largely due to their perfectly understandable view of it being ready only when
it is right.&lt;/p>
&lt;p>For a while, I ran their unstable distribution
called &lt;a href="http://www.debian.org/releases/unstable/">Sid&lt;/a>{ based on the disturbed,
hyperactive 10 year old boy in the
film &lt;a href="http://en.wikipedia.org/wiki/Toy%20Story">Toy Story&lt;/a>{ The idea being that
Sid breaks things, and it certainly did. While it taught me a heck of a lot
about linux (and the terminal), my computer was broken on a very regular basis.&lt;/p>
&lt;p>I switched down to
the &lt;a href="http://www.debian.org/releases/testing/">testing version&lt;/a> and that helped
ease the pain to a very large extent. I had always thought
that &lt;a href="http://www.debian.org">Debian&lt;/a> with a more regular and shorter release
cycle would make a world of difference.
Clearly, &lt;a href="http://en.wikipedia.org/wiki/Canonical%20Ltd.">Canonical&lt;/a> had the same
idea.&lt;/p></description><content:encoded><![CDATA[<p>According to <a href="http://lunduke.com/?page_id=2">Brian Lunduke</a>{
<a href="http://lunduke.com/?p=815" title="Ubuntu 9.10 - almost perfect">Ubuntu 9.10 is almost perfect</a>{
and I concur.</p>
<p>Being a bit of a purist, I ran <a href="http://www.debian.org">Debian</a>{for very many
years but found their stable releases lagging behind far too much. This was
largely due to their perfectly understandable view of it being ready only when
it is right.</p>
<p>For a while, I ran their unstable distribution
called <a href="http://www.debian.org/releases/unstable/">Sid</a>{ based on the disturbed,
hyperactive 10 year old boy in the
film <a href="http://en.wikipedia.org/wiki/Toy%20Story">Toy Story</a>{ The idea being that
Sid breaks things, and it certainly did. While it taught me a heck of a lot
about linux (and the terminal), my computer was broken on a very regular basis.</p>
<p>I switched down to
the <a href="http://www.debian.org/releases/testing/">testing version</a> and that helped
ease the pain to a very large extent. I had always thought
that <a href="http://www.debian.org">Debian</a> with a more regular and shorter release
cycle would make a world of difference.
Clearly, <a href="http://en.wikipedia.org/wiki/Canonical%20Ltd.">Canonical</a> had the same
idea.</p>
<p>Thus <a href="http://en.wikipedia.org/wiki/Ubuntu%20%28Linux%20distribution%29">Ubuntu</a>
was born and it has grown from strength to strength. Its latest distribution
of <a href="http://www.ubuntu.com/products/whatisubuntu/910features">9.10 codename karmic koala</a>
released October 2009, is a massive step forward.</p>
<p><a href="http://www.linkedin.com/in/stepram">Stephen</a>{ the head
of <a href="http://www.krayatec.co.uk">krayatec</a>{was so impressed by the new release
that he conducted an experiment. He asked three people in the office who are not
tech savvy to install and try out the new release. My view was that it was still
too early for such kind of an adoption. I felt that pushing people to try it out
at this stage would damage the reputation of the user friendliness
of <a href="http://en.wikipedia.org/wiki/Linux">Linux</a> rather than help it.</p>
<p>I must admit that I was proven wrong.  All of the installations went smoothly
and it was possible to log in and do the things that they wanted to do.</p>
<p>Do they now use <a href="http://en.wikipedia.org/wiki/Linux">Linux</a> instead
of <a href="http://en.wikipedia.org/wiki/Microsoft%20Windows">Windows</a> - No! There is
still a learning curve and with tight deadlines and little time for re-learning
how to navigate around a computer, it remains an experiment.</p>
<p>However, it does answer one question. Can a user who is not tech-savvy, pick up
a CD/DVD of the latest version
of <a href="http://en.wikipedia.org/wiki/Ubuntu%20%28Linux%20distribution%29">Ubuntu</a>
and run with it? The answer, from this very tiny experiment is a resounding Yes!</p>
<p>As someone who is technically very demanding, I have very few complaints about
the latest version. The only one is that it still looks largely the same as the
previous version. Then, the release is a collection of tools, so this is
understandable.</p>
<p>My three favourite things about the new release are:</p>
<ul>
<li>substantially faster bootup times</li>
<li><a href="http://en.wikipedia.org/wiki/Empathy%20%28software%29">Empathy</a> (particular
for people-nearby which works great in the office)</li>
<li><a href="http://en.wikipedia.org/wiki/Cloud%20services">Cloud</a> (Client &amp; Server)</li>
</ul>
<p>In my opinion, this is the beginning of the
end. <a href="http://en.wikipedia.org/wiki/Linux">Linux</a>, has finally taken a leap that
shows its potential to change everything&hellip; I would wish it luck, but it looks
like it doesn&rsquo;t need it ;-)</p>
<p>Why not
<a href="http://www.ubuntu.com/getubuntu/download" title="Download Ubuntu Live CD">download a live cd</a>
and try it out?</p>]]></content:encoded></item><item><title>Twitter is better</title><link>https://icle.es/2009/12/15/twitter-is-better/</link><pubDate>Tue, 15 Dec 2009 13:13:47 +0000</pubDate><guid>https://icle.es/2009/12/15/twitter-is-better/</guid><description>&lt;p>A little while ago,
&lt;a href="https://icle.es/2009/03/09/making-twitter-bettermaking-twitter-better/">I wrote about my pet peeves to do with twitter,&lt;/a>
and while they probably didn&amp;rsquo;t read my specific ramblings, they have certainly
addressed my key concerns.&lt;/p>
&lt;p>My biggest concern was of course, about security and twitter-apps. A little
while ago, I noticed that this has been resolved. Twitter is now linked with
applications in a more security conscious way. I love the way twitter now asks
if an application should be  authorised to access information. Yay! No more
giving my twitter account details to third party websites.&lt;/p></description><content:encoded><![CDATA[<p>A little while ago,
<a href="https://icle.es/2009/03/09/making-twitter-bettermaking-twitter-better/">I wrote about my pet peeves to do with twitter,</a>
and while they probably didn&rsquo;t read my specific ramblings, they have certainly
addressed my key concerns.</p>
<p>My biggest concern was of course, about security and twitter-apps. A little
while ago, I noticed that this has been resolved. Twitter is now linked with
applications in a more security conscious way. I love the way twitter now asks
if an application should be  authorised to access information. Yay! No more
giving my twitter account details to third party websites.</p>
<p>I also covered an issue that I had with grouping users to see relevant tweets
together. This has also been resolved with the use of lists. In fact, lists have
changed how twitter works to an extent. There is a blog post about how
<a href="http://corethinking.com/2009/12/13/how-twitters-new-lists-feature-will-dramatically-impact-follower-count/" title="How twitters lists feature will dramatically impact follower count">lists will impact follower counts</a>
Lists provide a powerful mechanism to follow a group of people - now, if only I
could have an option to see all the tweets made my the people that I am
following as well as the lists - that would be cool. It probably should&rsquo;t be the
default, but an option to do that would be useful.</p>
<p>I also like the new feature where it tells you that there are new posts since I
last viewed a timeline - saves me from having to click reload randomly to see if
there are no posts. The little grey line differentiating the new posts help in
that I know how far down I have to read to see just the new posts&hellip; :-D</p>
]]></content:encoded></item><item><title>Vista Guest, Linux Host, VirtualBox, Host Networking - Bridge</title><link>https://icle.es/2009/03/23/vista-guest-linux-host-virtualbox-host-networking-bridge/</link><pubDate>Mon, 23 Mar 2009 15:39:41 +0000</pubDate><guid>https://icle.es/2009/03/23/vista-guest-linux-host-virtualbox-host-networking-bridge/</guid><description>&lt;p>One would think that it would be straightforward, work off the bat, or at least
have some reasonable documentation. Unfortunately, no!&lt;/p>
&lt;p>I needed host networking to be able to access network resources (Samba shares
etc.) which does not work if the guest OS is on NAT :-(&lt;/p>
&lt;p>Solving it was easy though&amp;hellip; I assume Vista is installed as a guest with the
guest additions and that your user account is a part of the vboxusers group.&lt;/p></description><content:encoded><![CDATA[<p>One would think that it would be straightforward, work off the bat, or at least
have some reasonable documentation. Unfortunately, no!</p>
<p>I needed host networking to be able to access network resources (Samba shares
etc.) which does not work if the guest OS is on NAT :-(</p>
<p>Solving it was easy though&hellip; I assume Vista is installed as a guest with the
guest additions and that your user account is a part of the vboxusers group.</p>
<p>On the linux host, first install bridge utils. I run Ubuntu, so it was as easy
as:</p>
```bash
$ sudo aptitude install bridge-utils
```
<p>Next, you need to set up the bridge; again, easy on Ubuntu:</p>
<p>add the following section to /etc/network/interfaces</p>
```
auto br0
iface br0 inet dhcp
bridge_ports eth1
```
<p>Add the interfaces to VirtualBox</p>
```bash
$ sudo VBoxAddIF vbox0 'shri' br0
```
<p>Within the VirtualBox Guest settings, choose Host Networking and fo the
interface, choose br0</p>
<p>bring the interface up:</p>
```
$ sudo ifup br0
```
<p>and start your guest os&hellip; et voila, it just works&hellip;</p>
]]></content:encoded></item><item><title>mmmmmmm hhhhhh ddddd</title><link>https://icle.es/2009/03/17/mmmmmmm-hhhhhh-ddddd/</link><pubDate>Tue, 17 Mar 2009 05:25:14 +0000</pubDate><guid>https://icle.es/2009/03/17/mmmmmmm-hhhhhh-ddddd/</guid><description>&lt;p>&lt;a href="http://krish.blog.kraya.co.uk" title="Krish&amp;#39;s Blog">Krish&lt;/a>, my little brother ;-)
recently
&lt;a href="http://krish.blog.kraya.co.uk/2009/03/13/hd-oh-no/" title="HD OH NO">wrote about HD&lt;/a>
and I loved that article and agree with him wholeheartedly. Over the last few
days, I have watched a few movies&amp;hellip; One of them was fantastic (saw it before),
couple were ok, and one was just awful.&lt;/p>
&lt;p>The movie that I loved the most was Kung Fu Hustle. I had seen it before but it
was definitely enjoyable watching it again. Funnily enough, I didn&amp;rsquo;t watch it in
HD (Although I recently got a HD TV) since it was on Film4 (which,
unfortunately, does not have a Film4 HD).&lt;/p></description><content:encoded><![CDATA[<p><a href="http://krish.blog.kraya.co.uk" title="Krish&#39;s Blog">Krish</a>, my little brother ;-)
recently
<a href="http://krish.blog.kraya.co.uk/2009/03/13/hd-oh-no/" title="HD OH NO">wrote about HD</a>
and I loved that article and agree with him wholeheartedly. Over the last few
days, I have watched a few movies&hellip; One of them was fantastic (saw it before),
couple were ok, and one was just awful.</p>
<p>The movie that I loved the most was Kung Fu Hustle. I had seen it before but it
was definitely enjoyable watching it again. Funnily enough, I didn&rsquo;t watch it in
HD (Although I recently got a HD TV) since it was on Film4 (which,
unfortunately, does not have a Film4 HD).</p>
<p>I have been thinking about what I love the most about the movie&hellip; Is it the
story? The story is great and I do love it but it is a simple story although it
does bring in humour, romance, action and edge of the seats suspense
fantastically well.</p>
<p>Would I watch it if it was a low budget movie with the same story? Absolutely!</p>
<p>It also has absolutely fantastic special effects. Absolutely beautiful. If the
story was not as good but the special effects was just as fantastic, would I
still watch it? YES!</p>
<p>I don&rsquo;t have surround sound (yet) but I suspect that the sound is absolutely
gorgeous with 5(/6/7).1. Would I watch a movie without as good a story or
special effects but it had such good sound/music? Not sure, but probably&hellip;</p>
<p>I am certain the movie looks better in HD but would I watch it without HD. Since
I didn&rsquo;t see it in HD, yes, of course I would. Will I watch it in HD? Hell Yeah!</p>
<p>I also watched &ldquo;I married an axe murderer&rdquo;, this time in HD. Of course the fact
that i was in HD made no real difference to my viewing experience&hellip; I enjoyed
Kung Fu Hustle better (of course) but it was still a good enjoyable movie. God
bless Mike Myers</p>
<p>I also watched &ldquo;Man Of The Year&rdquo; with Robin Williams. Fantastic movie&hellip; Again
in HD. Again, didn&rsquo;t notice the HD part but I checked it side by side and the
quality was better.</p>
<p>I suppose it all comes down to what we take for granted and what we are used to.
With the big plasma tv&rsquo;s the image gets stretched a little too much and this is
noticeable. HD solves this. Of course 5.1 sound is beter than Stereo.</p>
<p>Before I go on, let me just mention the bad film I saw - it was Prophecy with
Christopher Walken. I could see the movie&rsquo;s potential. It has a good story but I
got bored halfway through - not because there were no special effects (I imagine
better effects through the movie). The story was not half bad (at least the
2/3rds that I watched). The acting was not bad either. In my view, the movie
failed to capture my attention for one very simple reason&hellip; Pacing&hellip; This is
not necessarily the movie&rsquo;s fault. We are now used to movies for the MTV
generation where everything moves so much faster. No, I am not talking about
cuts every 5 seconds. I am talking about the story moving forward. I watched for
a good 40 minutes or so, I still didn&rsquo;t know more than I knew at the start of
the movie. MTV generation or not, that is just boring&hellip;</p>
<p>I sincerely believe that everything has its place and a movie can be good just
because it has a fantastic story, fantastic special effects or fantastic sound.
It also needs to be delivered just right. Most importantly, for a movie to be
fantastic, you need it ALL!!! Thats what makes it a movie to be watched
repeatedly.</p>
<p>There are of course movies that buck this trend. The Big Lebowski is an
absolutely fantastic movie.</p>
<p>
  <img src="/assets/2009/03/2364378947_5686fe5269_m.jpg" alt="">

</p>
<p>I have already seen this half a dozen times and I&rsquo;ll watch it again. In all
honesty though, if it &ldquo;looked better&rdquo;. I&rsquo;d probably watch it even more often&hellip;
Although, in all fairness, I am not sure <strong>how</strong> it could <em>look</em> better but
that&rsquo;s not for me to worry about - I am not a filmmaker [yet ;-)]</p>
]]></content:encoded></item><item><title>Database Systems Compared</title><link>https://icle.es/2009/03/10/database-systems-compared/</link><pubDate>Tue, 10 Mar 2009 16:00:21 +0000</pubDate><guid>https://icle.es/2009/03/10/database-systems-compared/</guid><description>&lt;p>My first experiences of a computer started with
&lt;a href="http://en.wikipedia.org/wiki/DBase" title="Dbase on Wikipedia">DBase III+&lt;/a>which is
now &lt;a href="http://www.dbase.com/" title="dBASE">dBASE&lt;/a>, then went on to
&lt;a href="http://en.wikipedia.org/wiki/FoxPro_2" title="Foxpro 2 on Wikipedia">Foxpro&lt;/a>, now
&lt;a href="http://msdn.microsoft.com/en-us/vfoxpro/bb190288.aspx" title="Microsoft Visual Foxpro">Microsoft Visual Foxpro&lt;/a>.&lt;/p>
&lt;p>I have since used:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="http://www.filemaker.co.uk/" title="Filemaker Pro">Filemaker Pro&lt;/a>,&lt;/li>
&lt;li>&lt;a href="http://office.microsoft.com/en-us/access/default.aspx" title="Microsoft Access">Microsoft Access&lt;/a>,&lt;/li>
&lt;li>&lt;a href="http://www.microsoft.com/sqlserver/2008/en/us/default.aspx" title="Microsoft SQL Server">Microsoft SQL Server&lt;/a>,&lt;/li>
&lt;li>&lt;a href="http://www.mysql.com/" title="MySQL">MySQL&lt;/a>,&lt;/li>
&lt;li>&lt;a href="http://www.postgresql.org/" title="PostgreSQL">PostgreSQL&lt;/a>,&lt;/li>
&lt;li>&lt;a href="http://www.sqlite.org/" title="SQLite">SQLite&lt;/a> and&lt;/li>
&lt;li>&lt;a href="http://hsqldb.org/" title="HSQLDB">HSQLDB&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>I have not yet used:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="http://www.ibm.com/software/data/db2/" title="IBM DB2">IBM DB2&lt;/a>,&lt;/li>
&lt;li>&lt;a href="http://www.oracle.com/index.html" title="Oracle">Oracle&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="http://en.wikipedia.org/wiki/Comparison_of_relational_database_management_systems" title="Compare DB Systems">Wikipedia has a list of database systems&lt;/a>.&lt;/p></description><content:encoded><![CDATA[<p>My first experiences of a computer started with
<a href="http://en.wikipedia.org/wiki/DBase" title="Dbase on Wikipedia">DBase III+</a>which is
now <a href="http://www.dbase.com/" title="dBASE">dBASE</a>, then went on to
<a href="http://en.wikipedia.org/wiki/FoxPro_2" title="Foxpro 2 on Wikipedia">Foxpro</a>, now
<a href="http://msdn.microsoft.com/en-us/vfoxpro/bb190288.aspx" title="Microsoft Visual Foxpro">Microsoft Visual Foxpro</a>.</p>
<p>I have since used:</p>
<ul>
<li><a href="http://www.filemaker.co.uk/" title="Filemaker Pro">Filemaker Pro</a>,</li>
<li><a href="http://office.microsoft.com/en-us/access/default.aspx" title="Microsoft Access">Microsoft Access</a>,</li>
<li><a href="http://www.microsoft.com/sqlserver/2008/en/us/default.aspx" title="Microsoft SQL Server">Microsoft SQL Server</a>,</li>
<li><a href="http://www.mysql.com/" title="MySQL">MySQL</a>,</li>
<li><a href="http://www.postgresql.org/" title="PostgreSQL">PostgreSQL</a>,</li>
<li><a href="http://www.sqlite.org/" title="SQLite">SQLite</a> and</li>
<li><a href="http://hsqldb.org/" title="HSQLDB">HSQLDB</a>.</li>
</ul>
<p>I have not yet used:</p>
<ul>
<li><a href="http://www.ibm.com/software/data/db2/" title="IBM DB2">IBM DB2</a>,</li>
<li><a href="http://www.oracle.com/index.html" title="Oracle">Oracle</a>.</li>
</ul>
<p><a href="http://en.wikipedia.org/wiki/Comparison_of_relational_database_management_systems" title="Compare DB Systems">Wikipedia has a list of database systems</a>.</p>
<p>Having worked with this range of database systems and having done copious
amounts of research into DB2, Oracle and other DB systems I have not mentioned,
I like answering the age old questions. Which is the best database system?</p>
<p>Ah! if only it was that simple. There is no database system that is appropriate
for any given requirement. But then, if you have been in the technology sector
long enough, you would already know that. It's all about using the right tool
for the job.</p>
<p>I separate these systems into two broad categories and Oracle. There are the
Desktop based database systems:</p>
<ul>
<li>DBase</li>
<li>Foxpro</li>
<li>SQLite</li>
<li>HSQLDB</li>
<li>Filemaker Pro</li>
<li>Microsoft Access</li>
<li>MySQL</li>
</ul>
<p>DBase, FoxPro, Filemaker Pro and Microsoft Access are essentially a GUI frontend
that has a database backing.</p>
<p>Access is the best choice for this purpose under the majority of circumstances.
Filemaker Pro is relevant in some. The usual reason to use DBase or FoxPro is
simply that the developer is used to it. This is not a good enough reason.</p>
<p>I have used DBase III+ for developing an office management suite back in 1994. I
have since used Filemaker Pro to develop a simple contact management database in
1998, Microsoft Access to develop a patient management system for a clinic.</p>
<p>SQLite, HSQLDB and MySQL are database engines that are to be utilised by popping
a frontend on top; sometimes the frontend is Microsoft Access. Microsoft Access
can also be used for its database engine.</p>
<p>Access is usually the worst choice for this except as a stopgap. There are
exceptions to this. One is for a web frontend if the site is not too busy and
its running on a microsoft platform. You don't have to go to the hassle of
installing anything on the server. The drivers will take care of it all.</p>
<p>HSQLDB becomes an obvious choice for a light java based application and SQLite
for any other lightweight applications.</p>
<p>MySQL is substantially more powerful and scales a lot better. I include it in
this section because it is a server grade database system that can also work
well in a desktop environment.</p>
<p>I have used Access for several web based systems and I have used HSQLDB for unit
testing hibernate and for a quick and dirty MP3 library that linked into
<a href="http://musicbrainz.org/" title="Musicbrainz">musicBrainz</a>. I have used SQLite in
passing to be utilised by open source products.</p>
<p>I have used MySQL with an Access frontend as a management suite for a website as
well.</p>
<p>And we have the server based database systems:</p>
<ul>
<li>MySQL</li>
<li>Microsoft SQL Server</li>
<li>IBM DB2</li>
<li>PostgreSQL</li>
</ul>
<p>MySQL was used as the backed database system for the edFringe.com website. This
was the perfect choice since the most important requirement was speed.
Particuarly with the Query Cache and Master Slave replication, MySQL was the
best choice.</p>
<p>SQL Server was used as the backend system for an online course for the Scottish
Enterprise around 1999/2000. While MySQL would have been a good choice this, it
was not of production quality at the time.</p>
<p>We have also used Ms SQL Server for an insurance company since all the
infrastructure was based on Windows and PostgreSQL did not have a viable Windows
version at the time.</p>
<p>We use PostgreSQL for megabus. While speed is absolutely critical, it is a
ticketing system which means that transactionality is absolutely critical.</p>
<p>While MySQL now has transactionality with innodb, it is still nowhere near as
good as the transactionality provided by PostgreSQL through MVCC (Multi-version
Concurrency Control). We could have used Ms SQL Server but the cost savings are
dramatic.</p>
<p>To summarise, each system has a specific use, specific strengths and weaknesses
and which should be used is highly dependent on what it is to be used for. I am
hopeful that the summary of what we have used each of these systems for us
useful in determining which one is best placed to solve any specific problem :-D</p>
<p>We have not yet used Oracle and it was a strong contender for megabus but the
serious heavyweight functionality provided by Oracle comes at a price and it is
not yet a cost effective option.</p>]]></content:encoded></item><item><title>Making Twitter Better</title><link>https://icle.es/2009/03/09/making-twitter-better/</link><pubDate>Mon, 09 Mar 2009 15:01:40 +0000</pubDate><guid>https://icle.es/2009/03/09/making-twitter-better/</guid><description>&lt;p>I think that twitter is a fantastic service and has a bright future. However,
like a lot of new things, the question of whether it will flourish or perish is
really all down how the growth is managed, planned and executed.&lt;/p>
&lt;p>I should point out that I don&amp;rsquo;t know the people at twitter at all and is very
much an outsiders opinion. I have been running a business for about nine years,
and while it is of nowhere near the success of twitter, I&amp;rsquo;ve definitely learned
some hard lessons. I am not complaining - I am however, voicing some ideas on
how things could be made better.&lt;/p>
&lt;p>My experience also includes working very closely with megabus.com, which grew
from a fledgling website 6 years ago to what it is today servicing over a
100,000 visitors every day.&lt;/p>
&lt;p>My gut instinct about Twitter is that the guys and gals are working hard to
delivery one really good service really well. However, it is of a size now where
service delivery should be happening in the background with little or no effort.&lt;/p></description><content:encoded><![CDATA[<p>I think that twitter is a fantastic service and has a bright future. However,
like a lot of new things, the question of whether it will flourish or perish is
really all down how the growth is managed, planned and executed.</p>
<p>I should point out that I don&rsquo;t know the people at twitter at all and is very
much an outsiders opinion. I have been running a business for about nine years,
and while it is of nowhere near the success of twitter, I&rsquo;ve definitely learned
some hard lessons. I am not complaining - I am however, voicing some ideas on
how things could be made better.</p>
<p>My experience also includes working very closely with megabus.com, which grew
from a fledgling website 6 years ago to what it is today servicing over a
100,000 visitors every day.</p>
<p>My gut instinct about Twitter is that the guys and gals are working hard to
delivery one really good service really well. However, it is of a size now where
service delivery should be happening in the background with little or no effort.</p>
<p>When megabus.com first launched and over the first couple of years, we spent a
lot of time managing the hardware, software and processes till we got it right.
It went through a dramatic re-architecture in 2005 and since then, the
management time has dropped dramatically.</p>
<p>To take twitter to the next level so that it can be bigger than facebook, in my
opinion, requires twitter to a lot of things:</p>
<p><strong>Reliability &amp; Performance</strong></p>
<p>I don&rsquo;t know the architecture / infrastructure of twitter but having used it
fairly heavily over the last few days, have noticed intermittent outages. This
has to be solved. Not just in the short term, but in the medium and long term.
Twitter has to be a service that just works. All websites suffer glitches and
outages but the mean time to failure needs to be a lot higher and it should be
cheap and cost effective to scale.</p>
<p><strong>TwitApplications</strong></p>
<p>There are a lot of services and applications that link into twitter. I
consistently use tweetburner, tweetdeck and have looked at / considered a range
of other services / applications. While the wiki page can point someone in the
right direction. This needs to be integrated better into twitter itself</p>
<p>Facebook really took off and removed bebo and myspace as competitors, in my
opinion the day it introduced facebook applications.</p>
<p>It should be a different process from facebook as facebook applications are of a
different breed and different target market. Twitter simply needs to make it
easier for applications to integrate in to solve two problems</p>
<ol>
<li>Easy launchpad to add them in and use them</li>
<li>Remove the need to provide the twitter username/password in other websites.
I currently have to do this with tweetburner to post directly which makes me
very uncomfortable.</li>
</ol>
<p><strong>Accessibility</strong></p>
<p>I am not talking about makes it easier for people with disabilities to access
the site. I am talking about people who are not technically savvy or more
importantly twitter savvy.</p>
<p>I joined twitter a while back and just felt a bit lost. There was no guidance as
to what a tweet was, what it meant to be a follower or what it meant for people
to follow you.</p>
<p>It took an article on a magazine explaining it to make it easier for me to
understand and re-boot my twitter life.</p>
<p>Help &amp; Support are good and useful but it should not be necessary if the help
and support is present throughout the site. Facebook does this well and makes it
easy to learn and do new things. It does not need to be idiot proof but it does
need to have just enough information for a newbie to get started.</p>
<p>There are numerous blogs, articles and websites that cover this information but
that means that someone has to spend enough effort getting out there and finding
out.</p>
<p>This can be difficult when you don&rsquo;t know what you are searching for as well.</p>
<p><strong>Functional Integrations</strong></p>
<p>There are several integrations that would be useful. There are websites that do
some of these things but it would be useful to have them integrated within the
site. Examples include:</p>
<ul>
<li>Easy way to see the last tweet of all the people you are following / your
followers</li>
<li>Popularity of the people you are following / your followers</li>
<li>Group people, so that you can follow people who blog about different things
but read them together</li>
</ul>
<p><strong>Conclusion</strong></p>
<p>From my perspective, this is of course a starting point, the tip of the iceberg.
Twitter is involved in a lot of new things but without the soft aspect, I think
it is making its life harder than it has to be to get the masses.</p>]]></content:encoded></item><item><title>Making Twitter Faster</title><link>https://icle.es/2009/03/04/making-twitter-faster/</link><pubDate>Wed, 04 Mar 2009 17:36:35 +0000</pubDate><guid>https://icle.es/2009/03/04/making-twitter-faster/</guid><description>&lt;p>From my perspective, Twitter has a really really interesting technical problem
to solve. How to store and retrieve a large amount of data really really
quickly.&lt;/p>
&lt;p>I am making some assumptions based on how I see twitter working. I have little
information about how it is architected apart from some posts that suggests that
it is running ruby on rails with MySQL?&lt;/p>
&lt;p>Twitter is in the rare category where there is a very large number of data being
added. There should be no updates (except to user information but there should
be relatively very small amount of that). There is no need for transactionality.
If I guess right, it should be a large amount of inserts and selects.&lt;/p>
&lt;p>While a relational database is probably the only viable choice for the time
being, I think that twitter can scale and perform better if all the extra bits
of a relational database system was removed.&lt;/p>
&lt;p>I love challenges like this. Technical ones are easier ;-)&lt;/p>
&lt;p>If I didn&amp;rsquo;t have a lifetime job, I would prototype this in a bit more depth.
&lt;a href="http://garry.blog.kraya.co.uk" title="Garry&amp;#39;s Blog">Garry&lt;/a> pointed me in the
direction of &lt;a href="//hadoop.apache.org/" title="Hadoop">Hadoop&lt;/a>. Having had a quick look at
it, it can take care of the infrastructure, clustering and massive horizontal
scaling requirements.&lt;/p></description><content:encoded><![CDATA[<p>From my perspective, Twitter has a really really interesting technical problem
to solve. How to store and retrieve a large amount of data really really
quickly.</p>
<p>I am making some assumptions based on how I see twitter working. I have little
information about how it is architected apart from some posts that suggests that
it is running ruby on rails with MySQL?</p>
<p>Twitter is in the rare category where there is a very large number of data being
added. There should be no updates (except to user information but there should
be relatively very small amount of that). There is no need for transactionality.
If I guess right, it should be a large amount of inserts and selects.</p>
<p>While a relational database is probably the only viable choice for the time
being, I think that twitter can scale and perform better if all the extra bits
of a relational database system was removed.</p>
<p>I love challenges like this. Technical ones are easier ;-)</p>
<p>If I didn&rsquo;t have a lifetime job, I would prototype this in a bit more depth.
<a href="http://garry.blog.kraya.co.uk" title="Garry&#39;s Blog">Garry</a> pointed me in the
direction of <a href="//hadoop.apache.org/" title="Hadoop">Hadoop</a>. Having had a quick look at
it, it can take care of the infrastructure, clustering and massive horizontal
scaling requirements.</p>
<p>Now for the data layer on top. How to store and retrieve the data.
<a href="http://hadoop.apache.org/hbase/" title="HBase - a scalable distributed database">HBase</a>
is probably a good option but doing it manually should be fairly straightforward
too.</p>
<p>From my limited understanding of twitter, there are two key pieces of
functionality, the timelines and search.</p>
<p>The timelines can be solved by storing each tweet as a file within a directory
structure. My tweets would go into</p>
<p><code>/w/o/r/d/s/o/n/s/a/n/d/&lt;tweet-filename&gt;</code></p>
<p>The filename would be <code>&lt;username&gt;-&lt;timestamp&gt;</code></p>
<p>For the public timeline, you just have a similar folder structure, but with the
timestamp, for example, the timestamp 1236158897 would go into the following
structure as a symlink</p>
<p><code>/1/2/3/6/1/5/8/8/9/7/&lt;username&gt;</code></p>
<p>For search, pick up each word in the tweet and pop the tweet as a symlink into
that folder. You could have a folder per word or follow the structure above.</p>
<p><code>/t/w/i/t/t/e/r/&lt;username&gt;-&lt;timestamp&gt;</code> OR</p>
<p><code>twitter/&lt;username&gt;-&lt;timestamp&gt;</code></p>
<p>You would then have an application running on top with a distributed cache with
an API to ease access into the data easier than direct file access. Running on
Linux, the kernel will take care of the large part of the automatic caching and
buffering as long as there is enough RAM on the box.</p>
<p>This can in theory be done without Hadoop in between and separating the
directory structures across multiple servers but that can have complications of
its own, especially with adding and removing boxes for scalability.</p>
<p>You are also likely to run into issues with the number of files /
sub-directories limits but they can be solved by &lsquo;archiving&rsquo; - multiple options
for that too&hellip;</p>
<p>Thinking about this problem brought me back to the good old days of working on
the search mechanism within megabus.com. We needed the site to deal with a large
number of searches on limited hardware when the project was still classified as
a pilot.</p>
<p>With some hard work and experimentation, we were able to reduce the search time
to a tenth of the original time.</p>
<p>I&rsquo;ll admit that I don&rsquo;t know the details or the intricacies of the requirements
that twitter has. I have probably over-simplified the problem but it was still
fun to think about. If you can think of problems with this - let me know; I
wanna turn them into opportunities ;-)</p>]]></content:encoded></item><item><title>Accepting Google</title><link>https://icle.es/2009/02/10/accepting-google/</link><pubDate>Tue, 10 Feb 2009 11:41:44 +0000</pubDate><guid>https://icle.es/2009/02/10/accepting-google/</guid><description>&lt;p>&lt;a href="http://www.codinghorror.com/blog/archives/001224.html" title="Google monoculture">Jeff Atwood (Coding Horror)&lt;/a>
correctly points out that when we refer to search engines, we are really only
referring to one - &lt;a href="http://www.google.co.uk" title="Google">google&lt;/a>. With its easy to
use, efficient and most importantly effective search functionality, there really
is no reason to use another search engine.&lt;/p>
&lt;p>Jeff raises a couple of valid points. With no viable competition, where is the
incentive for them to improve the functionality.  It&amp;rsquo;s pleasant to see that
google still invests time and money into improving features including the
ability to personalise your search results. However, the question of how long
they will keep doing this is worth asking&amp;hellip;&lt;/p>
&lt;p>The more interesting point that Jeff raises is:&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;I&amp;rsquo;m a little surprised all the people who were so
&lt;a href="http://en.wikipedia.org/wiki/United_States_v._Microsoft">up in arms about the Microsoft &amp;ldquo;monopoly&amp;rdquo; ten years ago&lt;/a>
aren&amp;rsquo;t out in the streets today lighting torches and sharpening their
pitchforks to go after Google.&amp;rdquo;&lt;/p>&lt;/blockquote></description><content:encoded><![CDATA[<p><a href="http://www.codinghorror.com/blog/archives/001224.html" title="Google monoculture">Jeff Atwood (Coding Horror)</a>
correctly points out that when we refer to search engines, we are really only
referring to one - <a href="http://www.google.co.uk" title="Google">google</a>. With its easy to
use, efficient and most importantly effective search functionality, there really
is no reason to use another search engine.</p>
<p>Jeff raises a couple of valid points. With no viable competition, where is the
incentive for them to improve the functionality.  It&rsquo;s pleasant to see that
google still invests time and money into improving features including the
ability to personalise your search results. However, the question of how long
they will keep doing this is worth asking&hellip;</p>
<p>The more interesting point that Jeff raises is:</p>
<blockquote>
<p>&ldquo;I&rsquo;m a little surprised all the people who were so
<a href="http://en.wikipedia.org/wiki/United_States_v._Microsoft">up in arms about the Microsoft &ldquo;monopoly&rdquo; ten years ago</a>
aren&rsquo;t out in the streets today lighting torches and sharpening their
pitchforks to go after Google.&rdquo;</p></blockquote>
<p>My view on this is straightforward. Yes, google is a monopoly on the search
market. There is no viable competition. Yes, it possibly uses this position in
the market to push itself out more and more to the masses.</p>
<p>However, the reason Microsoft got into the bad books (at least for me) is that
while it provided (or provides) fantastic software - it doesn&rsquo;t treat its
customers fairly. Bundling Internet Explorer with windows is fine IF it also
bundled Netscape/Firefox which was/is a strong competitor and the only reason
people did not use them was lack of experience / knowledge of the option.</p>
<p>The reason google is successful is because it is the only viable choice. There
is no other option. If Internet Explorer had no competitor. Then, its fine to
include that exclude the others.</p>
<p>Then there is the unfairness in how Microsoft priced the products in relation to
the number of issues / bugs that were in the product. Not to mention the feeling
that, as customers, you were paying for the privilege of beta testing software.</p>
<p>As a software engineer, I am well aware of the issue around bugs. They are
present, and always will be. That&rsquo;s the nature of software. The issue is not
just the number of bugs that are present in software shipped but also the amount
of time it takes to resolve them.</p>
<p>It&rsquo;s not the monopolisation of the market that &ldquo;got them&rdquo;. It was their
attitude. The monopolisation of the market was the tool used to get them. Kinda
like Al Capone being arrested for Tax evasion instead of all the other crimes he
commited since that was the only way to get him.</p>]]></content:encoded></item><item><title>Customisation</title><link>https://icle.es/2009/02/03/customisation/</link><pubDate>Tue, 03 Feb 2009 11:14:08 +0000</pubDate><guid>https://icle.es/2009/02/03/customisation/</guid><description>&lt;p>Being an avid Linux user for users, I am seriously spoilt in terms of being able
to customise everything / anything to be more the way I want it to be&amp;hellip;&lt;/p>
&lt;p>Two main reasons for this is that most software that comes on Linux is highly
customisable to start off with. The second reason is that if you don&amp;rsquo;t like
something, you can change it.&lt;/p>
&lt;p>There is also the nice thing that most things that you think would be cool or
useful in software is already available in some form since someone else thought
so too, but before you did and has had the chance to spend some time building
it.&lt;/p></description><content:encoded><![CDATA[<p>Being an avid Linux user for users, I am seriously spoilt in terms of being able
to customise everything / anything to be more the way I want it to be&hellip;</p>
<p>Two main reasons for this is that most software that comes on Linux is highly
customisable to start off with. The second reason is that if you don&rsquo;t like
something, you can change it.</p>
<p>There is also the nice thing that most things that you think would be cool or
useful in software is already available in some form since someone else thought
so too, but before you did and has had the chance to spend some time building
it.</p>
<p>I love this so much so that I have often put together a quick linux box for
doing things that one could easily replace with an embedded device like a
router. I have swayed between the two options based on how much I want
simplicity vs flexibility.</p>
<p>One of my favourite responses to someone telling me that we need something that
we don&rsquo;t have is - &ldquo;we&rsquo;ll build one&rdquo;&hellip; The software customisation / writing has
turned into a metaphor that I apply across more and more things. You need a new
table with custom bits - let&rsquo;s build it. You need a classic car with all the
modern gizmos - you know what - let&rsquo;s just build it.</p>
<p>This has its pro&rsquo;s and cons. For one, it feels like anything is possible. It
also becomes very frustrating to work with limited, limiting, or closed source
software (esp when you just want to fix a quick bug that really irks you). It
also eats up all your time as you try and do all the things you want&hellip; just
because you can&hellip;</p>
<p>Striking a balance is hard especially when a client asks if it is possible to do
something very specific. The answer is of course yes and there is a question
that goes with that response. At what value does it become cost effective and
provide a good Return On Investment(ROI)</p>
]]></content:encoded></item><item><title>Bad Google</title><link>https://icle.es/2009/01/31/bad-google/</link><pubDate>Sat, 31 Jan 2009 19:09:31 +0000</pubDate><guid>https://icle.es/2009/01/31/bad-google/</guid><description>&lt;p>I stumbled across [a post by a Mark Ghosh, an unhappy orkut
user](&lt;a href="http://weblogtoolscollection.com/archives/2009/01/31/et-tu-google-then-fail-net-safety/%7b">http://weblogtoolscollection.com/archives/2009/01/31/et-tu-google-then-fail-net-safety/{&lt;/a>
which covers a very basic and age old security flaw within Orkut, a social
networking site similar to Facebook / MySpace which is now owned by Google.&lt;/p>
&lt;p>Google, one of the largest corporations in the world went through and acquired a
whole bunch of online communities and this is all fine. However, should a
company of this calibre not be more careful about associating with a website
that has such a silly but serious security flaw. A flaw that could probably be
resolved within an hour of work. I appreciate that there are probably numerous
other issues that the site has...&lt;/p></description><content:encoded><![CDATA[<p>I stumbled across [a post by a Mark Ghosh, an unhappy orkut
user](<a href="http://weblogtoolscollection.com/archives/2009/01/31/et-tu-google-then-fail-net-safety/%7b">http://weblogtoolscollection.com/archives/2009/01/31/et-tu-google-then-fail-net-safety/{</a>
which covers a very basic and age old security flaw within Orkut, a social
networking site similar to Facebook / MySpace which is now owned by Google.</p>
<p>Google, one of the largest corporations in the world went through and acquired a
whole bunch of online communities and this is all fine. However, should a
company of this calibre not be more careful about associating with a website
that has such a silly but serious security flaw. A flaw that could probably be
resolved within an hour of work. I appreciate that there are probably numerous
other issues that the site has...</p>
<p>However, if the security of the site is not given any priority, how can we, as
the masses place so much trust into an organisation that we trust to perform our
searches, store our emails (GMail), our files(Google Docs) and trawl through our
websites to make it searchable and available to the masses?</p>
<p>In all honesty, if Google cannot allocate enough resources to at least fix
security issues within its products, perhaps, they should at least shut them
down to limit the damage hackers can do to legitimate users.</p>
<p>Sure, if someone falls for a scam and accidentally gives out their password,
they end up paying a price but having zero control over being able to resolve it
is unacceptable. A user should be able to change their password and know that
someone who had your old password can no longer log in...</p>
]]></content:encoded></item><item><title>Controversy</title><link>https://icle.es/2009/01/31/controversy/</link><pubDate>Sat, 31 Jan 2009 18:53:02 +0000</pubDate><guid>https://icle.es/2009/01/31/controversy/</guid><description>&lt;p>We have never been shy about voicing our opinions or being controversial. While
discussing some PR requirements recently with a potential agency, the question
was asked about whether we would be willing to be controversial.&lt;/p>
&lt;p>We are not necessarily controversial, just that we hold a view that is usually a
little different from the mainstream views. It could be said that we bring the
alternative to the mainstream.&lt;/p>
&lt;p>But then, so did some world governments, bringing open source software into
their work places, successfully or unsuccessfully in the last few years instead
of Microsoft.&lt;/p>
&lt;p>Someone recently suggested that we were anti-microsoft. I don&amp;rsquo;t think that is
case. Microsoft has its place in a technology infrastructure. It is simply that
its position is usually overrated or misplaced. As far as desktops for
technically shy users are concerned, there is really no alternative but
Microsoft Windows. I can hear the Mac users scream that Macs are also an
alternative. Theoretically, yes but the fact is that they are too expensive for
someone to dabble with it. This is precisely the reason that Microsoft Windows
dominates the desktop market.&lt;/p>
&lt;p>We support and use Linux. In fact, the majority of the desktops in the office
run Linux (Ubuntu as it happens) but people who have a non-technical role use
Windows. They could use Linux but Windows is better suited to their role.&lt;/p></description><content:encoded><![CDATA[<p>We have never been shy about voicing our opinions or being controversial. While
discussing some PR requirements recently with a potential agency, the question
was asked about whether we would be willing to be controversial.</p>
<p>We are not necessarily controversial, just that we hold a view that is usually a
little different from the mainstream views. It could be said that we bring the
alternative to the mainstream.</p>
<p>But then, so did some world governments, bringing open source software into
their work places, successfully or unsuccessfully in the last few years instead
of Microsoft.</p>
<p>Someone recently suggested that we were anti-microsoft. I don&rsquo;t think that is
case. Microsoft has its place in a technology infrastructure. It is simply that
its position is usually overrated or misplaced. As far as desktops for
technically shy users are concerned, there is really no alternative but
Microsoft Windows. I can hear the Mac users scream that Macs are also an
alternative. Theoretically, yes but the fact is that they are too expensive for
someone to dabble with it. This is precisely the reason that Microsoft Windows
dominates the desktop market.</p>
<p>We support and use Linux. In fact, the majority of the desktops in the office
run Linux (Ubuntu as it happens) but people who have a non-technical role use
Windows. They could use Linux but Windows is better suited to their role.</p>
<p>This is not necessarily a cost-saving decision. Sure, we have saved thousands of
pounds by sticking to Linux instead of using Windows but that is a co-incidence
more than anything. In some ways, it is a testament to the skillset of the
people who work at Kraya that they are comfortable with Linux. The mindset of
Linux is in alignment with the mindset of a developer.</p>
<p>I used to develop in Windows and I often found myself fighting with Windows,
whereas with Linux, it just fits. There are several reasons for this. One being
that Linux forces you to understand what you (trying to ) do to a bit more depth
instead of pretending its magically taken care of.</p>
<p>I am not, for one moment implying that developers who use or develop on the
Windows platform is inferior or not as skilled. Simply that my experience was
that the Windows platform made it easier to do things badly and more difficult
to do things well.</p>
<p>Microsoft has done wonders in bringing technology to the masses and making it
more accessible. However, there is still a massive barrier, even for people
specifically in the technology sector to appreciate and use technologies which
require a bit more experience or knowledge to use appropriately.</p>
<p>There are a couple of really good examples. PostgreSQL is a powerful outstanding
database server that can easily compete with Microsoft SQL Server and Oracle.
However, very few people know about it and even fewer use it.</p>
<p>MySQL on the other hand is also an open source database server but is much more
widely used and accepted.</p>
<p>It surprises me when MySQL is used when PostgreSQL is, from a technical
perspective better suited. MySQL is faster than PostgreSQL at the cost of poor
transaction managment (at best). For any system where data integrity is even
remotely important, PostgreSQL is a better choice. However, since there are
better GUI tools for MySQL and since it is easier to get the hang of, it gets
chosen.</p>
<p>This give technology and people in that sector a bad name. Every tool or
software has its place, and should be used in an environment where its strengths
are displayed, not its weaknesses. We have instances where we use multiple
database servers within one project. PostgreSQL for all the data integrity
sensitive areas and MySQL for the speed sensitive areas. Sometimes you want
integrity and speed. In these cases, you have to make a choice based on which is
more important or layer the databases to use the strengths of both.</p>
<p>Metaphorically speaking, MySQL is a hammer, and PostgreSQL is a sledgehammer.
Would you use a sledgehammer to crack a nut, or a hammer to crack a slab of
concrete?</p>
<p>Before someone jumps down my throat, I am not suggesting that PostgreSQL is
better than MySQL or vice versa - just that they both have different goals,
different strengths and weaknesses. They have spent a lot of effort to converge
and strengthen their weaknesses but not matter the amount of convergence, their
core goals are still different that they will never truly be able to remove
their weaknesses without giving up some of their strengths as well. One tool
cannot be both a hammer and a sledgehammer&hellip;</p>]]></content:encoded></item><item><title>On top of Tasktop</title><link>https://icle.es/2009/01/13/on-top-of-tasktop/</link><pubDate>Tue, 13 Jan 2009 13:43:56 +0000</pubDate><guid>https://icle.es/2009/01/13/on-top-of-tasktop/</guid><description>&lt;p>My post about
&lt;a href="http://drone-ah.com/2008/12/13/your-time/" title="Your Time [words on sand]">tracking time&lt;/a>
attracted the attention of &lt;a href="http://tasktop.com/" title="Tasktop">Tasktop&lt;/a>. While this
had been mentioned to me before, I was &lt;strong>mistakenly&lt;/strong> under the impression that
this was a windows only app.&lt;/p>
&lt;p>I was pleased to find out that this was also available for linux. Great... Lets
try it out.&lt;/p>
&lt;p>First stumbling block is the requirement to register on the website before I can
download a trial. I am a firm believer of try before you buy. I should be able
to register but it should be entirely my choice.&lt;/p>
&lt;p>I am more comfortable with registering before buying or for the use of a free
piece of software. However, registering for a trial always irritates me. This
was also the case when I wanted to trial InDesign / Illustrator the other day.&lt;/p></description><content:encoded><![CDATA[<p>My post about
<a href="http://drone-ah.com/2008/12/13/your-time/" title="Your Time [words on sand]">tracking time</a>
attracted the attention of <a href="http://tasktop.com/" title="Tasktop">Tasktop</a>. While this
had been mentioned to me before, I was <strong>mistakenly</strong> under the impression that
this was a windows only app.</p>
<p>I was pleased to find out that this was also available for linux. Great... Lets
try it out.</p>
<p>First stumbling block is the requirement to register on the website before I can
download a trial. I am a firm believer of try before you buy. I should be able
to register but it should be entirely my choice.</p>
<p>I am more comfortable with registering before buying or for the use of a free
piece of software. However, registering for a trial always irritates me. This
was also the case when I wanted to trial InDesign / Illustrator the other day.</p>
<p>After registering, there was the irritating wait for the email to arrive. Now,
this is irritating. When I want something, I want it <strong><em>NOW</em></strong>. I hate waiting.
Adobe did not make me wait for the confirmation email of registration before
downloading the trials. There are two good reasons as to why this irritates me.</p>
<ol>
<li>Email, as reliable as it is generally, can take time. In theory, this can be
anywhere from a few seconds to hours. How about if my mail server is
currently down. Or even more importantly, what if I have shut down my mail
client so that it does not keep distracting me from something that I am
trying to do. Opening up my mail client, I now want to find out about the
other emails that are in my inbox and whether any of them require an
action...</li>
<li>I have reluctantly provided details about myself. Confirming my email
address before I am allowed to download a trial suggests that Tasktop does
not trust me enough to just let me download the trial. The software has
started off on the wrong foot. How much of an issue is it really if someone
gave the wrong details before downloading a trial. Is it really that
important that you are able to keep bugging them via email to buy the
product?</li>
</ol>
<p>I was curious enough to jump through the hoops to download the product. The
first thing I noticed is that there is no 64bit for Linux :-(. More steps
involved in installing this on my 64bit machine. So instead, I installed it one
of my 32bit machines - save time.</p>
<p>Once the download completed, the steps on the website suggested that I needed to
configure it (with ./configureTasktop.sh) and then run Tasktop. The
configuration step required no input from the user and outputted nothing. I have
to ask:</p>
<ol>
<li>Why is the configuration step not integrated into Tasktop and configured to
run once? Alternatively,</li>
<li>Why does the configuration step, not start Tasktop right after.</li>
<li>Even better: Make Tasktop a symlink to configureTasktop.sh, which then
relinks that to the Tasktop Binary with the configureTasktop running Tasktop
right after. This means that from the users perspective, they are always
running the same command, and you save any cost associated with run once
checks.</li>
</ol>
<p>I finally got Tasktop to run and it asks me if I want to install the firefox
addon to integrate with Tasktop. I want to see how it integrates, so I do. Of
course, this is yet <strong>another</strong> step.</p>
<p>A restart later, I was ready to try out Tasktop - or was I? We use bugzilla to
track tasks and I wanted to integrate that in similar to how I do it in Eclipse.
This was also trickier than I expected.</p>
<p>I went into the partner connectors section which did not cover bugzilla, which I
assumed meant that it came with Bugzilla integration by default. This is true
but how the hell do I get there to configure it. It took me a little while to
find the configuration section (there are no menus). Once I was there, I wanted
to get back to the original layout which was tricky since the &quot;close
configuration&quot; button was nicely hidden away up at the top right.</p>
<p>Once I had this working, I tried out the active/deactive mechanisms and this
works just the same as in Eclipse. Except with the Firefox plugin, it adds in
the links that you browse as part of your context - GREAT!</p>
<p>Add in a task to blog about it and went through writing half the document, then
decided to de-activate it before I started working on something else. All the
firefox tabs were closed - again, great...</p>
<p>The problem is that when you re-activate the context, it just clears the tabs in
firefox and shows you the links you last had open. The page titles for the pages
that I had open were the same for a few, so going through them trial and error
to get to the blog post was tricky. More importantly, the cookie was already
gone and I had to re-login. This might be a timeout issue with Wordpress so wont
tag that against Tasktop.</p>
<p>I haven't tried linking folders / files yet but considering that with the above
process taking me more time than I expected due to the sheer number of steps
involved, I shall have to leave that to another day. In all honesty, it might
never happen.</p>
<p>I do like the time logging feature of Tasktop as it tells me which tasks I spent
my time on in different chart formats. This is great. However, I have a problem
in that this is on an individual basis. I see nothing on here about how a team
leader can link in Tasktop used by the team to calculate total time spent on a
project / task. This is a necessary feature for a tool like this in the team
environment.</p>
<p>It is possible that all of this is easier in a windows environment. Possibly
because it was built on there, but more likely because Windows users are used to
taking several steps to achieve something (what is it - 7 clicks to delete a
file in Vista?)</p>
<p>Having ranted on for a while, dont get me wrong. I think that Tasktop is a
fantastic concept and with a bunch of tweaking can be a very intuitive tool to
use. However, at the stage that it is in, it does not do what I need it to do.
It is actually more obtrusive than useful (e.g. by removing all my tabs from
firefox when switching out of a context and not re-instating them on going back
to the context).</p>
<p>Then, it is probably just because I simply expect too much... :-(</p>]]></content:encoded></item><item><title>Building A Website</title><link>https://icle.es/2009/01/12/building-a-website/</link><pubDate>Mon, 12 Jan 2009 00:39:12 +0000</pubDate><guid>https://icle.es/2009/01/12/building-a-website/</guid><description>&lt;p>Most people would think that building a good website is straightforward and it
was. A few years ago, when the web was still relatively new, it was easy enough
to put together a designer and a developer and you could get a reasonable
website as the end product.&lt;/p>
&lt;p>However, in the modern age of websites, this kind of a websites simply does not
cut the mustard. It is of course adequate, but simply feels a little lacking.&lt;/p>
&lt;p>There are several websites that I have recently come across that excel in
design - they have fantastic design but when it falls down when it comes to
usability or functionality. The websites of some graphic design agencies are
prime examples of this.&lt;/p>
&lt;p>On the other hands, we have highly functional websites with a wide range of
features and functionality. The website might even be attractive but fails
terribly in terms of usability. &lt;a href="http://www.sf.net" title="Sourceforge">sourceforge&lt;/a>
is a very good example of this. I used to use it a lot a few years ago but its
usability has gotten worse in the last few years, not to mention the fact that
it seems to have slowed to a crawl. I still use sourceforge now and again to
look up pieces of software but I don't look forward to it.&lt;/p></description><content:encoded><![CDATA[<p>Most people would think that building a good website is straightforward and it
was. A few years ago, when the web was still relatively new, it was easy enough
to put together a designer and a developer and you could get a reasonable
website as the end product.</p>
<p>However, in the modern age of websites, this kind of a websites simply does not
cut the mustard. It is of course adequate, but simply feels a little lacking.</p>
<p>There are several websites that I have recently come across that excel in
design - they have fantastic design but when it falls down when it comes to
usability or functionality. The websites of some graphic design agencies are
prime examples of this.</p>
<p>On the other hands, we have highly functional websites with a wide range of
features and functionality. The website might even be attractive but fails
terribly in terms of usability. <a href="http://www.sf.net" title="Sourceforge">sourceforge</a>
is a very good example of this. I used to use it a lot a few years ago but its
usability has gotten worse in the last few years, not to mention the fact that
it seems to have slowed to a crawl. I still use sourceforge now and again to
look up pieces of software but I don't look forward to it.</p>
<p>Then you have the rare gems, that are exceptionally usable and functional.
<a href="http://www.google.co.uk" title="Google">Google</a> is an excellent example of this. Note
however, that the design of google in minimal.</p>
<p>Having worked in the web for numerous years and having used more websites than I
could possibly count, I strongly feel that the medium that is the web is heavily
under-utilised.</p>
<p><a href="http://www.facebook.com" title="Facebook">Facebook</a> is a good example of some of the
good things you can do with web. Things just feel a lot more natural. If you
take the news feed, you can hover over an item to see the menu at the top right
that lets you set your preferences for that particular item.</p>
<p>Same with your wall, hover over an item on your wall, and you see a menu option,
click on it and you get relevant options.</p>
<p>This is a simple and minor thing. However, this brings in the concept of context
and I think that context is largely ignored in all applications. However, it
should be easier and much more useful to have context sensitive commands /
functionality within websites.</p>
<p>Now, If facebook was to take it one step further and allow you to right click
anywhere on a news item and then choose one of the options, that would be even
better - save me from moving the mouse to the menu.</p>
<p>Another excellent thing Facebook has done is provide the ability to comment on
most things that someone does. Social interaction can take a website from zero
to hero in an instant. How can you allow your customers / visitors to interact
with each other. Even better - Can your website integrate with Facebook and
allow your visitors / customers to use the interaction capabilities of Facebook
to drive your site further?</p>]]></content:encoded></item><item><title>Proprietary FSF</title><link>https://icle.es/2009/01/01/proprietary-fsf/</link><pubDate>Thu, 01 Jan 2009 20:24:16 +0000</pubDate><guid>https://icle.es/2009/01/01/proprietary-fsf/</guid><description>&lt;p>I have always a big fan and proponent of the FSF and having recently been
interested in researching for a project came across a document covering
&lt;a href="http://www.gnu.org/licenses/why-not-lgpl.html" title="Why Not LGPL">Why you shouldn't use the Lesser GPL for your next library&lt;/a>&lt;/p>
&lt;p>What the document basically suggests is to limit what proprietary software
developers can do by licensing libraries as GPL instead of LGPL.&lt;/p>
&lt;p>This is no longer free(as in speech, not beer) software. Why?&lt;/p>
&lt;p>Freedom means the ability to use something without restriction. If I cannot use
a library in a proprietary product, that is removing an important freedom.&lt;/p>
&lt;p>This attitude is likely to alienate the &amp;quot;commercial&amp;quot; or proprietary developers
further from FSF/GNU.&lt;/p></description><content:encoded><![CDATA[<p>I have always a big fan and proponent of the FSF and having recently been
interested in researching for a project came across a document covering
<a href="http://www.gnu.org/licenses/why-not-lgpl.html" title="Why Not LGPL">Why you shouldn't use the Lesser GPL for your next library</a></p>
<p>What the document basically suggests is to limit what proprietary software
developers can do by licensing libraries as GPL instead of LGPL.</p>
<p>This is no longer free(as in speech, not beer) software. Why?</p>
<p>Freedom means the ability to use something without restriction. If I cannot use
a library in a proprietary product, that is removing an important freedom.</p>
<p>This attitude is likely to alienate the &quot;commercial&quot; or proprietary developers
further from FSF/GNU.</p>
<p>In fact, doing this is just not fair and not in line with how I view is the
concept behind the FSF. The point is to write software / libraries and share
that with the world so others may build upon what you have done. Stand on the
shoulders of giants in a way...</p>
<p>It makes perfect sense for software to be GPL since you don't want somebody to
pick up a GPL software, build something on top, and sell it without source.</p>
<p>However, if libraries are released under the GPL instead of LGPL, it means that
I can not link against that library to write a non-GPL compatible application.</p>
<p>The <a href="http://www.gnu.org/" title="The GNU Operating Sytem">GNU Website</a> states</p>
<blockquote>
<p>&quot;Free software is a matter of the users' freedom to run, copy, distribute,
study, change and improve&quot;</p></blockquote>
<p>Additionally, the
<a href="http://www.gnu.org/licenses/quick-guide-gplv3.html" title="A Quick Guide To GPLv3">Quick Guide to GPLv3</a>
states that</p>
<blockquote>
<p>Nobody should be restricted by the software they use. There are four freedoms
that every user should have:</p></blockquote>
<ul>
<li>the freedom to use the software for any purpose,</li>
<li>the freedom to change the software to suit your needs,</li>
<li>the freedom to share the software with your friends and neighbors, and</li>
<li>the freedom to share the changes you make.</li>
</ul>
<p>This has always been my impressing of the purpose of GPL. Now, how does this
work with Libraries? A little differently... :-(</p>
<p>From my perspective, if I have the freedom to use the [library] for any
purpose, that means that I can write an application that <strong><em>uses</em></strong> that library
without having to worry about licensing issue.</p>
<p>However, this is not the case. There is a clause that states that the software
cannot be used in a larger software project that has a license incompatible with
the GPL. This includes linking the library into another software application.</p>
<p>Therefore, I do not have the freedom to use the software <strong><em>for any purpose</em></strong>.</p>
<p>Freedom cannot be uni-directional. If GNU/FSF are trying to muscle out
developers of proprietary software, all they are doing is alienating themselves
further...</p>
<p>I run a technology firm that uses a heck of a lot of open source software. In
fact, I am posting this from an ubuntu desktop running firefox from a VServer. I
am probably using a dozen open source applications to do this simple
straightforward act.</p>
<p>There is in fact, not a simple closed source application at any point through
this.</p>
<p>The main problem that I see with this is that it makes Open Source so much more
zealot(ous) and FSF, GNU and OSS becomes fundamentalists. The attitude is not
one of freedom and inclusion but of exclusivity and marginalisation.</p>
<p>The worst part is the price that is asked of developers who want to use an Open
Source library. The price is the acceptance and propogation of an idea (Freedom
or else).</p>
<p>Compared to the cost of conversion to another idealogy (Free Software Idealogy),
the cost of a few hundred, thousand, or even millions of dollars / pounds for a
piece of software seems dirt cheap.</p>
<p>I understand that each developer has the freedom to choose which license to use
for their products/libraries. My question is how can an organisation that claims
to be a proponent of freedom encourage the removal of freedoms?</p>
<p>I would like to ask how this shift is any different from religious fanatics who
tell you that their god is the one true god and there is nothing else.</p>]]></content:encoded></item><item><title>X11 Remote Applications Responsiveness</title><link>https://icle.es/2008/12/30/x11-remote-applications-responsiveness/</link><pubDate>Tue, 30 Dec 2008 15:01:43 +0000</pubDate><guid>https://icle.es/2008/12/30/x11-remote-applications-responsiveness/</guid><description>&lt;p>As a developer, I use eclipse a lot&amp;hellip; We have a powerful server that off which
eclipse is run which allows us to keep the desktops at a much lower spec. In
general, this works well for us.&lt;/p>
&lt;p>However, recently, I have been niggled by the amount of time it takes to switch
perspectives on eclipse. It takes a good 4 seconds to switch between
perspectives.There is also a noticeable lag when performing some operations.&lt;/p></description><content:encoded><![CDATA[<p>As a developer, I use eclipse a lot&hellip; We have a powerful server that off which
eclipse is run which allows us to keep the desktops at a much lower spec. In
general, this works well for us.</p>
<p>However, recently, I have been niggled by the amount of time it takes to switch
perspectives on eclipse. It takes a good 4 seconds to switch between
perspectives.There is also a noticeable lag when performing some operations.</p>
<p>To resolve this, I spent a lot of time looking at the linux real-time and
low-latency patches. I had expected that running X11 applications remotely would
not cause a bottleneck over a gigabit link. Turns out that I was wrong.</p>
<p>To test this, I ran a vnc server on the application server and found that
switching perspectives on there was super fast.</p>
<p>To be able to resolve this, the first thing to do was to remove any latency put
on the X-&gt;X communication by ssh.</p>
<p>We use gdm, so I had to enable to TCP on there first. Do this using the
following config line in <code>/etc/gdm/gdm.conf</code></p>
```
DisallowTCP=false
```
<p>Restart gdm</p>
<p>on the remote host, export DISPLAY</p>
```
export DISPLAY=<yourhost>:0
```
<p>and run your application.</p>
<p>I found the application to be a lot more responsive after this. I didn&rsquo;t have to
worry about X auth since we have nfs mounted home. If you don&rsquo;t, check
<a href="http://www.xs4all.nl/~zweije/xauth.html" title="Remote X Apps Mini HowTo">this mini howto</a></p>
]]></content:encoded></item><item><title>Maven2, EJB3 and JBoss</title><link>https://icle.es/2008/12/28/maven2-ejb3-and-jboss/</link><pubDate>Sun, 28 Dec 2008 18:53:50 +0000</pubDate><guid>https://icle.es/2008/12/28/maven2-ejb3-and-jboss/</guid><description>&lt;p>I started work on a project called InVision about a year ago but have probably
spent about a week or two worth of effort on it in total&amp;hellip; :-(&lt;/p>
&lt;p>The Project aim was to bring together the easy time logging capabilities of
&lt;a href="http://processdash.sourceforge.net/" title="The Software Process Dashboard Initiative">Process Dashboard&lt;/a>
along with the project management capabilities of Microsoft Project (including
the Server Component). It is also to be integrated into our request tracking
System - &lt;a href="http://bestpractical.com/rt/" title="Request Tracker">Request Tracker&lt;/a>.
Eventually, it is also to integrate with our accounting system and turn into an
ERP (Enterprise Resource Planning) system and MIS (Management Information
System). There are plans to integrate with our Wiki and our Document Management
System too.&lt;/p>
&lt;p>But these are all lofty goals.  One of our recent projects introduced me to the
&lt;a href="http://www.springframework.net/" title="Spring.NET Application Framework">Spring Framework&lt;/a>.
While I am still not a fan of Spring, the scale of the project and the way of
approaching it gave me some ideas and additional tools to work with. I wanted to
bring these into the InVision Project.&lt;/p>
&lt;p>The key one here was Maven 2. InVision already used EJB3 and JBoss (4.2 as it
happened). There was one additional issue for me to resolve and that was out of
container testing. Something that is very easy to do with Spring but a little
more troublesome with EJB3 since it doesn&amp;rsquo;t have an out of container
framework&amp;hellip;&lt;/p></description><content:encoded><![CDATA[<p>I started work on a project called InVision about a year ago but have probably
spent about a week or two worth of effort on it in total&hellip; :-(</p>
<p>The Project aim was to bring together the easy time logging capabilities of
<a href="http://processdash.sourceforge.net/" title="The Software Process Dashboard Initiative">Process Dashboard</a>
along with the project management capabilities of Microsoft Project (including
the Server Component). It is also to be integrated into our request tracking
System - <a href="http://bestpractical.com/rt/" title="Request Tracker">Request Tracker</a>.
Eventually, it is also to integrate with our accounting system and turn into an
ERP (Enterprise Resource Planning) system and MIS (Management Information
System). There are plans to integrate with our Wiki and our Document Management
System too.</p>
<p>But these are all lofty goals.  One of our recent projects introduced me to the
<a href="http://www.springframework.net/" title="Spring.NET Application Framework">Spring Framework</a>.
While I am still not a fan of Spring, the scale of the project and the way of
approaching it gave me some ideas and additional tools to work with. I wanted to
bring these into the InVision Project.</p>
<p>The key one here was Maven 2. InVision already used EJB3 and JBoss (4.2 as it
happened). There was one additional issue for me to resolve and that was out of
container testing. Something that is very easy to do with Spring but a little
more troublesome with EJB3 since it doesn&rsquo;t have an out of container
framework&hellip;</p>
<p>I have grown to be a big fan of Maven 2 and using Maven 2 to configure an EJB
project is not as easy or straightforward as I would have liked: I wanted to
separate the whole project into four parts</p>
<ul>
<li>Domain Model (or just the entity beans); Also referred to as a Hibernate
Archive (HAR)</li>
<li>Stateful/Stateless Beans (Just the Beans, since I don&rsquo;t consider entities
beans in EJB3)</li>
<li>Application Client (J2SE Application)</li>
<li>Web App (Using SEAM)</li>
<li>I would also need an EAR project to deploy the DomainModel, Beans &amp; WebApp as
one pacakge into JBoss.</li>
</ul>
<p>I have not got as far as the SEAM project yet but the other ones were
straightforward enough to set up with Maven 2.</p>
<p>Both the Domain Model and the Beans project had to be set up as ejb projects and
use the maven-ejb-plugin</p>
```xml
 <build>
     <plugins>
         <plugin>
             <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-ejb-plugin</artifactId>
             <configuration>
                 <ejbVersion>3.0</ejbVersion>
             </configuration>
         </plugin>
     </plugins>
 </build>
```
<p>I set up the persistence context within the Domain Model</p>
```xml
<persistence-unit name="em">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>java:/datasource</jta-data-source>
</persistence-unit>
```
<p>I could then reference the context from the Beans project by injecting it with</p>
```java
@PersistenceContext(unitName="em")
```
<p>Easy enough!</p>
<p>Now configuring the EAR project: This was configured as an ear package which
depended on the other two projects with the following configuration</p>
```
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ear-plugin</artifactId>
<configuration>
<version>5</version>
 <modules>
 <ejbModule>
 <groupId>uk.co.kraya.invision</groupId>
 <artifactId>beans</artifactId>
 </ejbModule>
 <ejbModule>
 <groupId>uk.co.kraya.invision</groupId>
 <artifactId>DomainModel</artifactId>
 </ejbModule>
 </modules>
 <jboss>
 <version>4.2</version>
 <data-sources>
 <data-source>invision-ds.xml</data-source>
 </data-sources>
 </jboss>
 </configuration>
 </plugin>
 <plugin>
 <groupId>org.codehaus.mojo</groupId>
 <artifactId>jboss-maven-plugin</artifactId>
 <configuration>
 <jbossHome><jboss-home-path></jbossHome>
 <hostName><hostname></hostName>
 <port>8080</port>
 </configuration>
 </plugin>
 </plugins>
 </build>
```
<p>With this configured, from the EAR project, I could do mvn ear:deploy to deploy
to JBoss.</p>
<p>Additionally, within eclipse, I created a new run-type that ran ear:undeploy
package ear:deploy to re-deploy the package to JBoss. Works a treat</p>
<p>There are still a few kinks to be ironed out.</p>
<p>I still need to install (mvn install) the two projects before the EAR will pick
it up to deploy. I need to get the ear re-deploy to re-build the other projects.
Something to look at another day.</p>
<p>I had manually deployed the DataSource file to JBoss. It might be possible to do
this via Maven.</p>
<p>I also very much liked the Eclipse automatic deploy feature. It is possible to
use the eclipse plugin on maven to get Eclipse to identify this as a JBoss
deployable project but I ran into some problems and gave up. Ideally, Eclipse
would auto-deploy the project.</p>
<p>However, the above is less relevant once Out-Of-Container testing is in place.
Now, this does work, but I will leave that to another day&hellip;</p>]]></content:encoded></item><item><title>Eclipse TPTP on Ubuntu (64bit)</title><link>https://icle.es/2008/12/28/eclipse-tptp-on-ubuntu-64bit/</link><pubDate>Sun, 28 Dec 2008 18:15:45 +0000</pubDate><guid>https://icle.es/2008/12/28/eclipse-tptp-on-ubuntu-64bit/</guid><description>&lt;p>I run ubuntu 64 bit (technically, I run an ubuntu 64bit vserver which I access
from ubuntu 32 bit but thats not really relevant).&lt;/p>
&lt;p>In the open source world, I expect that all things which are accessible as 32bit
are also accessible and 64bit and ubuntu makes it automagic enough that
everything just works. Yes, I run into problems with closed source software like
Flash Player (recently resolved with flash player 10) and the Java Plugin but
that is another story. I use Eclipse and wanted to do some performance analysis
and benchmarking to find a bottleneck and installed the TPTP plugin; and ran
into a problem. It just didn&amp;rsquo;t work.&lt;/p></description><content:encoded><![CDATA[<p>I run ubuntu 64 bit (technically, I run an ubuntu 64bit vserver which I access
from ubuntu 32 bit but thats not really relevant).</p>
<p>In the open source world, I expect that all things which are accessible as 32bit
are also accessible and 64bit and ubuntu makes it automagic enough that
everything just works. Yes, I run into problems with closed source software like
Flash Player (recently resolved with flash player 10) and the Java Plugin but
that is another story. I use Eclipse and wanted to do some performance analysis
and benchmarking to find a bottleneck and installed the TPTP plugin; and ran
into a problem. It just didn&rsquo;t work.</p>
<p>To resolve it, I turned to google&hellip; In this instance, it turned out to be a
distraction and a red-herring. It lead me in the direction of installing
libstdc++2.10-glibc2.2_2.95.4-27_i386.deb which was difficult at best since
there was only a 32bit version of the package and that wasn&rsquo;t even in the
standard repository.</p>
<p>In the end, digging deeper, I found that it simply missed the following shared
object libstdc++.so.5.</p>
<p>All I had to do was install libstdc++5:</p>
```bash
sudo aptitude install libstdc++5
```
<p>and it worked&hellip; :-D</p>
<p>Now, I think that ACServer which Eclipse uses to do TPTP should not link to an
outdated library but that is another issue&hellip;</p>
]]></content:encoded></item><item><title>Hibernate Domain Model Testing</title><link>https://icle.es/2008/12/23/hibernate-domain-model-testing/</link><pubDate>Tue, 23 Dec 2008 22:14:42 +0000</pubDate><guid>https://icle.es/2008/12/23/hibernate-domain-model-testing/</guid><description>&lt;p>One of my pet peeves with Hibernate has always been how difficult it was to test
it. I want to test the persistence of data, loading the data back and any
specific funtionality with the domain model.&lt;/p>
&lt;p>Simple? NO! The main problem was the management of the data set. I had set up,
in the past fairly interesting classes to test the functionality using
reflection, and injecting the data from the classes themselves through the data
provider mechanism of &lt;a href="http://testng.org/d" title="TestNG">TestNG&lt;/a>. However, this was
error prone and clunky at best. It also made dependency management of data quite
cumbersome.&lt;/p>
&lt;p>With a view to resolving this, I also looked at
&lt;a href="http://dbunit.sourceforge.net/" title="DbUnit">DbUnit&lt;/a>,
&lt;a href="http://unitils.org/" title="Unitils">unitils&lt;/a> and
&lt;a href="http://ejb3unit.sourceforge.net/" title="Ejb3Unit">Ejb3Unit&lt;/a>. They all did some
things that I liked but lacked some functionality that was important.&lt;/p></description><content:encoded><![CDATA[<p>One of my pet peeves with Hibernate has always been how difficult it was to test
it. I want to test the persistence of data, loading the data back and any
specific funtionality with the domain model.</p>
<p>Simple? NO! The main problem was the management of the data set. I had set up,
in the past fairly interesting classes to test the functionality using
reflection, and injecting the data from the classes themselves through the data
provider mechanism of <a href="http://testng.org/d" title="TestNG">TestNG</a>. However, this was
error prone and clunky at best. It also made dependency management of data quite
cumbersome.</p>
<p>With a view to resolving this, I also looked at
<a href="http://dbunit.sourceforge.net/" title="DbUnit">DbUnit</a>,
<a href="http://unitils.org/" title="Unitils">unitils</a> and
<a href="http://ejb3unit.sourceforge.net/" title="Ejb3Unit">Ejb3Unit</a>. They all did some
things that I liked but lacked some functionality that was important.</p>
<p>This led me to write a simple testing infrastructure. The goal was
straightforward.</p>
<ul>
<li>I need to be able to define data in a CSV (actually it was seperated by the
pipe character |, so PSV) based on entities.</li>
<li>The framework should automatically persist the data (and fail on errors)</li>
<li>It should test that it can load all that data back</li>
<li>It should run as many automated tests on the DOM as possible.</li>
</ul>
<p>The framework uses the CSV files to read the data for each of the classes (using
the excellent <a href="http://supercsv.sourceforge.net/" title="SuperCsv">SuperCsv</a> library).
It needs an Id field for internal reference. As long as the id&rsquo;s match within
the CSV files for the relationships, it will be persisted correctly into the
database even when the persisted id&rsquo;s are different.</p>
<p>For example, I could have a Contact.csv with 5 records (ids 1 through 5) and a
Company.csv with 3 records (ids 1 through 3).</p>
<p>The Contact.csv records can map to the id specified in the Company.csv file and
when the records get persisted, they will be associated correctly, even if the
id&rsquo;s in the database end up being different.</p>
<p>The framework also looks for the CSV file which has the same name as the class
within the location defined within the configuration file. This means that as
long as the filename matches the class name, the data loading is automatic.</p>
<p>For simple classes, the Test case is as simple as:</p>
```java
public class CompanyTest extends DOMTest<Company> {

public CompanyTest() { super(Company.class); } }
```
<p>The system (with the help of testNG) is also easily flexible to define object
model dependencies. Just override the persist method (which just calls the
super.persist) and define the groups to be persist and <code>&lt;object&gt;.persist</code></p>
<p>in this particular case, it would be</p>
```java
@override
@Test(groups={"persist", "Company.persist"}
public void persist() {
    super.persist();
}
```
<p>For all dependent classes, I then depend on the Company.persist group (For the
ContactTest class for example, since it needs to link to the Company object)</p>
<p>You can specify OneToOne and ManyToOne relationships with just the CSV files -
just defining the field name and the id of the object to pull in.</p>
<p>ManyToMany is more complex and requires an interim object to be created within
the test section. If the Contact to Company relationship above was ManyToMany,
we would create a ContactCompany class with just the two fields - Contact &amp;
Company, then create a csv file with three fields, id, Contact, &amp; Company. The
framework currently always needs an id field.</p>
<p>You would then need to write a method within the ContactTest or CompanyTest(I
use the owning side) to read the CSV file in and pump the data. This process is
a little bit complex just now.</p>
<p>With an appropriate amount of test data, you are able to write a test suite that
can consistently test your domain model. More importantly, you can configure it
to drop the database at the start of each run so that once the tests are
complete, you have a database structure and data than can be used for testing of
higher level components (EJB/Spring/UI/WebApp)</p>
<p>We currently use this framework to test the domain model as well as distribute a
data set for development and testing of the higher tier functionalities.</p>
<p>For the future, there are several additional features this framework needs:</p>
<ul>
<li>It currently needs the setters/getters &amp; constructors to be public. This needs
to be FIXED</li>
<li>Refactor the ManyToMany Relationship code to make it easier and simpler to
test and pump data</li>
<li>See if we can ensure that additional tests which data is done within a
transaction and rolled back so that the database is left in the &ldquo;CSV Imported&rdquo;
state on completion of tests</li>
<li>Easier Dependency management if possible</li>
</ul>
<p>This framework is still inside the walls of Kraya, but once the above issues are
resolved and it is in a releasable state, it will be published into the open
source community. If you are interested in getting a hold of it, email me and
I&rsquo;ll provide you with the latest version.</p>
<p>The easier and quicker it is to test, the more time we can spend on writing
code&hellip; :-) The higher the coverage of the tests, the more confident you can be
of your final product.</p>
<p>To more testing&hellip;</p>]]></content:encoded></item><item><title>Breaking Software Down</title><link>https://icle.es/2008/12/15/breaking-software-down/</link><pubDate>Mon, 15 Dec 2008 15:53:43 +0000</pubDate><guid>https://icle.es/2008/12/15/breaking-software-down/</guid><description>&lt;p>&lt;a href="http://www.codinghorror.com/blog/archives/000987.html" title="Tending Your Software Garden">Jeff Atwood likens software development to tending a garden.&lt;/a>
I can relate to this. In fact, I would like to ask, if you have a nice plant in
one of your gardens, how complicated is it to &amp;ldquo;copy&amp;rdquo; that across to another one?&lt;/p>
&lt;p>I realise that I am moving away from the analogy here but there is an important
concept here. Libraries were born out of the desire to share and distribute code
to be re-used.&lt;/p>
&lt;p>The idea for
&lt;a href="http://en.wikipedia.org/wiki/Remote_procedure_call" title="Remote Procedure Call">Remote Procedure Calls&lt;/a>
dates as far back as 1976. Microsoft brought along
&lt;a href="http://en.wikipedia.org/wiki/Object_Linking_and_Embedding" title="Object Linking and Embedding">OLE&lt;/a>
and then
&lt;a href="http://en.wikipedia.org/wiki/Component_Object_Model" title="Component Object Model">COM&lt;/a>
made this more generic and better.&lt;/p>
&lt;p>RPC is widely in use these days and there are several other mechanisms for inter
process communication including
&lt;a href="http://en.wikipedia.org/wiki/CORBA" title="Common Object Request Broker Architecture">CORBA&lt;/a>,
&lt;a href="http://en.wikipedia.org/wiki/REST" title="Representational State Transfer">REST&lt;/a> &amp;amp;
&lt;a href="http://en.wikipedia.org/wiki/SOAP_%28protocol%29" title="Simple
Object Access Protocol">SOAP&lt;/a>.&lt;/p></description><content:encoded><![CDATA[<p><a href="http://www.codinghorror.com/blog/archives/000987.html" title="Tending Your Software Garden">Jeff Atwood likens software development to tending a garden.</a>
I can relate to this. In fact, I would like to ask, if you have a nice plant in
one of your gardens, how complicated is it to &ldquo;copy&rdquo; that across to another one?</p>
<p>I realise that I am moving away from the analogy here but there is an important
concept here. Libraries were born out of the desire to share and distribute code
to be re-used.</p>
<p>The idea for
<a href="http://en.wikipedia.org/wiki/Remote_procedure_call" title="Remote Procedure Call">Remote Procedure Calls</a>
dates as far back as 1976. Microsoft brought along
<a href="http://en.wikipedia.org/wiki/Object_Linking_and_Embedding" title="Object Linking and Embedding">OLE</a>
and then
<a href="http://en.wikipedia.org/wiki/Component_Object_Model" title="Component Object Model">COM</a>
made this more generic and better.</p>
<p>RPC is widely in use these days and there are several other mechanisms for inter
process communication including
<a href="http://en.wikipedia.org/wiki/CORBA" title="Common Object Request Broker Architecture">CORBA</a>,
<a href="http://en.wikipedia.org/wiki/REST" title="Representational State Transfer">REST</a> &amp;
<a href="http://en.wikipedia.org/wiki/SOAP_%28protocol%29" title="Simple
Object Access Protocol">SOAP</a>.</p>
<p>I don&rsquo;t think software is broken down into small enough components. *nix is
great in that you can tag a whole bunch of commands together on the command line
to do some amazing things. I have personally piped data through a dozen or so
commands and scripts to do some interesting things.</p>
<p>If we could break everything down into individual components that could be
linked together, we would have a massive arsenal of interoporable tools that
each user can pick and choose to put together very powerful solutions.</p>
<p>How many times have you found a piece of software that does one thing really
well, but fails in something else. Then found another piece of software that
does the other thing really well.</p>
<p>For example, the extensibility of
<a href="http://www.mozilla.org/firefox" title="Firefox">Firefox</a> is fantastic but I love the
rendering of <a href="http://www.apple.com/safari/" title="Safari Web Browser">Safari</a>. I love
the Contact Management within
<a href="http://projects.gnome.org/evolution/" title="Evolution">Evolution</a> and the Mail
capabilities of <a href="http://www.mozilla.org/thunderbird" title="Thunderbird">Thunderbird</a>.</p>
<p>Why don&rsquo;t we break each software down into each of it&rsquo;s individual components
(and I am not talking about libraries here) and allow them to be deployed as
services usable by other pieces of software.</p>
<p>In other words, release the contact management capabilities of Evolution as a
product of it&rsquo;s own right with a pre-defined API that any application can link
into (including perhaps a web interface). Release the Mail management component
of Thunderbird as a service, Release GUI&rsquo;s as a component. Then we can pick any
GUI we want, link into a specific mail component and another addressbook
component.</p>
<p>Do one thing and do it well. In fact, let&rsquo;s take it one step further and release
a public API for each software component - an API for Mail, one for Contact
Management and so on.</p>
<p>Each software component can then be a black box that delivers this API.</p>
<p>Choice can be a bad thing if it makes it difficult to choose -
<a href="http://subclipse.tigris.org" title="Subclipse">Subclipse</a> vs
<a href="http://www.eclipse.org/subversive" title="Subversive">Subversive</a> is a good example
of this. Let us however, not confuse choice with flexibility.</p>
<p>Let&rsquo;s say that you want to find all the files within a folder modified within
the last 3 days containing the text &ldquo;abracadabra&rdquo; and then replace all
occurences in those files of the world &ldquo;super&rdquo;  with &ldquo;hyper&rdquo;.</p>
<p>To do this in linux, all you would do is chain find (to identify files modified
in the last 3 days), grep (to identify only the files that contain
&ldquo;abracadabra&rdquo;) and sed (to do the replacement).</p>
<p>If you know these commands well enough, you could chain something together in
half a minute or so. You could probably figure out how to do this with the
search tools in Windows within a minute or so but where this really shines is if
there are thousands of files that needs to be processed. With other search
tools, you would have to wait for the original search results to be returned
before running to replace operation. This takes up the users time.</p>
<p>With the chaining of commands, I have run it and worked on something else while
it completes.</p>
<p>Let me visualise a brave new world:</p>
<p>In this world, all software would be interoperable components. For example,
there would be components for:</p>
<ul>
<li>Mail account management (Perhaps genericised into configuration management)</li>
<li>Text composition (usage for mail, documents, plain text et al)</li>
<li>Text reading (again, usable for mail, documents, plain text et al)</li>
<li>Spam Filtering (already available to some extent)</li>
<li>Contact Management (optionally linked into organisation&rsquo;s LDAP server)</li>
<li>Task Management (Standalone
<a href="http://www.eclipse.org/mylyn/" title="Eclipse - Mylyn">Mylyn</a> if you know the
product)</li>
<li>Scheduling (or calendering if you prefer that term)</li>
</ul>
<p>If all of these components were interoperable, then there would a
<a href="http://en.wikipedia.org/wiki/Graphical_user_interface" title="Graphical User Interface">GUI</a>
that is generic and could bring all of these together. In this way, the people
working on each of the components could concentrate on doing one thing and one
thing well.</p>
<p>If we then start working on public API&rsquo;s in a collaborative fashion, each of the
component could be fleshed out to be as flexible and complete as necessary to
gain maximum benefit.</p>
<p>If these components provided the services as a network based API, it would also
allow for the components to be distributed across a network providing redundancy
and efficiency. This makes it easier to turn each desktop into more of dump
terminal concentrating purely on user interaction and getting closer to the
<a href="http://drone-ah.com/2008/12/12/invisible-interface/" title="Invisible Interface">invisible interface.</a></p>
<p>Software as a service has taken a step in the right direction. Can we take a
leap and have software component as a service&hellip;</p>]]></content:encoded></item><item><title>Foxy Web</title><link>https://icle.es/2008/12/14/foxy-web/</link><pubDate>Sun, 14 Dec 2008 01:07:35 +0000</pubDate><guid>https://icle.es/2008/12/14/foxy-web/</guid><description>&lt;p>Since &lt;a href="http://www.mozilla.com/en-US/firefox/" title="Firefox">Firefox&lt;/a> 2.0, I have
never felt a desire to use
&lt;a href="http://www.microsoft.com/windows/products/winfamily/ie/default.mspx" title="Internet Explorer">Internet Explorer&lt;/a>.
There have been times when I have used IE, either out of a need to test a
website on the browser or purely as the first step to downloading Firefox.&lt;/p>
&lt;p>&lt;a href="http://www.w3schools.com/browsers/browsers_stats.asp" title="IE vs Firefox adoption">According to W3C&lt;/a>,
as of November 2008, IE(6/7) dominate 46.6% of the market with Firefox at 44.2%.
Compare this to November 2007 when IE (5/6/7) dominated 56% of the market and
Firefox only had 36.3%&lt;/p>
&lt;p>It is interesting to
&lt;a href="http://www.w3schools.com/browsers/browsers_os.asp" title="OS Statistics">note&lt;/a> that
between Nov 2007 and Nov 2008, Linux adoption (as far as internet browsing is
concerned) went up a meagre .5% from 3.3% to 3.8%.&lt;/p></description><content:encoded><![CDATA[<p>Since <a href="http://www.mozilla.com/en-US/firefox/" title="Firefox">Firefox</a> 2.0, I have
never felt a desire to use
<a href="http://www.microsoft.com/windows/products/winfamily/ie/default.mspx" title="Internet Explorer">Internet Explorer</a>.
There have been times when I have used IE, either out of a need to test a
website on the browser or purely as the first step to downloading Firefox.</p>
<p><a href="http://www.w3schools.com/browsers/browsers_stats.asp" title="IE vs Firefox adoption">According to W3C</a>,
as of November 2008, IE(6/7) dominate 46.6% of the market with Firefox at 44.2%.
Compare this to November 2007 when IE (5/6/7) dominated 56% of the market and
Firefox only had 36.3%</p>
<p>It is interesting to
<a href="http://www.w3schools.com/browsers/browsers_os.asp" title="OS Statistics">note</a> that
between Nov 2007 and Nov 2008, Linux adoption (as far as internet browsing is
concerned) went up a meagre .5% from 3.3% to 3.8%.</p>
<p>This means that a very large proportion of the firefox users are from the
Windows Platform. Why is this impressive? There is technically no reason for a
user on Windows to download Firefox. Windows comes with Internet Explorer, which
should be adequate for all the internet browsing needs.</p>
<p>If 44.2% of all windows users went to the effort to download, install and use
firefox instead of Internet Explorer which comes pre-installed, let me ask the
question - if Windows came pre-installed with Firefox instead of Internet
Explorer - how many would go to the effort of downloading and installing
Internet Explorer.</p>
<p>While it is possible to install Internet Explorer on Linux, it might be a little
unfair to answer this question based on the number of Internet Explorer's
running off linux. People who run linux have proven to be biased against
Microsoft anyway, so it would be a loaded statistic.</p>
<p>While I have no doubt in my mind that Firefox is better than Internet Explorer,
I still don't feel that Firefox is perfect. It still feels far too bulky, with
disproportionate memory usage and it is still not as fast as Safari in terms of
page display.</p>
<p>Sure, the addons and themes functionality is great and useful. However, it would
be nice if it was faster to load, faster to use and just felt more
lightweight... like Safari does.&hellip;</p>
<p>Having said that, I am <strong>not</strong> going to switch to safari. I like the browser but
it is still just <strong>not</strong> as good as firefox.</p>
<p>One of the points of open source software, should be to bring all the benefits
of all the competing pieces of software into one but it just doesnt work like
that. If Firefox had all the benefits of firefox as well as the benefits of
Safari, I am sure the adoption rate would be far higher...</p>
<p>Lets take it one step at a time... I vote for firefox feeling a lot quicker and
snappier for a wishlist... :-)</p>]]></content:encoded></item><item><title>Tinkerbell</title><link>https://icle.es/2008/12/13/tinkerbell/</link><pubDate>Sat, 13 Dec 2008 15:30:06 +0000</pubDate><guid>https://icle.es/2008/12/13/tinkerbell/</guid><description>&lt;p>I am going to veer away from the seriousness of work for a minute (don't laugh
at that) and cover a very important and underappreciated creature that graces
our offices. Used to grace our office a lot but is now rare to find...&lt;/p>
&lt;p>&lt;a href="http://drone-ah.com/files/2008/12/the-mascot.png">
 &lt;img src="%7B%7Bsite.baseurl%7D%7D/assets/2008/12/the-mascot-225x300.png" alt="">

&lt;/a>&lt;/p>
&lt;p>Here is the mascot too shy to show his face. Our mascot loves and cares about
us. Cheers up when we end up working those really late night shifts to rescue a
website or hit a crunch deadline which our clients sometime ask us for. Even
used to bring us food, beer, bubblewrap and good humour.&lt;/p></description><content:encoded><![CDATA[<p>I am going to veer away from the seriousness of work for a minute (don't laugh
at that) and cover a very important and underappreciated creature that graces
our offices. Used to grace our office a lot but is now rare to find...</p>
<p><a href="http://drone-ah.com/files/2008/12/the-mascot.png">
  <img src="%7B%7Bsite.baseurl%7D%7D/assets/2008/12/the-mascot-225x300.png" alt="">

</a></p>
<p>Here is the mascot too shy to show his face. Our mascot loves and cares about
us. Cheers up when we end up working those really late night shifts to rescue a
website or hit a crunch deadline which our clients sometime ask us for. Even
used to bring us food, beer, bubblewrap and good humour.</p>
<p>In serious times of need, the mascot would even dance for us:</p>
<p><a href="http://drone-ah.com/files/2008/12/dancing-mascot.png">
  <img src="%7B%7Bsite.baseurl%7D%7D/assets/2008/12/dancing-mascot-225x300.png" alt="">

{.alignnone .size-medium .wp-image-29 width=&ldquo;225&rdquo; height=&ldquo;300&rdquo;}</a></p>
<p>We love the mascot because he goes to such lengths to make us happy.
Unfortunately, sometimes, it all gets a little too much for the little guy:</p>
<p><a href="http://drone-ah.com/files/2008/12/too-much.png">
  <img src="%7B%7Bsite.baseurl%7D%7D/assets/2008/12/too-much-300x225.png" alt="">

</a><a href="http://drone-ah.com/files/2008/12/too-much-2.png">
  <img src="%7B%7Bsite.baseurl%7D%7D/assets/2008/12/too-much-2-300x225.png" alt="">

{.size-medium .wp-image-31 .alignnone width=&ldquo;300&rdquo; height=&ldquo;225&rdquo;}</a></p>
<p><a href="http://drone-ah.com/files/2008/12/too-much-3.png">
  <img src="%7B%7Bsite.baseurl%7D%7D/assets/2008/12/too-much-3-300x225.png" alt="">

</a></p>
<p>We love the mascot and look forward to seeing him as Tinkerbell at our Christmas
Party next week :-)</p>
<p>Here is to our Mascot:</p>
<p><a href="http://drone-ah.com/files/2008/12/to-the-mascot.png">
  <img src="%7B%7Bsite.baseurl%7D%7D/assets/2008/12/to-the-mascot-262x300.png" alt="">

</a></p>
]]></content:encoded></item><item><title>Design</title><link>https://icle.es/2008/12/12/design/</link><pubDate>Fri, 12 Dec 2008 16:42:37 +0000</pubDate><guid>https://icle.es/2008/12/12/design/</guid><description>&lt;p>Admitting to being a techie - I have often overlooked design. In fact, I have
often explained to (potential) clients, using the analogy of a ferrari that we
make the engine and everything else work while somebody else makes it look
gorgeous. For me, how something looks was largely irrelevant - as long as it
worked well.&lt;/p>
&lt;p>This explains why, for a long time, I used a fairly bland desktop environment.
My desktop itself was just pure black with no wallpaper. Ironically, I would
remove all the icons, so it would be pure black and nothing else.&lt;/p></description><content:encoded><![CDATA[<p>Admitting to being a techie - I have often overlooked design. In fact, I have
often explained to (potential) clients, using the analogy of a ferrari that we
make the engine and everything else work while somebody else makes it look
gorgeous. For me, how something looks was largely irrelevant - as long as it
worked well.</p>
<p>This explains why, for a long time, I used a fairly bland desktop environment.
My desktop itself was just pure black with no wallpaper. Ironically, I would
remove all the icons, so it would be pure black and nothing else.</p>
<p>This should have tipped me off on my own desire for design. I thought my desire
for black stemmed from the &ldquo;good old&rdquo; days of DOS when the screen was black and
my love for the linux terminal. As an aside, I used to reconfigure the terminal
windows in X to have a white on black background as well - so much better for
the eyes. In fact, I still don&rsquo;t understand why everyone uses a white background
for terminals and such like. Paper was white because that was easier. There is
really no reason for the screen to be white too&hellip;</p>
<p>Now, this was before I bumped into
<a href="http://www.enlightenment.org/" title="Beauty at your fingertips">Enlightenment</a> (at
this time, it was E16) and to put it bluntly, I was captivated. This was
absolutely gorgeous. Fairly unusable since I was used to
<a href="http://www.gnome.org/" title="The Free Software Desktop Project">GNOME</a> and of course
Microsoft Windows. I thoroughly enjoyed this until it became more of a
distraction&hellip;</p>
<p>I ended up reconfiguring GNOME to be prettier - in fact, I had the Mac OS X
theme for a while which I enjoyed.</p>
<p>I then dabbled with E17 and it was absolutely gorgeous - E16 paled in
comparison. I ran into a bug where some java applications would jump a few
pixels when changing the decorations. This was a real pain since I was
developing a Java application at the time. I spent an entire day trying to &ldquo;fix&rdquo;
this before I realised that it was E17 screwing it up and not my code&hellip; :-(</p>
<p>More recently, I thoroughly enjoyed
<a href="http://compiz.org/" title="A Compositing Window Manager">Compiz</a> with the shaky
windows and such like - I just always wished that I could actually throw a
window and watch the momentum carry it that extra distance.</p>
<p>Nevertheless, this bridged the gap enough to E17 to keep me happy for a little
while.</p>
<p>Last week, I dabbled with E17 again to see if the issue with Java was resolved.
To my surprise E17 had changed more or less completely - it was bridging the gap
between a window manager and a full fledged Desktop environment.</p>
<p>However, there was a problem. It looked like I couldn&rsquo;t get it back to its old
glory of absolutely fantastic graphics without some effort in configuration. One
other issue I ran into was that maximising a screen would fill it up across both
my monitors. Another thing I could configure but then, it all seemed like too
much effort.</p>
<p>E17 gives me the feeling that this is where user interfaces will end up - it
automates so many of the things that makes it quicker to do anything. However,
it still lacks some of the &ldquo;basics&rdquo;.</p>
<p>E17 is a very good example of a UI that tries to conform to what I call the
&ldquo;<a href="https://icle.es/2008/12/12/invisible-interface/" title="Invisible Interface">Invisible Interface</a>&rdquo;
which I will be writing about later.</p>
<p>To bring it all back to now, I found it a hassle to go through all the available
themes for WordPress for the Company Blog as well as my own.</p>
<p>I used to take great pleasure in going through dozens or hundreds of themes and
picking ones that I liked but after doing it a few times (for Firefox,
Thunderbird, my phone, GNOME, GDM and my flat), it gets a bit repetitous.</p>
<p>Now, for a wish. A website that pulls in all the different themes for all over
the world for everything. A one-stop-theme shop. Here, I could go through and
pick a general theme that I liked and download it for all the applications, my
phone(s), mp3 players (and of course, taking it to the next level, all the
gadgets at my flat).</p>
<p>That gives my life more uniformity. Perhaps this is something that Designers
could take on&hellip; Say Hugo Boss, and design something that even matches your
clothes, shoes, hair - everything.</p>
<p>That way, you could have your own unique branding&hellip; and while you are at it
link it into Gravatars and you are also instantly recognisable</p>
<p>Now for the issue of privacy - I think I best leave that for another day.</p>
]]></content:encoded></item><item><title>Evil Linux</title><link>https://icle.es/2008/12/12/evil-linux/</link><pubDate>Fri, 12 Dec 2008 14:33:21 +0000</pubDate><guid>https://icle.es/2008/12/12/evil-linux/</guid><description>&lt;p>I received an
&lt;a href="http://www.theinquirer.net/inquirer/news/965/1049965/school-teacher-bans-linux" title="School Teacher Bans Linux">interesting link&lt;/a>
in my email this morning. The story (which thinks that sauce and source are the
same thing btw)  covers a school in the United States that has banned the use of
Linux because &amp;ldquo;anything that wasn&amp;rsquo;t Windows was illegal and immoral.&amp;rdquo;&lt;/p>
&lt;p>I could only ponder about the sheer stupidity of this teacher and wonder about
the next generation of students brought up under this ignorance.&lt;/p></description><content:encoded><![CDATA[<p>I received an
<a href="http://www.theinquirer.net/inquirer/news/965/1049965/school-teacher-bans-linux" title="School Teacher Bans Linux">interesting link</a>
in my email this morning. The story (which thinks that sauce and source are the
same thing btw)  covers a school in the United States that has banned the use of
Linux because &ldquo;anything that wasn&rsquo;t Windows was illegal and immoral.&rdquo;</p>
<p>I could only ponder about the sheer stupidity of this teacher and wonder about
the next generation of students brought up under this ignorance.</p>
<p>I grew up with Microsoft, with DOS 3 as my first Operating System and went
through DOS 5, 6, Windows 3.1, 95, NT, 98, &amp; ME.</p>
<p>I also played around with BeOS, and various versions of Mac.</p>
<p>I was then introduced to Linux turned into an open source zealot and wiped out
my Windows installation in anger. Since then, while my primary operating system
is Linux, I still have Windows running on my Laptop and have both Windows &amp;
Linux on my home computer.</p>
<p>I have since worked with Windows 2000, XP, 2003 &amp; Vista. I love what Microsoft
does with these products. They do innovative things, pick up features from other
products that are useful and <strong>try</strong> to simplify things.</p>
<p>My Laptop came pre-installed with Windows and I never went to the effort of
installing Linux and I use my home computer to play games, which (whether I like
it or not) just handles games so much better.</p>
<p>As per the old joke, It is the software engineers job to make software as idiot
proof as possible. It is the job of the universe to create bigger and bigger
idiots. So far the universe is winning.</p>
<p>Linux &amp; Open Source software (in general) takes a different approach to
software. It should be easy to use and manage software but it also expects you
to understand (or at least think about) what you are doing or trying to do.</p>
<p>Microsoft seems to be under the impression that this is not necessary. The user
does not need to know what they are doing - they just need to know what is to
happen. e.g.</p>
<p>Lets take a simple operation - deleting a file. Before Windows 95, this used to
be a simple, difficult to undo operation. Windows 95 brings in the concept of
the Recycle Bin (or Trash), a concept that was available on the Mac platform for
quite some time.</p>
<p>After this point, you no longer delete a file on Windows - you move it to the
Recycle Bin, which will delete them from the disk when the number of files in
there exceeds the set capacity.</p>
<p>Now, from a users perspective, what they are doing is deleting a file - in fact,
thats what the menu item says - Delete. But what happens is completely
different. The file disappears from their folder. What they aimed to do - &ldquo;make
this file disappear&rdquo; has happened. However, the file has <strong>not</strong> been deleted.</p>
<p>Windows has effectively lied to the user since it is &ldquo;smarter&rdquo;. If the user
later discovers that they deleted the wrong file, it can be recovered easier.
However, that is not the point.</p>
<p>Microsoft software, are in general rife with such miscommunications. I find this
fairly insulting and this was one of the main reasons that I started using
Linux.  If you ask it to delete a file - it deletes it. If you want to move
something to recycle bin, it can do that too.</p>
<p>To go back to the original point, the ignorance shown by the teacher in this
school is exactly the kind that Microsoft panders to. Microsoft allows (nay
encourages)  its users to be as &ldquo;simple&rdquo; as possible and let Microsoft worry
about the rest.</p>
<p>Don&rsquo;t get me wrong. I think that Microsoft do a fantastic job in making software
accessible and easy to use but it should also help educate it users on what they
are doing and help them think about what they are trying to do. Don&rsquo;t pretend or
try to do their thinking for them. Thats their job.</p>
<p>&ldquo;Give a <em>man</em> a <em>fish</em>; you have fed him for today. <em>Teach a man to fish</em> ; and
you have fed him for a lifetime&rdquo;</p>
]]></content:encoded></item><item><title>A Ubuquitous Avatar</title><link>https://icle.es/2008/12/12/a-ubuquitous-avatar/</link><pubDate>Fri, 12 Dec 2008 14:31:09 +0000</pubDate><guid>https://icle.es/2008/12/12/a-ubuquitous-avatar/</guid><description>&lt;p>With around 6.5 billion people in the world, there is a good likelihood that if
you think up something &amp;ldquo;original&amp;rdquo;, somebody else in the world has already
thought of it.&lt;/p>
&lt;p>Now, take the internet with just under 1.5 billion people linked in. Now, if you
think up something that would be cool or useful (especially if it pertains to
technology in some way), there is a good chance that somebody else has thought
about it. And if you, like me just want to use it instead of creating it,
somebody else has probably gone to the effort of making it work.&lt;/p></description><content:encoded><![CDATA[<p>With around 6.5 billion people in the world, there is a good likelihood that if
you think up something &ldquo;original&rdquo;, somebody else in the world has already
thought of it.</p>
<p>Now, take the internet with just under 1.5 billion people linked in. Now, if you
think up something that would be cool or useful (especially if it pertains to
technology in some way), there is a good chance that somebody else has thought
about it. And if you, like me just want to use it instead of creating it,
somebody else has probably gone to the effort of making it work.</p>
<p>For a trivial example, it would be cool if I could have just one bookmarks
folder for my <a href="http://www.mozilla.com/firefox/" title="Firefox Web Browser">Firefox</a>
and have this synchronised across all my computers (one in the office, the
laptop, and the one at home). Do a quick google search and Bam - there it is&hellip;
<a href="http://www.foxmarks.com/" title="Foxmarks | Home">Foxmarks</a> and guess what - it does
even more&hellip;</p>
<p>Now, this has an interesting side effect. What about all cool things we could do
if only I knew what to search for in the first place&hellip; I am subscribed to
enough newsletters, websites and blogs (of course) to stay apprised of a lot of
things that are happening, changing and being used in the world but that still
does not tell you about all the cool things that could be done. So, when I
stumbled across this tiny (pun intended) little gem of a service called
<a href="http://www.gravatar.com/" title="Gravatar - Globally Recognised Avatars">Gravatar</a>, I
was intrigued and impressed. It is such a tiny, simple, straightforward little
thing. It also does just one thing, but it does it well. Now what it does it do?</p>
<p>It allows you to set a picture as your avatar against your email address and
everyone who subscribes to the service is able to associate you with this
avatar.</p>
<p>Why is this cool? Well, we just installed Wordpress for our blog and it comes
integrated with Gravatar and my user account was automagically liked in to the
display the picture that I had set as my avatar. Cool!</p>
<p>If that is not cool enough - I set my mail account to link in to Gravatar (thats
actually how I stumbled across the service) and anyone else who uses the service
will show up with their pre-defined avatar on my browser.</p>
<p>All that needs to happen now is for Facebook to integrate with Gravatar so that
when I change my profile picture, it will update my Gravatar&hellip;</p>
]]></content:encoded></item><item><title>words on sand</title><link>https://icle.es/endeavours/wordsonsand/</link><pubDate>Fri, 12 Dec 2008 10:07:08 +0100</pubDate><guid>https://icle.es/endeavours/wordsonsand/</guid><description>&lt;p>This blog site&lt;/p></description><content:encoded>&lt;p>This blog site&lt;/p>
</content:encoded></item><item><title>megabus.com ticketing system</title><link>https://icle.es/endeavours/megabus-ticketing/</link><pubDate>Fri, 26 Sep 2008 15:49:54 +0100</pubDate><guid>https://icle.es/endeavours/megabus-ticketing/</guid><description>&lt;p>This one is a bit of a mixed one in that I didn&amp;rsquo;t do it on my own. However, I
was the lead everything for. I ran the company responsible for building and
managing the ticketing system for megabus.com from its inception until 2012.&lt;/p>
&lt;p>I lived, breathed and loved megabus for 9 years. Whenever my girlfriend at the
time asked me what I was thinking - the answer was inevitably megabus.&lt;/p></description><content:encoded><![CDATA[<p>This one is a bit of a mixed one in that I didn&rsquo;t do it on my own. However, I
was the lead everything for. I ran the company responsible for building and
managing the ticketing system for megabus.com from its inception until 2012.</p>
<p>I lived, breathed and loved megabus for 9 years. Whenever my girlfriend at the
time asked me what I was thinking - the answer was inevitably megabus.</p>
<p>It nearly killed me. When I talk about <a href="https://icle.es/tags/burnout">burnout</a> - megabus was
the main culprit.</p>
<p>I still have the code for the final iteration of it, which was a
<a href="https://icle.es/tags/java-ee">Java EE</a> ticketing system.</p>
<p>I don&rsquo;t think I have the code for the original php based system anymore.</p>
<p>If there is some interest, I&rsquo;ll consider sharing the code, albeit it was last
updated in 2015.</p>
]]></content:encoded></item><item><title>Selfish</title><link>https://icle.es/2007/05/02/selfish/</link><pubDate>Wed, 02 May 2007 16:13:13 +0000</pubDate><guid>https://icle.es/2007/05/02/selfish/</guid><description>&lt;blockquote>
&lt;p>💡 &lt;strong>Note&lt;/strong>&lt;br>
The date of writing this is unclear. All I know is that it was before May 2007&lt;/p>&lt;/blockquote>
&lt;p>I knew it my heart,&lt;br>
even though I doubted it,&lt;br>
now and again.&lt;/p>
&lt;p>My love was not for a purpose,&lt;br>
it simply was.&lt;/p>
&lt;p>I knew this but I forgot.\&lt;/p>
&lt;p>I wanted nothing back,&lt;br>
I wanted nothing forward.&lt;/p>
&lt;p>I just wanted to be,&lt;br>
I just wanted to love.&lt;/p>
&lt;p>It is easy to get confused,&lt;br>
Who loves?&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>💡 <strong>Note</strong><br>
The date of writing this is unclear. All I know is that it was before May 2007</p></blockquote>
<p>I knew it my heart,<br>
even though I doubted it,<br>
now and again.</p>
<p>My love was not for a purpose,<br>
it simply was.</p>
<p>I knew this but I forgot.\</p>
<p>I wanted nothing back,<br>
I wanted nothing forward.</p>
<p>I just wanted to be,<br>
I just wanted to love.</p>
<p>It is easy to get confused,<br>
Who loves?</p>
<p>Is it me, my heart, my soul?\</p>
<p>Is it wrong that I miss her?\</p>
<p>To be with her fills me with joy,<br>
fills me with happiness,</p>
<p>Is that not selfish?</p>
<p>Should I not let her be?</p>
<p>Let her do what she wants?</p>
<p>as long as it makes her happy!</p>
<p>Indeed, but I still miss her,<br>
and all I want to do,<br>
is what I can to make her smile,<br>
to bring a little joy into her life.\</p>
]]></content:encoded></item><item><title>Wealth</title><link>https://icle.es/2007/05/02/wealth/</link><pubDate>Wed, 02 May 2007 16:13:06 +0000</pubDate><guid>https://icle.es/2007/05/02/wealth/</guid><description>&lt;blockquote>
&lt;p>💡 &lt;strong>Note&lt;/strong>&lt;br>
The date of writing this is unclear. All I know is that it was before May 2007&lt;/p>&lt;/blockquote>
&lt;p>All the riches in the world,&lt;br>
Diamonds, jewels and gold.&lt;/p>
&lt;p>Palaces with a thousand servants,&lt;br>
Gardens with a million roses,&lt;/p>
&lt;p>All the luxuries that money can buy,&lt;br>
Or the ones that money cannot buy.&lt;br>
Even to be a star,&lt;br>
admired and loved by all.&lt;/p>
&lt;p>None of this (if I had it) would compare,&lt;br>
not even a little bit,&lt;br>
to the happiness I feel,&lt;br>
Every time I see you smile.&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>💡 <strong>Note</strong><br>
The date of writing this is unclear. All I know is that it was before May 2007</p></blockquote>
<p>All the riches in the world,<br>
Diamonds, jewels and gold.</p>
<p>Palaces with a thousand servants,<br>
Gardens with a million roses,</p>
<p>All the luxuries that money can buy,<br>
Or the ones that money cannot buy.<br>
Even to be a star,<br>
admired and loved by all.</p>
<p>None of this (if I had it) would compare,<br>
not even a little bit,<br>
to the happiness I feel,<br>
Every time I see you smile.</p>
]]></content:encoded></item><item><title>It matters not</title><link>https://icle.es/2007/05/02/it-matters-not/</link><pubDate>Wed, 02 May 2007 16:13:05 +0000</pubDate><guid>https://icle.es/2007/05/02/it-matters-not/</guid><description>&lt;blockquote>
&lt;p>💡 &lt;strong>Note&lt;/strong>&lt;br>
The date of writing this is unclear. All I know is that it was before May 2007&lt;/p>&lt;/blockquote>
&lt;p>Sitting here quietly,&lt;br>
At the railway station of life,&lt;br>
Quietly watching lovers and friends,&lt;/p>
&lt;p>As they say their fond farewells,&lt;br>
As they pray for the train to be delayed&lt;br>
so they may spend another minute together,&lt;br>
just one more minute,&lt;br>
just one more second.&lt;/p>
&lt;p>As they wait with baited breath,&lt;br>
For the train to arrive,&lt;br>
So they may say their enthusiastic hello&amp;rsquo;s.&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>💡 <strong>Note</strong><br>
The date of writing this is unclear. All I know is that it was before May 2007</p></blockquote>
<p>Sitting here quietly,<br>
At the railway station of life,<br>
Quietly watching lovers and friends,</p>
<p>As they say their fond farewells,<br>
As they pray for the train to be delayed<br>
so they may spend another minute together,<br>
just one more minute,<br>
just one more second.</p>
<p>As they wait with baited breath,<br>
For the train to arrive,<br>
So they may say their enthusiastic hello&rsquo;s.</p>
<p>I am not sad,<br>
that I sit here alone,<br>
for I love you,<br>
with all my heart,<br>
and it matters not,<br>
not a little bit,<br>
that you do not love me</p>
]]></content:encoded></item><item><title>Kissed</title><link>https://icle.es/2007/05/02/kissed/</link><pubDate>Wed, 02 May 2007 16:13:04 +0000</pubDate><guid>https://icle.es/2007/05/02/kissed/</guid><description>&lt;blockquote>
&lt;p>💡 &lt;strong>Note&lt;/strong>&lt;br>
The date of writing this is unclear. All I know is that it was before May 2007&lt;/p>&lt;/blockquote>
&lt;p>Every single cell,&lt;br>
Every single atom,&lt;br>
Of my mind,&lt;br>
Of my body,&lt;br>
Of my soul.&lt;/p>
&lt;p>My whole being loved her,&lt;br>
Loved her totally,&lt;br>
Loved her completely,&lt;br>
Loved her unconditionally.&lt;/p>
&lt;p>She took my hand,&lt;br>
kissed it sweetly,&lt;br>
and just walked away&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>💡 <strong>Note</strong><br>
The date of writing this is unclear. All I know is that it was before May 2007</p></blockquote>
<p>Every single cell,<br>
Every single atom,<br>
Of my mind,<br>
Of my body,<br>
Of my soul.</p>
<p>My whole being loved her,<br>
Loved her totally,<br>
Loved her completely,<br>
Loved her unconditionally.</p>
<p>She took my hand,<br>
kissed it sweetly,<br>
and just walked away</p>
]]></content:encoded></item><item><title>Who but you?</title><link>https://icle.es/2007/05/02/who-but-you/</link><pubDate>Wed, 02 May 2007 16:13:04 +0000</pubDate><guid>https://icle.es/2007/05/02/who-but-you/</guid><description>&lt;blockquote>
&lt;p>💡 &lt;strong>Note&lt;/strong>&lt;br>
The date of writing this is unclear. All I know is that it was before May 2007&lt;/p>&lt;/blockquote>
&lt;p>Who but you my love,&lt;br>
Can brighten my day,&lt;br>
The saddest of days,&lt;br>
The hardest of days,&lt;br>
The most frustrating days,&lt;br>
With but a smile.&lt;/p>
&lt;p>Who but you my love,&lt;br>
Can make me feel rich,&lt;br>
When I have lost,&lt;br>
All that I had built,&lt;br>
With my blood, sweat and tears,&lt;br>
When you embrace me.&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>💡 <strong>Note</strong><br>
The date of writing this is unclear. All I know is that it was before May 2007</p></blockquote>
<p>Who but you my love,<br>
Can brighten my day,<br>
The saddest of days,<br>
The hardest of days,<br>
The most frustrating days,<br>
With but a smile.</p>
<p>Who but you my love,<br>
Can make me feel rich,<br>
When I have lost,<br>
All that I had built,<br>
With my blood, sweat and tears,<br>
When you embrace me.</p>
<p>Who but you my love,<br>
Of all the people,<br>
In the whole wide world,<br>
Who but you my love,<br>
Can break my heart.</p>
]]></content:encoded></item><item><title>Adventure</title><link>https://icle.es/2007/05/02/adventure/</link><pubDate>Wed, 02 May 2007 16:13:02 +0000</pubDate><guid>https://icle.es/2007/05/02/adventure/</guid><description>&lt;blockquote>
&lt;p>💡 &lt;strong>Note&lt;/strong>&lt;br>
The date of writing this is unclear. All I know is that it was before May 2007&lt;/p>&lt;/blockquote>
&lt;p>make me happy,&lt;br>
make me sad,&lt;br>
make me calm,&lt;br>
make me angry.&lt;/p>
&lt;p>I love you not for who you are,&lt;br>
I love you not for how you make me feel,&lt;br>
I love you for the adventure we embark upon.&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>💡 <strong>Note</strong><br>
The date of writing this is unclear. All I know is that it was before May 2007</p></blockquote>
<p>make me happy,<br>
make me sad,<br>
make me calm,<br>
make me angry.</p>
<p>I love you not for who you are,<br>
I love you not for how you make me feel,<br>
I love you for the adventure we embark upon.</p>
]]></content:encoded></item><item><title>Thank You</title><link>https://icle.es/2007/05/02/thank-you/</link><pubDate>Wed, 02 May 2007 16:13:01 +0000</pubDate><guid>https://icle.es/2007/05/02/thank-you/</guid><description>&lt;blockquote>
&lt;p>💡 &lt;strong>Note&lt;/strong>&lt;br>
The date of writing this is unclear. All I know is that it was before May 2007&lt;/p>&lt;/blockquote>
&lt;p>Thank You, she said,&lt;br>
Thank you for the dance.&lt;/p>
&lt;p>Thank You, I said,&lt;br>
Thank you very much.&lt;/p>
&lt;p>For brightening my day with you smile,&lt;br>
For making for forget my worries,&lt;br>
To take me away from the illusion of this world,&lt;br>
and into the depths of your eyes.&lt;/p>
&lt;p>Thank You, I said,&lt;br>
Thank you very much.&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>💡 <strong>Note</strong><br>
The date of writing this is unclear. All I know is that it was before May 2007</p></blockquote>
<p>Thank You, she said,<br>
Thank you for the dance.</p>
<p>Thank You, I said,<br>
Thank you very much.</p>
<p>For brightening my day with you smile,<br>
For making for forget my worries,<br>
To take me away from the illusion of this world,<br>
and into the depths of your eyes.</p>
<p>Thank You, I said,<br>
Thank you very much.</p>
<p>For freeing me from the shackles of this world,<br>
And liberating my heart and soul,<br>
When I love you as I do,<br>
I feel joy in everything I do.</p>
<p>Thank You, I said,<br>
Thank you very much.</p>
<p>For the pain that I feel that you are not mine,<br>
The sadness every time you walk away,<br>
The sorrow when you say goodbye,<br>
and the longing when you are away</p>
<p>Thank You, I said,<br>
Thank you very much.</p>
<p>For all the joy I feel when you are near,<br>
For all the pain I feel when you are far,<br>
For all the love I feel for you always,<br>
and I am grateful that I feel these for You.</p>
<p>Thank You, she said,<br>
Thank you for the dance.</p>
]]></content:encoded></item><item><title>Sleep</title><link>https://icle.es/2007/05/02/sleep/</link><pubDate>Wed, 02 May 2007 16:13:00 +0000</pubDate><guid>https://icle.es/2007/05/02/sleep/</guid><description>&lt;blockquote>
&lt;p>💡 &lt;strong>Note&lt;/strong>&lt;br>
The date of writing this is unclear. All I know is that it was before May 2007&lt;/p>&lt;/blockquote>
&lt;p>Ill sleep when I am dead.&lt;br>
Spending all night thinking of you,&lt;br>
against my will,&lt;br>
against my tired body,&lt;br>
my tired mind,&lt;br>
and my tired heart.&lt;/p>
&lt;p>Lying here, thinking of you,&lt;br>
unable to sleep,&lt;br>
all night long.&lt;br>
all week long,&lt;br>
all life long.&lt;/p>
&lt;p>Until it is time to sleep.&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>💡 <strong>Note</strong><br>
The date of writing this is unclear. All I know is that it was before May 2007</p></blockquote>
<p>Ill sleep when I am dead.<br>
Spending all night thinking of you,<br>
against my will,<br>
against my tired body,<br>
my tired mind,<br>
and my tired heart.</p>
<p>Lying here, thinking of you,<br>
unable to sleep,<br>
all night long.<br>
all week long,<br>
all life long.</p>
<p>Until it is time to sleep.</p>
]]></content:encoded></item><item><title>A Thousand Lives</title><link>https://icle.es/2007/05/02/a-thousand-lives/</link><pubDate>Wed, 02 May 2007 16:12:59 +0000</pubDate><guid>https://icle.es/2007/05/02/a-thousand-lives/</guid><description>&lt;blockquote>
&lt;p>💡 &lt;strong>Note&lt;/strong>&lt;br>
The date of writing this is unclear. All I know is that it was before May 2007&lt;/p>&lt;/blockquote>
&lt;p>I have lived a thousand lives,&lt;br>
through countless millenia.&lt;/p>
&lt;p>And now that I have met you,&lt;br>
whats another few days,&lt;br>
another few months,&lt;br>
another few years,&lt;br>
or a few more lives,&lt;br>
for us to fall in love.&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>💡 <strong>Note</strong><br>
The date of writing this is unclear. All I know is that it was before May 2007</p></blockquote>
<p>I have lived a thousand lives,<br>
through countless millenia.</p>
<p>And now that I have met you,<br>
whats another few days,<br>
another few months,<br>
another few years,<br>
or a few more lives,<br>
for us to fall in love.</p>
]]></content:encoded></item><item><title>About</title><link>https://icle.es/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://icle.es/about/</guid><description>&lt;p>My name is Shri, and online, I often go by the handle of @drone.ah (or some
variant of it).&lt;/p>
&lt;p>I am many things and this site captures in words, some of the things that may be
capturable in words.&lt;/p>
&lt;p>Feel free to get in touch&lt;/p>
&lt;p>PS: The name drone.ah has nothing to do with drones. It&amp;rsquo;s a pun. drone ah sounds
like &lt;a href="https://en.wikipedia.org/wiki/Drona">Drona&lt;/a> and was intended to embody the
acharya, or teacher aspect. It suggested by my brother, and it became my handle
way before drones &amp;ldquo;were a thing.&amp;rdquo;&lt;/p></description><content:encoded><![CDATA[<p>My name is Shri, and online, I often go by the handle of @drone.ah (or some
variant of it).</p>
<p>I am many things and this site captures in words, some of the things that may be
capturable in words.</p>
<p>Feel free to get in touch</p>
<p>PS: The name drone.ah has nothing to do with drones. It&rsquo;s a pun. drone ah sounds
like <a href="https://en.wikipedia.org/wiki/Drona">Drona</a> and was intended to embody the
acharya, or teacher aspect. It suggested by my brother, and it became my handle
way before drones &ldquo;were a thing.&rdquo;</p>
<p>shri, lightkeeper<br>
tending to what glows quietly.</p>
]]></content:encoded></item></channel></rss>