Steve's Space Blog for stevesspace.com http://stevesspace.com 3d icons for your Mixed Reality application <p>The default app launchers for Mixed Reality applications built in Unity is a flat image - essentially a splash screen on a floating rectangle.</p> <p><img src="/images/2020-09-01-mixed-reality-3d-app-launcher/hl2-2d-launchers.jpg" alt="2D app launchers on a couch" /></p> <p>I’m sure we can have better immersion than a bunch of 2d panels around the place.</p> <p>The good news is, Windows Mixed Reality supports this, and have a <a href="https://docs.microsoft.com/en-us/windows/mixed-reality/implementing-3d-app-launchers">guide on how to do it</a> - so you can easily turn it into this:</p> <p><img src="/images/2020-09-01-mixed-reality-3d-app-launcher/hl2-3d-launchers.jpg" alt="3D app launchers on a couch" /></p> <p>Isn’t that much nicer? <a href="https://gist.github.com/xwipeoutx/913449ad97a719e434f2803d8476183f">Grab the gist</a> and use it in Unity straight away, or read on for usage.</p> <!--break--> <h2 id="status-quo">Status quo</h2> <p>To add a 3D app launcher to your Unity normally requires a few steps.</p> <ol> <li>Create your 3d model, export as GLB</li> <li>Build your UWP project</li> <li>Copy it into the <code class="language-plaintext highlighter-rouge">Assets/</code> folder</li> <li>Update your project file and Package.appxmanifest to reference your new model.</li> </ol> <p>I didn’t think this was very repeatable - especially since the guidance is to <em>not check in your build folder</em> (good advice, follow it).</p> <h2 id="usage">Usage</h2> <p>I’ve automated the steps above into a little script, so now it’s simply</p> <ol> <li>Copy the <code class="language-plaintext highlighter-rouge">MixedReality3dAppLauncher.cs</code> script into an <code class="language-plaintext highlighter-rouge">Editor/</code> folder of your scripts</li> <li>Save your app launcher to <code class="language-plaintext highlighter-rouge">Assets/app-icon.glb</code></li> <li>Build your project</li> </ol> <p>That’s it! Your app launcher will be included in the project, appxmanifest, and copied across when you build. Or you can manually patch it by going to <code class="language-plaintext highlighter-rouge">UWP Tools/Patch manifest and project</code>.</p> <script src="https://gist.github.com/xwipeoutx/913449ad97a719e434f2803d8476183f.js"></script> <p>Enjoy!</p> Tue, 01 Sep 2020 00:30:00 +0000 http://stevesspace.com/2020/09/mixed-reality-3d-app-launcher/ http://stevesspace.com/2020/09/mixed-reality-3d-app-launcher/ WebXR - the game changer <p>Let’s start with a message of mine on the <a href="https://holodevelopersslack.azurewebsites.net/">HoloDevelopers Slack Community</a> I wrote yesterday:</p> <blockquote> <p>So I’m doing my first WebXR project now (on Babylon). I must say, I’m loving it. Having the power of every NPM module under the sun makes it extremely easy to integrate with things, and the async-by-default nature of the web just makes delivering content nice and simple. Deployment of new versions and things is also ridiculously simple, including things like multiple environments (dev/test/prod type thing) - it’s basically using the maturity of the web for immersive experiences.</p> <p>Plus it’s easy to preview/emulate the base experience, and you can just use normal web stuff for debug/UI/options etc. It’s just an amazing fit.</p> <p>I just felt like gushing. Go back to your normal lives 🙂</p> </blockquote> <p>I think that captures it all quite nicely - really, you can stop reading here if you want - or the even more conscise version:</p> <p><strong>WebXR is a game-changer in immersive technologies, especially in enterprise.</strong></p> <p>Why? Read on.</p> <p>Be warned this is developer centric. You won’t find many art or design opinions here.</p> <!--break--> <h2 id="in-the-users-hands-faster">In the user’s hands, faster</h2> <p>App deployment in enterprises right now is not a fun affair. Dealing with MDM, provisioning profiles with user-specific lists, not having the same hardware as the users (e.g. I run Windows - sorry iOS users, I can’t build your software) and/or manual installation steps - these slowdowns should be familiar to anyone deploying in an enterprise.</p> <p>It’s especially problematic when you’re taking your product to customers - saying “Please download and install my app (after I get through the submission process), so I can sell you my widgets better” goes down like a lead balloon.</p> <p>The web is another ballgame entirely. I can push to GitHub and my software is available in seconds (minutes if it gets complex).</p> <p>Getting a user to test a new build is simply a matter of saying “press F5”. There you go, new version is just there. No updating from the app store required.</p> <p>Want to take it to the public? Well you already have a website. There you go! Or, send them a link. Couldn’t be easier.</p> <p>It’s also much simpler to have multiple streams - eg. “Dev”, “Test”, “MPE”, and “Production”. We’ve been doing multi-environment web pushes for years now. The tooling on your CI/CD platform of choice does this out of the box - simply see the “DevOps 101” part of the documentation.</p> <p>In general, the move from App Dev to Web Dev means faster iterations, quicker feedback, and costs you less money. Why should immersive tech be any different?</p> <h2 id="easy-to-pick-up-for-experts-and-newbies-alike">Easy to pick up, for experts and newbies alike</h2> <p>Existing non-game developers can jump into WebXR relatively simply - no learning complex editor UIs, just grab a starter, download some assets and render it.</p> <p>The <em>Hello, World!</em> of Augmented Reality - <a href="https://modelviewer.dev/examples/augmented-reality.html">show an 3d model in your living room</a> - is, well, easy. Literally some script includes, and a single HTML tag.</p> <script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script> <script nomodule="" src="https://unpkg.com/@google/model-viewer/dist/model-viewer-legacy.js"></script> <model-viewer src="/assets/models/Astronaut.glb" ar="" ar-modes="webxr scene-viewer quick-look" ar-scale="auto" camera-controls="" alt="A 3D model of an astronaut" skybox-image="/assets/environments/aircraft_workshop_01_1k.hdr" ios-src="/assets/models/Astronaut.usdz" style="width: 640px; height: 400px;"></model-viewer> <script src="https://gist.github.com/xwipeoutx/b2b2b189dd037409b41c7b4183613e81.js"></script> <p>Hello World in a full-featured game engine isn’t much more complicated - <a href="https://www.babylonjs-playground.com/pg/F41V6N/revision/32">BabylonJS does it in 20 lines of code</a>.</p> <p>And then you can build on top of it.</p> <h2 id="better-integrations">Better integrations</h2> <p>What’s your average enterprise running their entire operation on right now? If you answered anything but “web browser”, you’re wrong. Most people spend their days with a browser window/app opened. Internet-enabled technologies are enabling their day-to-day work.</p> <p>Indeed, I work with someone who bought and uses a Chromebook as their daily workhorse, because they “don’t need anything but a browser”.</p> <p>If you’re building out enterprise solutions, you will be integrating with these other services. Make it easier on yourself, and use the well-known web standards for this - what is better equipped for calling a JSON service: Unity, or a web browser?</p> <h2 id="npm-wow">NPM. Wow.</h2> <blockquote> <p><em>“Any application that can be written in JavaScript, will eventually be written in JavaScript.”</em></p> <p>-<a href="https://blog.codinghorror.com/the-principle-of-least-power/">Jeff Atwood, Author, Entrepreneur, Cofounder of StackOverflow</a></p> </blockquote> <p>Look, it’s a reality the NPM has a <a href="http://www.modulecounts.com/">ridiculous number of packages</a>. There’s an NPM package for everything. It has more than all the other languages combined.</p> <p>Need to consume/render GIS data? <a href="https://turfjs.org/">TurfJS</a></p> <p>Need to do machine learning? <a href="https://www.tensorflow.org/js">TensorFlow</a></p> <p>OCR? <a href="https://tesseract.projectnaptha.com/">Tesseract has been ported</a></p> <p><a href="https://github.com/IdentityModel/oidc-client-js">Authentication</a>, <a href="https://analytics.google.com/">Analytics</a>, <a href="https://mozilla.github.io/pdf.js/">PDF Rendering</a>, <a href="https://js-dos.com/DOOM/">DOOM</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API">Real Time Communication</a> - JavaScript, and the browser has it all.</p> <p>And by using WebXR, you can have it in your project.</p> <h2 id="better-suited-ui">Better suited UI</h2> <p>A lot of enterprise experiences have a decent amount of pre-work before starting your immersive experience. Take VR training - you need to authenticate, select your training modules, configure any scenarios (maybe even review past ones first) - before you even begin. Once you’re done, you might need to review/comment on it, do quizzes, submit it.</p> <p>I don’t want to do that in VR - noone likes typing on a VR keyboard, and any VR based menu system is going to be harder to learn than a web-based one.</p> <p>So why not build that UI in HTML, and then pop the VR headset when you’re good to go? It’s all <em>just web stuff</em> - and a great way to bring in existing web development capabilities in your business.</p> <h2 id="control-your-code">Control your code</h2> <p>Using game engines (Unity, I’m looking at you) involves forsaking control. Want some behaviour in Unity? Inherit from MonoBehaviour, use these <del>magic</del> specific and well-defined methods. Oh, and forget about dependency injection.</p> <p>Not so much in WebXR land - engines like <a href="https://threejs.org/">three.js</a> and <a href="https://www.babylonjs.com/">babylon.js</a> are built in the JavaScript ecosystem, which has long favoured modular systems that can be independenty controlled.</p> <p>Don’t like the physics engine? Switch it out. Don’t like multiplayer system? I don’t have to wait for <a href="https://blogs.unity3d.com/2018/08/02/evolving-multiplayer-games-beyond-unet/?_ga=2.79025494.393690968.1596882967-720316448.1594185464">UNet to be deprecated</a> - I can switch it out. All covered by open standards, and an NPM package (or 17) for each.</p> <h2 id="async-by-default">Async by default</h2> <p>Game engines usually support synchronous/embedded content first. You see it all the time - download an app, it’s 38GB. Or it’s 20MB, and you get a hefty download bar on first launch.</p> <p>Bringing in dynamic content (like product models from a database) is often <em>supported</em>, but never the first thing in the documentation. Google it every time.</p> <p>Moving to the web, you have no choice. Loading over HTTPS? Guess what, it’s asynchronous. You can’t avoid it.</p> <p>Why is this a good thing? Well, it’s async by default - so you won’t fall into that trap of “Oh wait, I want that asset to be externally, I have to rejig my whole scene”. You’re already async.</p> <p>It’s easy to make something that is asynchronous become synchronous/blocking - just “wait for load” before continuing. The reverse? Hard.</p> <h2 id="summing-up">Summing up</h2> <p>I’ve painted a fairly rosey picture of WebXR - I honestly believe that this standard is the tipping point for enterprise immersive tech uptake.</p> <p>Not everything is perfect - the standards aren’t fully settled yet, JavaScript and WebGL is slower* than native code (and lacks useful constructs like custom value types 😢), and <a href="https://doc.babylonjs.com/how_to/introduction_to_webxr#device-and-browser-support">browser support</a> is <em>pretty good</em> - not ubiquitious.</p> <p><em>* Worth mentioning, I am nearly always GPU-bound on my projects, not CPU-bound. So JavaScript speed isn’t really a big deal. Enterprise apps don’t necessarily have a lot of AI, physics, etc. going on for this to be a problem. That said, lack of custom value-types (structs) are painful, especially for vectors. Gotta keep cloning them!</em></p> <p>Ultimately, you will still need to test cross-device. You will still have to keep one-eye-in-the-headset when debugging. And you will <em>also</em> have to deal with browser quirks.</p> <p>But you will be faster. You will get feedback quicker. It will be safer. You will have more control, and more options. It will be easier to pivot. You will have simpler access to SDKs and integrations. Playgrounding code will be trivial.</p> <p>It’s a game changer.</p> Wed, 19 Aug 2020 00:00:00 +0000 http://stevesspace.com/2020/08/webxr-the-gamechanger/ http://stevesspace.com/2020/08/webxr-the-gamechanger/ OBS for remote meets <p>We’ve recently had to pivot to fully remote church service at my church, for reasons that are hopefully quite apparent.</p> <p>We had a few constraints</p> <ul> <li>Accessible by the technically illiterate - need to see the stream in a single click</li> <li>We wanted several people presenting - to keep a discussion and community feel</li> <li>As little contact as possible - ideally none. All the presenters meeting in 1 place means that one infection can take down the lot, not to mention the public health concerns of doing such.</li> <li>Usable by technically illiterate - the presenters are not necessarily literate either</li> </ul> <p>Is this your situation? The good news is, we did this in a relatively straight forward way - using <a href="https://obsproject.com/">OBS Studio</a>, <a href="https://zoom.us/">Zoom</a> and YouTube Live.</p> <p>Read on to see how</p> <!--break--> <h2 id="tldr">TL;DR</h2> <p>The solution we’re going for is as follows</p> <ul> <li>Start your YouTube Live session, and get the streaming key</li> <li>All presenters dial in to 1 team meeting (be it Zoom, Hangouts, Teams, whatever)</li> <li>Using OBS <ul> <li>Configure to stream to your YouTube live</li> <li>Set the computer audio as an audio source</li> <li>Set a video, screenshare, or static image as a video source</li> <li>Reduce your bitrate if you’re only sharing an image</li> <li>Have a countdown timer for stream start (bonus)</li> <li>Start streaming</li> </ul> </li> <li>When all is tested, tell YouTube to “go live”</li> </ul> <p>That’s it! More details below - we’ll do the simplest possible version (static image, audio only)</p> <h2 id="setup-peoples-studios">Setup people’s studios</h2> <p>Your presenters need decent sound quality</p> <ul> <li>Buy a good mic. I can recommend the <a href="https://www.bluedesigns.com/products/yeti/">Yeti</a> mics (I’m not affiliated, they’re just awesome)- I got exceptional results out of even the Yeti Nano.</li> <li>Read the mic instructions. Worth it.</li> <li>Do some rudimentary sound proofing - a matress against the window, close the door</li> <li>Do some rudimentary echo prevention - hang some towels or sheets over large flat surfaces to absorb the sound</li> <li>Ensure decent internet access - plugged in directly via cable (not wifi), if possible!</li> </ul> <h2 id="prepare-youtube-live">Prepare YouTube Live</h2> <p>First step is to get YouTube live going.</p> <p>Log into your YouTube account in your browser, and choose “Go Live”</p> <p><img src="/images/2020-03-30-obs/youtube-go-live.png" alt="Go Live in YouTube" /></p> <p>You’ll have to wait 24 hours now - presumably this is to reduce spam live streaming. Hopefully this isn’t last minute for you</p> <p>After you’ve waited a day, press the same thing, and it will try and start a webcam-based stream. We <strong>do not want this</strong> - instead, press the “Stream” tab at the top:</p> <p><img src="/images/2020-03-30-obs/youtube-go-to-stream-tab.png" alt="YouTube Stream tab" /></p> <p>Now fill in all the deets - a title, whether it’s public or unlisted, etc. etc. - hopefully you can figure this out.</p> <p><img src="/images/2020-03-30-obs/youtube-stream-settings.png" alt="YouTube Stream settings" /></p> <p>Press Create Stream when you’re done. From here, you’re taken to the streaming mode - the bit that matters for OBS is the Stream key:</p> <p>this</p> <p><img src="/images/2020-03-30-obs/youtube-stream-key-1.png" alt="" /></p> <p>or this</p> <p><img src="/images/2020-03-30-obs/youtube-stream-key-2.png" alt="" /></p> <p>That’s it for YouTube live for now - it’s basically waiting for you to start streaming to it.</p> <p>At this point, you have access to the share URL - you can set this up <em>days</em> in advance if you’re crazy enough, people will just get told it’s not going yet.</p> <p><img src="/images/2020-03-30-obs/youtube-share-code.png" alt="" /></p> <p>So onto OBS.</p> <h2 id="download-install-and-configure-obs">Download, install and configure OBS</h2> <p>Head to the <a href="https://obsproject.com/download">OBS Studio Download Page</a> and download OBS.</p> <p>After you’ve installed (next, next, next) and started it, you’re greeted with a fun start screen. WooOOOooo.</p> <p>Say no to autoconfiguration - mosty because you need to <em>learn</em> not just <em>get it working this one time</em>.</p> <p><img src="/images/2020-03-30-obs/obs-start.png" alt="" /></p> <p>I’ve numbered the parts as follows</p> <ol> <li>Scenes - different configuration profiles (eg. different events)</li> <li>Sources - where your audio and video come from</li> <li>Mixer - Mute your audio as you want</li> <li>Some buttons. Click them and they do things.</li> <li>Preview</li> </ol> <h3 id="step-1-new-scene">Step 1: New scene</h3> <p>Create a new scene for this stream, and give it a name (eg. “Trivia Night” or “Sunday Church Service”)</p> <p><img src="/images/2020-03-30-obs/obs-create-scene.png" alt="" /></p> <h3 id="step-2-add-a-static-image-source">Step 2: Add a static image source</h3> <p>After you’ve made a sweet image (I recommend at 1920x1080), we’ll use this for the scene.</p> <p><img src="/images/2020-03-30-obs/obs-image-source.png" alt="" /></p> <p>Choose Create New, give it a name, and press OK</p> <p>You will see a popup to choose your image file - browse to it, choose your file, and hit OK.</p> <p><img src="/images/2020-03-30-obs/obs-image-background.png" alt="" /></p> <h3 id="step-3-add-audio-source">Step 3: Add audio source</h3> <p>It gets a <em>little</em> more complex here (and clearly assumed windows). Your computer has 2 audio configurations - one that is system-wide, and one for individual apps or browser windows.</p> <p>For example, when I’m working, I use my speakers to pump tunes out, watch videos, and such - but whenever I’m in a meeting, I use my headset.</p> <p>In this case, I set my <em>system</em> audio to <code class="language-plaintext highlighter-rouge">My pumpin' rad speakers</code>, and my audio for Teams (or Zoom, or Skype or whatever) to <code class="language-plaintext highlighter-rouge">My boring headset</code>.</p> <p>For these purposes, we will use one audio output for Zoom, and another audio output for everything else - this is mostly a safety step so any notifications don’t show up in the live stream. If you don’t have a secondary audio device (eg. a bluetooth headset), just make sure you’re not running any other apps that might make sounds.</p> <p>We’ll cover the zoom audio later, but to set your system audio, use the volume in the system tray and select the preferred device:</p> <p><img src="/images/2020-03-30-obs/system-audio.png" alt="" /></p> <p>Now in OBS, add an “Audio Output Capture”, and choose the audio source you’ll be using for Zoom:</p> <p><img src="/images/2020-03-30-obs/obs-create-audio-input.png" alt="" /></p> <p>Call it something helpful - I called mine “Presenter Audio (Zoom)” to be clear</p> <p>You will get a popup now to choose your device - this is important - choose the device you expect the presenters (ie. Zoom) to be using.</p> <p><img src="/images/2020-03-30-obs/obs-select-audio-device.png" alt="" /></p> <h3 id="step-4a-your-own-mic">Step 4a: Your own mic</h3> <p>If you want to also present to the live stream, you’ll want to add your own mic as an input source - add an “Audio Input Source”, and choose “Use existing” to ensure it’s there.</p> <p>Just make sure you’re not using speakers and a mic together, or your viewers will get reverb. Test it first.</p> <h3 id="step-4-get-your-mixer-right">Step 4: Get your mixer right</h3> <p>By default, your audio mixer will contain desktop audio as well as a mic. You will also see an entry for your recently added presenter source.</p> <p>Mute the 2 default ones (so they don’t distract you - you can even hide them), and adjust the presenter volume so that ordinary speaking hovers in the <em>yellow</em> - you want to avoid red, as it clips, but you want to have enough space to turn it up or down depending on presenter volume.</p> <p><img src="/images/2020-03-30-obs/obs-mixer.png" alt="" /></p> <h3 id="step-5-enter-your-stream-key">Step 5: Enter your stream key!</h3> <p>I nearly published this post without this step…</p> <p>Go to OBS settings, the “Stream” category, and configure it for YouTube.</p> <p>Paste in the given stream key from way up the page!</p> <p><img src="/images/2020-03-30-obs/obs-stream-key.png" alt="" /></p> <blockquote> <p><strong>Bonus</strong>: If you’re just using a static image, go to the “Output” category, and set your Video bitrate to 500kbps (well, that’s what I used). You can even reduce your FPS in the Video tab - it’s just a static image after all! I run mine at 10FPS and 500kpbs, just incase my Internet decides it’s slow for a bit - audio is most important in my case</p> </blockquote> <h2 id="zoom-or-whatever">Zoom (or whatever)</h2> <p>Not much detail here - create a Zoom meeting as always, and be sure to <strong><em>SET YOUR AUDIO SOURCE TO THE CORRECT ONE</em></strong> *ahem*. It’s important.</p> <p>Send people the Zoom link, ask them to join</p> <h2 id="start-streaming-to-youtube-live">Start Streaming to YouTube LIve</h2> <p>In OBS press “Start Streaming” in the bottom right. You’re off!</p> <p>If you switch over to YouTube now (well, you’ll have to wait 30seconds or more), you should see your image.</p> <p>Right now, only your YouTube account (or accounts with enough access to your channel) can view it - once you press “GO LIVE”, then it’s ready for the world to see.</p> <p>But first…</p> <h2 id="sanity-checks">Sanity checks</h2> <p>It’s best to do a few tests - get someone to help you here, or if you have a clear head, join your Zoom meeting on your phone (be prepared for feedback).</p> <p>Some tests you’ll want to run - at least until you’re used to it all</p> <ul> <li>Play some random video or spotify or something on your computer. the mixer shouldn’t show any signal for it - it’s a different audio source</li> <li>Unmute the little preview video in YouTube Studio, and have someone in the zoom meeting talk. You should hear them after 30 seconds or so. <ul> <li>(Note this may be disorienting - you’ll hear them initially from Zoom, and then again 30 seconds later on YouTube. If you’re testing from your phone, it will pick up the audio from youtube, and send it to the zoom meeting again. Loop loop loop!)</li> </ul> </li> <li>Try muting and unmuting the zoom audio source, make sure any conversation never goes to your YouTube Studio video</li> <li>Talk yourself. Your presenters should hear you (from Zoom), but it won’t go to YouTube live</li> </ul> <h2 id="go-live">Go live</h2> <p>Time to go live! I have a little routine I do hear to ensure it’s a crisp start.</p> <p>Even if you’re very familiar with the setup, go live (without audio) 5 or 10 mintues early - you may want to play some <strong>public domain</strong> music too (don’t get copyright hammered)</p> <ul> <li>In OBS: Mute the zoom audio source</li> <li>Wait 30 seconds or so for YouTube to catch up</li> <li>In YouTube studio hit “Go Live”</li> <li>Wait for it to go live</li> <li>Talk to your presenters - “1 minute until live” etc.</li> <li>At 10 seconds to go, give them a countdown “10… 5… 3, 2, 1, Go”</li> <li>At “Go”, immediately unmute their OBS audio source, mute your microphone in Zoom (so they can’t hear you clicking around and being distracting), and have them start talking</li> </ul> <p>That’s it! You’re live! Keep an eye on the audio mixer levels - adjust them to keep it about equal.</p> <blockquote> <p><strong>Note:</strong> YouTube will frequently complain that your bitrate isn’t high enough for their standards. They’re catering for much more professional setups - if you’re on terrible Australian infrastucture during a pandemic, you’ll know that 5000kbps is probably out of most of our reach.</p> </blockquote> <h2 id="congrats">Congrats!</h2> <p>You did it, you’re live. Winning!</p> <p>For a lot of cases, this is enough - you’re essentially doing a podcast on a publically accessible platform for free. This is enough for most communities, don’t overcomplicate it if you don’t have to.</p> <h2 id="bonus">Bonus?</h2> <p>…But if you must, some more ideas</p> <ul> <li>Engage your audience with YouTube live chat - if they’re not savvy enough to monitor the chat themselves, then just send them SMS messages or something with the questions, or answer them directly as the channel. Don’t forget your viewers are at least 30 seconds behind in the stream video</li> <li>Grab a countdown timer for OBS for the early viewers (eg. <a href="https://www.mystreamtimer.com/">here</a> or <a href="https://obsproject.com/forum/threads/tool-obs-timer-the-easy-to-use-countdown-timer.10617/">here</a>. Note I can’t vouch for these ones, I can’t find the one I ended up downloading… so use at your own risk). I use white text with a black border, so it’s always readable.</li> <li>Author a countdown video, with some music and stuff, to keep early joiners interested. You can just reuse the same one each week</li> <li>Share the Zoom screen if you want faces - add a “Window Capture” layer, and use the “Windows Graphics Capture” method, which will grab the video as well.</li> </ul> Mon, 30 Mar 2020 11:30:00 +0000 http://stevesspace.com/2020/03/obs-for-remote-meets/ http://stevesspace.com/2020/03/obs-for-remote-meets/ How does the Hololens 2 matter? <p>So that happened, <a href="https://www.youtube.com/watch?v=c1CZsqwnWtM">the Hololens 2 has been released</a>. A few people have asked me what I think, so it’s about time I got my thoughts down on <del>paper</del> the interwebs. I’ve had a day or 2 to mull over it now - so here it is for you.</p> <p>The main features touted are improved FOV, fully articulated hand tracking and increased engagement and collaboration - partnerships and technology enablements.</p> <p>But how will this play out? How much is smoke and mirrors? What are the effects here? Read on for my thoughts.</p> <!--break--> <h2 id="field-of-view">Field of view</h2> <p>The elephant in the room (that you can’t see much of because it doesn’t fit in viewport).</p> <p>If you gave a Hololens 1 user/dev a handful of Microsoft dollars to improve the headset, chances are they’d spend it all improving the field of view.</p> <p>Well, that’s what they’ve done. 2x improvement is their numbers. But let’s look at the numbers</p> <h3 id="how-much-better">How much better?</h3> <p>Fortunately <a href="https://twitter.com/akipman/status/1100069645661495298">Alex Kipman tweeted</a> the FOV details for us.</p> <p>Hololens 1 has a FOV of <strong>30° horizontally</strong>, and <strong>17.5° vertically</strong> (source: <a href="https://en.wikipedia.org/wiki/Microsoft_HoloLens">Wikipedia</a>). So, thanks to pythagoras, we get a <strong>diagonal FOV of around 35°</strong>. Note the aspect ratio - approx 16:9</p> <p>Hololens 2 now has a <strong>52° diagonal FOV</strong> and a 3:2 aspect ratio - so <strong>43° horizontally</strong> and <strong>29° vertically</strong>.</p> <p>Just for reference, the magic leap has <strong>40° horizontally</strong>, <strong>30° vertically</strong> - so a <strong>diagonal of 50°</strong>.</p> <p>The image on <a href="https://uploadvr.com/hololens-2-field-of-view/">UploadVR</a> is a good view comparing all 3, but I’m gonna do it here in CSS <em>because I can</em> (and so you can view the source to check my measurements for yourself).</p> <div style="position:relative; width: 600px; height: 320px; text-align: center; background: #1e1e1e"> <div style="position: absolute; border: 4px solid rgba(255, 255, 0, 0.5); width: 430px; height: 290px; top: 5px; left: 87.5px; color: rgb(255, 255, 0); text-align: center;"><br />Hololens 2</div> <div style="position: absolute; border: 4px solid rgba(0, 255, 255, 0.3); width: 400px; height: 300px; top: 0px; left: 100px; color: rgb(0, 255, 255);">Magic Leap</div> <div style="position: absolute; border: 4px solid rgba(255, 0, 0, 0.3); width: 300px; height: 175px; top: 62.5px; left: 150px; color: rgb(255, 0, 0);">Hololens 1</div> </div> <p>The take home: It’s pretty much the same as the Magic Leap, and calling it double is pretty misleading.</p> <p><strong>Update:</strong> I mistakingly said it is 4x the area (<code class="language-plaintext highlighter-rouge">2236°²</code>) below - it is actually 2.4x (<code class="language-plaintext highlighter-rouge">1247°²</code>). Thanks to <a href="https://twitter.com/WithinRafael">Rafael</a> for <a href="https://twitter.com/WithinRafael/status/1100540006706438144">picking that up</a></p> <p>It’s not even double the perimeter (<code class="language-plaintext highlighter-rouge">95°</code> vs <code class="language-plaintext highlighter-rouge">144°</code>) but it <strong>IS</strong> ~2.4x the area (<code class="language-plaintext highlighter-rouge">525°²</code> vs <code class="language-plaintext highlighter-rouge">1247°²</code>) - but IMHO these units don’t even make sense* ! <strong>Comparing diagonals gets about 1.5x improvement</strong>, which I think it the more realistic number.</p> <p><em>(*ok, so a <a href="https://en.wikipedia.org/wiki/Square_degree">square degree</a> is apparently a valid measurement, if not SI-compliant. There are apparently 41,252.96 square degrees in a sphere, so if we stack 34 Hololens 2 units perfectly, we have ourselves a fully Holographic sphere)</em></p> <p>So don’t get hyped on that! It’s a nice improvement, certainly, but it’s not mind blowing as it sounds - and certainly not “double the FOV” unless you <em>really</em> stretch the definition of FOV.</p> <h3 id="what-does-it-mean">What does it mean?</h3> <p>I’ll throw this out there - a bigger FOV is not better because you can fit more on - it’s better because you can <em>get <strong>closer</strong> to your holograms</em>.</p> <p>Microsoft recommends the holograms do not show any nearer than 85cm for the original Hololens - any closer and your eyes go all funky. Why? Because at that distance, some of the hologram is hanging off the side of one of your eyes, and not the other.</p> <p>To show what I mean, I’ve done a quick Unity scene - a single sphere with a radius 15cm sitting 50cm* from the eyes. This is the approximate view on each hololens for the sphere:</p> <p>(*I chose these sizes because that makes the sphere take up a similar viewport % on the Hololens 2 as the same size sphere at 85cm on the original Hololens)</p> <p><img src="/images/2019-02-26-fov-example.png" alt="Comparing Hololens and Hololens 2 FOV impact" /></p> <p><strong>Figure</strong>: Comparison of Hololens 1 and 2 rendering from the left and right eyes</p> <p>The first view will make you feel cross-eyed, with about 20% of the object cut off on each eye. Comparitively, on the Hololens2, most of the sphere is in view - it will appear close to you, but your eyes still agree on what they’re seeing.</p> <p>THIS is why the FOV is <em>so important</em> - at 85cm, you can only <em>just</em> reach the holograms. at 50cm though? It’s within reach. This is a big deal. Arguably the high quality finger/hand recognition wouldn’t have worked well without this proximity anyway.</p> <h2 id="gestures">Gestures</h2> <p>You know what you get sick of when showing people the Hololens? Teaching someone to Air-Tap.</p> <p>The always want to be reaching forward and touching the Holograms - but it’s been impossible.</p> <p>I’m gonna say it - I think this high-accuracy hand and finger tracking is the killer feature for this device. Assuming minimal smoke-and-mirrors, <a href="https://youtu.be/c1CZsqwnWtM?t=2200">Julia Schwarz played a freakin’ holographic piano</a>.</p> <p>I think this is what the Holographics UIs have been waiting for - simple, intuitive interaction. No longer do I need to make a <code class="language-plaintext highlighter-rouge">+</code> and <code class="language-plaintext highlighter-rouge">-</code> button for a number in put - sliders will work! No longer does someone have to be looking <em>directly</em> at a button to tap it - the can just… <em>actually</em> tap it.</p> <p>This will greatly reduce the main issue with using the Hololens in educational environments - having a no-training-required experience to learn the app. It’s important!</p> <p>Of course, there’s an issue. Notice Julia is always an arms length away from what she’s interacting with? My guess is the near-clip plane sits at about 40-50cm for the Hololens 2 by default, so we’re not getting any small-and-up-close UI elements any time soon. Expect to have to reach out to them.</p> <p>Admittedly this is a very minor issue.</p> <h2 id="performance">Performance</h2> <p>We got ourselves a pretty big upgrade here!</p> <p>Hololens 1 was pretty bad performance-wise. You’re lucky to push 400k triangles, even with the simplest of shaders. Want reflections? Hope you’re not using too many pixels! And do you have a lot of transparency? Are you overdrawing? Not on this device, my friend!</p> <p>Enter Hololens 2 - it has a <a href="https://mspoweruser.com/full-tech-specs-of-microsoft-hololens-2/">Qualcomm Snapdragon 850 Compute Platform</a> for a CPU/GPU - compared to the 1Ghz Atom processor in the Hololens 1.</p> <p>We’re talking nearly a tripling of maximum clock rate, and a huge jump in GPU power too. If Hololens 1 was an iPhone 5 (a good comparison), then Hololens 2 is an iPhone X + 20% ish.</p> <p>So we’ve got some actual (comparative) horsepower now.</p> <p>The flipside? We’re having to push many more pixels with the FOV increase. With a viewing area jump of about 2.4x, we have about 2.4x the number of pixels.</p> <p>So it’s gonna <em>need</em> that extra GPU power.</p> <p>And honestly? CPU power hasn’t been a problem in any of the apps I’ve created.</p> <p>We’ll have to wait and see how this performance pans out. Having recently done a fairly graphics intensive iPad-Pro ARKit application, I know first hand that graphics power unlocks a lot of that “wow” factor that’s hard to get on the original Hololens. I hope we can get that same “wow” on the Hololens 2.</p> <h2 id="partners-and-open-tech">Partners and open tech</h2> <p>In case you missed it, <a href="https://readify.net">Readify</a> made a front and center appearance on the stage with Alex Kipman!</p> <blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Oh hi there <a href="https://twitter.com/readify?ref_src=twsrc%5Etfw">@readify</a> logo 🧐<br /><br />Recognition for some great work done, and being done, by our teams! 💪 Great to be part of this journey with <a href="https://twitter.com/HoloLens?ref_src=twsrc%5Etfw">@HoloLens</a>, and their HoloLens 2 launch this morning. <a href="https://t.co/FI5Cd8IpHf">pic.twitter.com/FI5Cd8IpHf</a></p>&mdash; Tatham Oddie (@tathamoddie) <a href="https://twitter.com/tathamoddie/status/1099797151943553024?ref_src=twsrc%5Etfw">February 24, 2019</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <p>Ok, so more like… rear and slightly to the right. You get the point. I’m pretty sure he picked his stage position careuflly.</p> <p>Microsoft have been putting in the hard yards attracting, partnering and equipping partners to deliver this stuff well - and I must say, it’s been super useful to partner with them in this.</p> <p>They really are thinking of developers for this journey - with the <a href="https://github.com/Microsoft/MixedRealityToolkit-Unity">MRTK</a> and a <a href="https://github.com/Microsoft/VisualProfiler-Unity">other tools</a> that I use on every gig (and <a href="https://github.com/Microsoft/MRLightingTools-Unity">probably future gigs</a>), it makes everything easy.</p> <p>Indeed, the next version of this tooling goes <em>beyond</em> Mixed Reality devices - Microsoft seem to be bringing in other AR devices (Android, iOS) into the fold and enabling them to work. My guess is this is part of the “use whatever OS you want, as long as it’s in Azure” aspect - providing services like cloud anchors and speech understanding transcends devices, and brings in the bacon.</p> <p>I’ve heard enterprises are squeamish about this openness - but I whole-heartedly embrace it.</p> <p><em><strong>Shameless plug</strong>: Do you want the chance to work on this stuff? Reach out - Readify are always hiring. Reach out on <a href="https://twitter.com/xwipeoutx">twitter</a> or hit up <a href="https://join.readify.net">https://join.readify.net</a>.</em></p> <h2 id="unfortunately">Unfortunately…</h2> <p>Sadly, some things are missing here that I was really hoping for.</p> <p>Firstly, there’s no talk of GPS/Compass/4g/5g support. Outdoor is not a thing. I get it - this sort display does not lend itself to outdoor - but I was kinda hoping, y’know?</p> <p>I haven’t heard any word on the development story being improved - specifically, holographic remoting and build times (IL2CPP is a slow way to go about things). Native code is a bit of a second class citizen here, so I don’t have much hope for this improving. But dang it would be nice to go from pressing “build” to on device in seconds rather than <em>several</em> minutes.</p> <h2 id="final-bits">Final bits</h2> <p>I haven’t touched on a lot of the cool things to watch out for - mostly because they were cool, but I didn’t have any insight.</p> <p>Windows Hello integration and eye tracking is super cool - finally some simple (automatic) IPD adjustment, which should sharpen things up a bit! I’m pretty interested in how the eye tracking will work from an interaction perspective. I suspect <em>really annoying</em> - we’re not used to things moving with our eyes.</p> <p>The comfort factor is a big one, and it does look much better in this regard. I’m torn about the location of the computing device (keeping it on the unit instead of offloading it a.l.a. Magic Leap). Center of Mass is important, and I’m glad it’s to the geometric center now - but overall weight is, too. We’ll see.</p> <p>Azure Kinect might be a bit of a game changer. I see this becoming a 3d room scanner, a remote-assist companion, a game enabler and a cheap way to get a high quality spectator view for an application (c’mon first party support out of the box for this!). When I watched the MWC announcement, <em>this</em> was the most exciting to me (though mostly because I’d assumed the rest was happening already).</p> <p>This release is <em>super</em> exciting, I can’t wait to get my hands on one and see what it can do.</p> Tue, 26 Feb 2019 13:30:00 +0000 http://stevesspace.com/2019/02/how-does-hololens2-matter/ http://stevesspace.com/2019/02/how-does-hololens2-matter/ Seedy Fake Data <p>With fake user authenication done, we had everything we needed to generate fake data.</p> <p>The seed data was generated on every deployment to our dev and demo environments - which gave us nice, clean, predictable demos, and our dev server was never a terrible mess of temporary data (yeah, you know what I’m talking about).</p> <!--break--> <h2 id="our-approach">Our approach</h2> <p>Our server side was running WebAPI and we had strong types for all our commands, which made it easy to use the same types for communicating with it. We also took CQS fairly seriously at the API level, which greatly simplified everything - we generally didn’t have to worry about parsing return values at all.</p> <p>We had already written some <a href="http://martinfowler.com/bliki/SubcutaneousTest.html">subcutaneous tests</a> for our API, and they all ran on the <a href="https://msdn.microsoft.com/en-us/library/microsoft.owin.testing.testserver(v=vs.113).aspx">OWIN test server</a>. We’d already seperated out separate clients (grouped loosely by aggregate type) that wrapped the HTTP client, meaning we already had a handy way to call API methods from C#.</p> <p>So creating seed data was just a matter of defining data structures that mapped to commands to throw at our API.</p> <h2 id="what-data-do-i-put-in">What data do I put in?</h2> <p>Seed data works best if it is up to date - so we made a point of ensuring that <strong>every time a story was completed, that there was seed data for that story</strong>. If nothing else, it provided assurances of the happy-path usage of our API. But more usefully, when it came time for sprint review, we knew the data was there, and was identical to the data we’d been using on dev the whole time.</p> <p>Most of the data we generated was hard coded - it was relatively important to our client to have realistic and professional looking data. It certainly had its fair share of <code class="language-plaintext highlighter-rouge">Lorem Ipsum</code> still, but we didn’t use <a href="https://github.com/bchavez/Bogus">Bogus</a> for this part. Depending on your domain, using bogus could be a very sensible thing to do, though.</p> <h2 id="why-at-the-api-level">Why at the API level?</h2> <p>A common way is to seed data directly into the DB, but we found this was pretty limiting. The main value of using the API is that our whole environment is ready - specifically, our service bus messages are created, fired off and handled. Our whole system is seeded, not just the database - this is very valuable.</p> <p>Apart from that, we had assurance of our happy-path, and early warning of any slowdowns. You can generate pretty large amounts of data in the sample - and it’s relatively fast too because of the high level of parallelism. If your seed data takes minutes to generate a few hundred entities, then you’ll get frustrated enough to fix your performance problems.</p> <p>That said, there was one part where we twiddle it in the database - some dates were calculated based on the current server time, and our feature was about having warnings for old data. This approach doesn’t prevent hacking in a DB update like that.</p> <h2 id="how-we-did-it">How we did it</h2> <p>There’s a lot of code here - but I thought it worth sharing, because the approach was very successful and flexible. I’ll certainly be copy/pasting from this post in a future project.</p> <p>To start with, we do some initial plumbing to make the API as simple to consume as we can, with appropriate error reporting. Then we make some declarative data structures to represent the seed data, and map them API calls.</p> <h3 id="http-clients">HTTP Clients</h3> <p><code class="language-plaintext highlighter-rouge">System.Net.Http.HttpClient</code> is rather low level, working with <code class="language-plaintext highlighter-rouge">HttpContent</code> and requiring manual insertion of things like bearer tokens and XSRF protection - so we wrote some extension methods which proved quite helpful:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">ApiHttpClientExtensions</span> <span class="p">{</span> <span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpResponseMessage</span><span class="p">&gt;</span> <span class="nf">GetAsync</span><span class="p">(</span><span class="k">this</span> <span class="n">HttpClient</span> <span class="n">httpClient</span><span class="p">,</span> <span class="kt">string</span> <span class="n">url</span><span class="p">,</span> <span class="n">IUser</span> <span class="n">user</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="k">await</span> <span class="nf">CreateRequestAsync</span><span class="p">(</span><span class="n">HttpMethod</span><span class="p">.</span><span class="n">Get</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">user</span><span class="p">);</span> <span class="k">return</span> <span class="k">await</span> <span class="n">httpClient</span><span class="p">.</span><span class="nf">SendAsync</span><span class="p">(</span><span class="n">request</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpResponseMessage</span><span class="p">&gt;</span> <span class="n">PostAsync</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">HttpClient</span> <span class="n">httpClient</span><span class="p">,</span> <span class="kt">string</span> <span class="n">url</span><span class="p">,</span> <span class="n">IUser</span> <span class="n">user</span><span class="p">,</span> <span class="n">T</span> <span class="n">command</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="k">await</span> <span class="nf">CreateRequestAsync</span><span class="p">(</span><span class="n">HttpMethod</span><span class="p">.</span><span class="n">Post</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">user</span><span class="p">);</span> <span class="n">request</span><span class="p">.</span><span class="n">Content</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StringContent</span><span class="p">(</span><span class="n">JsonConvert</span><span class="p">.</span><span class="nf">SerializeObject</span><span class="p">(</span><span class="n">command</span><span class="p">),</span> <span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">,</span> <span class="s">"application/json"</span><span class="p">);</span> <span class="k">return</span> <span class="k">await</span> <span class="n">httpClient</span><span class="p">.</span><span class="nf">SendAsync</span><span class="p">(</span><span class="n">request</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpResponseMessage</span><span class="p">&gt;</span> <span class="nf">PostStreamAsync</span><span class="p">(</span><span class="k">this</span> <span class="n">HttpClient</span> <span class="n">httpClient</span><span class="p">,</span> <span class="kt">string</span> <span class="n">url</span><span class="p">,</span> <span class="n">IUser</span> <span class="n">user</span><span class="p">,</span> <span class="n">FileReference</span> <span class="n">fileReference</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="k">await</span> <span class="nf">CreateRequestAsync</span><span class="p">(</span><span class="n">HttpMethod</span><span class="p">.</span><span class="n">Post</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">user</span><span class="p">);</span> <span class="n">request</span><span class="p">.</span><span class="n">Content</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StreamContent</span><span class="p">(</span><span class="n">fileReference</span><span class="p">.</span><span class="n">Stream</span><span class="p">);</span> <span class="n">request</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="n">Headers</span><span class="p">.</span><span class="n">ContentType</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MediaTypeHeaderValue</span><span class="p">(</span><span class="n">fileReference</span><span class="p">.</span><span class="n">MimeType</span><span class="p">);</span> <span class="k">return</span> <span class="k">await</span> <span class="n">httpClient</span><span class="p">.</span><span class="nf">SendAsync</span><span class="p">(</span><span class="n">request</span><span class="p">);</span> <span class="p">}</span> <span class="c1">// Continue for PATCH, DELETE and so on</span> <span class="k">private</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpRequestMessage</span><span class="p">&gt;</span> <span class="nf">CreateRequestAsync</span><span class="p">(</span><span class="n">HttpMethod</span> <span class="n">method</span><span class="p">,</span> <span class="kt">string</span> <span class="n">url</span><span class="p">,</span> <span class="n">IUser</span> <span class="n">user</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpRequestMessage</span><span class="p">(</span><span class="n">method</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">user</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="n">request</span><span class="p">.</span><span class="n">Headers</span><span class="p">.</span><span class="n">Authorization</span> <span class="p">=</span> <span class="k">await</span> <span class="nf">CreatAuthorizationHeaderForUser</span><span class="p">(</span><span class="n">user</span><span class="p">);</span> <span class="n">request</span><span class="p">.</span><span class="n">Headers</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">XsrfProtection</span><span class="p">.</span><span class="n">XsrfTokenHeaderName</span><span class="p">,</span> <span class="n">XsrfProtection</span><span class="p">.</span><span class="nf">GenerateXsrfCode</span><span class="p">(</span><span class="n">user</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="n">ConfigurationManager</span><span class="p">.</span><span class="n">AppSettings</span><span class="p">[</span><span class="s">"XsrfProtectionServerSecret"</span><span class="p">]));</span> <span class="p">}</span> <span class="k">return</span> <span class="n">request</span><span class="p">;</span> <span class="p">}</span> <span class="k">private</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">AuthenticationHeaderValue</span><span class="p">&gt;</span> <span class="nf">CreatAuthorizationHeaderForUser</span><span class="p">(</span><span class="n">IUser</span> <span class="n">user</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">AuthenticationHeaderValue</span><span class="p">(</span><span class="s">"Bearer"</span><span class="p">,</span> <span class="k">await</span> <span class="n">TestBearerTokenGenerator</span><span class="p">.</span><span class="nf">CreateBearerToken</span><span class="p">(</span><span class="n">user</span><span class="p">));</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>These extension methods do a lot of our work for making our server calls for us, and let us work with the raw message. The user token is also thrown in here - this bit will depend on your auth mechanism, so do with it as you will.</p> <h3 id="checking-for-errors">Checking for errors</h3> <p>Our subcutaneous tests did a lot of checking for errors in responses and reporting them. Here’s the extension methods we used for that.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">ResponseExtensions</span> <span class="p">{</span> <span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">ParseSuccessResponseAsync</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpResponseMessage</span><span class="p">&gt;</span> <span class="n">response</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">await</span> <span class="n">ParseSuccessResponseAsync</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">await</span> <span class="n">response</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">ParseSuccessResponseAsync</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">HttpResponseMessage</span> <span class="n">response</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(!</span><span class="n">response</span><span class="p">.</span><span class="n">IsSuccessStatusCode</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentException</span><span class="p">(</span><span class="s">$"Expected a success status code, but got </span><span class="p">{</span><span class="n">response</span><span class="p">.</span><span class="n">StatusCode</span><span class="p">}</span><span class="s"> for </span><span class="p">{</span><span class="n">response</span><span class="p">.</span><span class="n">RequestMessage</span><span class="p">.</span><span class="n">RequestUri</span><span class="p">}</span><span class="s"> "</span><span class="p">,</span> <span class="k">nameof</span><span class="p">(</span><span class="n">response</span><span class="p">));</span> <span class="kt">var</span> <span class="n">asString</span> <span class="p">=</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsStringAsync</span><span class="p">();</span> <span class="k">return</span> <span class="n">TryDeserialize</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">asString</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpResponseMessage</span><span class="p">&gt;</span> <span class="nf">AndCheckForErrors</span><span class="p">(</span><span class="k">this</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpResponseMessage</span><span class="p">&gt;</span> <span class="n">response</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">await</span> <span class="nf">AndCheckForErrors</span><span class="p">(</span><span class="k">await</span> <span class="n">response</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpResponseMessage</span><span class="p">&gt;</span> <span class="nf">AndCheckForErrors</span><span class="p">(</span><span class="k">this</span> <span class="n">HttpResponseMessage</span> <span class="n">response</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">IsSuccessStatusCode</span> <span class="p">!=</span> <span class="k">true</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">asString</span> <span class="p">=</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsStringAsync</span><span class="p">();</span> <span class="kt">var</span> <span class="n">errorResponse</span> <span class="p">=</span> <span class="n">TryDeserialize</span><span class="p">&lt;</span><span class="n">ErrorResponse</span><span class="p">&gt;(</span><span class="n">asString</span><span class="p">);</span> <span class="kt">var</span> <span class="n">message</span> <span class="p">=</span> <span class="n">errorResponse</span><span class="p">?.</span><span class="n">Message</span> <span class="p">??</span> <span class="n">asString</span><span class="p">;</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">Exception</span><span class="p">(</span><span class="s">$"Response for </span><span class="p">{</span><span class="n">response</span><span class="p">.</span><span class="n">RequestMessage</span><span class="p">.</span><span class="n">RequestUri</span><span class="p">}</span><span class="s"> failed with HTTP </span><span class="p">{</span><span class="n">response</span><span class="p">.</span><span class="n">StatusCode</span><span class="p">}</span><span class="s">: \"</span><span class="p">{</span><span class="n">message</span><span class="p">}</span><span class="s">\""</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="n">response</span><span class="p">;</span> <span class="p">}</span> <span class="k">private</span> <span class="k">static</span> <span class="n">T</span> <span class="n">TryDeserialize</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="kt">string</span> <span class="n">text</span><span class="p">)</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="k">return</span> <span class="n">JsonConvert</span><span class="p">.</span><span class="n">DeserializeObject</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">text</span><span class="p">,</span> <span class="n">Serializers</span><span class="p">.</span><span class="n">ApiSettings</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">default</span><span class="p">(</span><span class="n">T</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>With all this put together, we could make API calls as specific users using our own command objects and ensure that everything was successful.</p> <p>We could also pull data from our API and parse it very simply.</p> <h3 id="domain-specific-clients">Domain-specific clients</h3> <p>We used these extension methods to create aggregate-specific HTTP clients:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">ProductHttpClient</span> <span class="p">{</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">HttpClient</span> <span class="n">_httpClient</span><span class="p">;</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">IUser</span> <span class="n">_user</span><span class="p">;</span> <span class="k">public</span> <span class="nf">ProductHttpClient</span><span class="p">(</span><span class="n">HttpClient</span> <span class="n">httpClient</span><span class="p">,</span> <span class="n">IUser</span> <span class="n">user</span><span class="p">)</span> <span class="p">{</span> <span class="n">_httpClient</span> <span class="p">=</span> <span class="n">httpClient</span><span class="p">;</span> <span class="n">_user</span> <span class="p">=</span> <span class="n">user</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="n">ProductHttpClient</span> <span class="nf">AsUser</span><span class="p">(</span><span class="n">IUser</span> <span class="n">user</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">ProductHttpClient</span><span class="p">(</span><span class="n">_httpClient</span><span class="p">,</span> <span class="n">user</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpResponseMessage</span><span class="p">&gt;</span> <span class="nf">Create</span><span class="p">(</span><span class="n">Guid</span> <span class="n">productId</span><span class="p">,</span> <span class="kt">string</span> <span class="n">name</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">command</span> <span class="p">=</span> <span class="k">new</span> <span class="n">CreateProductCommand</span> <span class="p">{</span> <span class="n">Id</span> <span class="p">=</span> <span class="n">productId</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="n">name</span> <span class="p">};</span> <span class="k">return</span> <span class="n">_httpClient</span><span class="p">.</span><span class="nf">PostAsync</span><span class="p">(</span><span class="s">"/api/products/create"</span><span class="p">,</span> <span class="n">_user</span><span class="p">,</span> <span class="n">command</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpResponseMessage</span><span class="p">&gt;</span> <span class="nf">ChangePrice</span><span class="p">(</span><span class="n">Guid</span> <span class="n">productId</span><span class="p">,</span> <span class="kt">decimal</span> <span class="n">price</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">command</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ChangeProductPriceCommand</span><span class="p">()</span> <span class="p">{</span> <span class="n">Id</span> <span class="p">=</span> <span class="n">productId</span><span class="p">,</span> <span class="n">Price</span> <span class="p">=</span> <span class="n">price</span> <span class="p">};</span> <span class="k">return</span> <span class="n">_httpClient</span><span class="p">.</span><span class="nf">PostAsync</span><span class="p">(</span><span class="s">"/api/products/change-price"</span><span class="p">,</span> <span class="n">_user</span><span class="p">,</span> <span class="n">command</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpResponseMessage</span><span class="p">&gt;</span> <span class="nf">Activate</span><span class="p">(</span><span class="n">Guid</span> <span class="n">productId</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">command</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ActivateProductCommand</span><span class="p">()</span> <span class="p">{</span> <span class="n">Id</span> <span class="p">=</span> <span class="n">productId</span> <span class="p">};</span> <span class="k">return</span> <span class="n">_httpClient</span><span class="p">.</span><span class="nf">PostAsync</span><span class="p">(</span><span class="s">"/api/products/activate"</span><span class="p">,</span> <span class="n">_user</span><span class="p">,</span> <span class="n">command</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">ProductDetails</span><span class="p">&gt;</span> <span class="nf">Details</span><span class="p">(</span><span class="n">Guid</span> <span class="n">productId</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_httpClient</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span><span class="s">$"/api/products/</span><span class="p">{</span><span class="n">productId</span><span class="p">:</span><span class="n">D</span><span class="p">}</span><span class="s">"</span><span class="p">,</span> <span class="n">_user</span><span class="p">).</span><span class="n">ParseSuccessResponseAsync</span><span class="p">&lt;</span><span class="n">ProductDetails</span><span class="p">&gt;();</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Note we still return the raw HTTP requests for commands. This is a hangover from our subcutaneous tests, where we test that an error status code is returned appropriately.</p> <h3 id="seeding-it-all">Seeding it all</h3> <p>All we need now is a definition of our destination data, and a way to get from nothing to that definition.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">ProductDefinition</span> <span class="p">{</span> <span class="k">public</span> <span class="n">Guid</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">decimal</span><span class="p">?</span> <span class="n">Price</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">bool</span> <span class="n">IsActive</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">ProductBuilder</span> <span class="p">{</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">ProductHttpClient</span> <span class="n">_httpClient</span><span class="p">;</span> <span class="k">public</span> <span class="nf">ProductBuilder</span><span class="p">(</span><span class="n">ProductHttpClient</span> <span class="n">httpClient</span><span class="p">)</span> <span class="p">{</span> <span class="n">_httpClient</span> <span class="p">=</span> <span class="n">httpClient</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">Build</span><span class="p">(</span><span class="n">ProductDefinition</span> <span class="n">product</span><span class="p">)</span> <span class="p">{</span> <span class="k">await</span> <span class="n">_httpClient</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="n">product</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="n">product</span><span class="p">.</span><span class="n">Name</span><span class="p">).</span><span class="nf">AndCheckForErrors</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="n">product</span><span class="p">.</span><span class="n">Price</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="k">await</span> <span class="n">_httpClient</span><span class="p">.</span><span class="nf">ChangePrice</span><span class="p">(</span><span class="n">product</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="n">product</span><span class="p">.</span><span class="n">Price</span><span class="p">.</span><span class="n">Value</span><span class="p">).</span><span class="nf">AndCheckForErrors</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="n">product</span><span class="p">.</span><span class="n">IsActive</span><span class="p">)</span> <span class="k">await</span> <span class="n">_httpClient</span><span class="p">.</span><span class="nf">Activate</span><span class="p">(</span><span class="n">product</span><span class="p">.</span><span class="n">Id</span><span class="p">).</span><span class="nf">AndCheckForErrors</span><span class="p">();</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">ProductSeedData</span> <span class="p">{</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">ProductBuilder</span> <span class="n">_productBuilder</span><span class="p">;</span> <span class="k">public</span> <span class="nf">ProductSeedData</span><span class="p">(</span><span class="n">HttpClient</span> <span class="n">httpClient</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">productHttpClient</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ProductHttpClient</span><span class="p">(</span><span class="n">httpClient</span><span class="p">,</span> <span class="n">TestUsers</span><span class="p">.</span><span class="n">Staff</span><span class="p">.</span><span class="n">FuzzyMcStickpants</span><span class="p">);</span> <span class="n">_productBuilder</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ProductBuilder</span><span class="p">(</span><span class="n">productHttpClient</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">SeedAsync</span><span class="p">()</span> <span class="p">{</span> <span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="nf">WhenAll</span><span class="p">(</span> <span class="n">Products</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">_productBuilder</span><span class="p">.</span><span class="n">Build</span><span class="p">)</span> <span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">IReadOnlyList</span><span class="p">&lt;</span><span class="n">ProductDefinition</span><span class="p">&gt;</span> <span class="n">Products</span> <span class="p">=</span> <span class="k">new</span><span class="p">[]</span> <span class="p">{</span> <span class="k">new</span> <span class="n">ProductDefinition</span> <span class="p">{</span> <span class="n">Id</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="nf">NewGuid</span><span class="p">(),</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Bike"</span><span class="p">,</span> <span class="n">Price</span> <span class="p">=</span> <span class="m">800</span><span class="p">,</span> <span class="n">IsActive</span> <span class="p">=</span> <span class="k">true</span> <span class="p">},</span> <span class="k">new</span> <span class="n">ProductDefinition</span> <span class="p">{</span> <span class="n">Id</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="nf">NewGuid</span><span class="p">(),</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Trampoline"</span><span class="p">,</span> <span class="n">Price</span> <span class="p">=</span> <span class="m">120</span><span class="p">,</span> <span class="n">IsActive</span> <span class="p">=</span> <span class="k">true</span> <span class="p">},</span> <span class="k">new</span> <span class="n">ProductDefinition</span> <span class="p">{</span> <span class="n">Id</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="nf">NewGuid</span><span class="p">(),</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Helmet"</span> <span class="p">}</span> <span class="p">};</span> <span class="k">public</span> <span class="k">static</span> <span class="n">ProductDefinition</span> <span class="n">Bike</span> <span class="p">=&gt;</span> <span class="n">Products</span><span class="p">[</span><span class="m">0</span><span class="p">];</span> <span class="k">public</span> <span class="k">static</span> <span class="n">ProductDefinition</span> <span class="n">Trampoline</span> <span class="p">=&gt;</span> <span class="n">Products</span><span class="p">[</span><span class="m">1</span><span class="p">];</span> <span class="p">}</span> <span class="c1">// NOTE: Order definition/builders omitted for brevity</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">OrderSeedData</span> <span class="p">{</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">OrderBuilder</span> <span class="n">_orderBuilder</span><span class="p">;</span> <span class="k">public</span> <span class="nf">OrderSeedData</span><span class="p">(</span><span class="n">HttpClient</span> <span class="n">httpClient</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">orderHttpClient</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">OrderHttpClient</span><span class="p">(</span><span class="n">httpClient</span><span class="p">,</span> <span class="n">TestUsers</span><span class="p">.</span><span class="n">Customers</span><span class="p">.</span><span class="n">BaloneyMaloney</span><span class="p">);</span> <span class="n">_orderBuilder</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">OrderBuilder</span><span class="p">(</span><span class="n">orderHttpClient</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">SeedAsync</span><span class="p">()</span> <span class="p">{</span> <span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="nf">WhenAll</span><span class="p">(</span> <span class="n">Orders</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">_orderBuilder</span><span class="p">.</span><span class="n">Build</span><span class="p">)</span> <span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">IReadOnlyCollection</span><span class="p">&lt;</span><span class="n">OrderDefinition</span><span class="p">&gt;</span> <span class="n">Orders</span> <span class="p">=</span> <span class="k">new</span><span class="p">[]</span> <span class="p">{</span> <span class="k">new</span> <span class="n">OrderDefinition</span> <span class="p">{</span> <span class="n">Id</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="nf">NewGuid</span><span class="p">(),</span> <span class="n">ProductIds</span> <span class="p">=</span> <span class="p">{</span> <span class="n">ProductSeedData</span><span class="p">.</span><span class="n">Bike</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="n">ProductSeedData</span><span class="p">.</span><span class="n">Trampoline</span><span class="p">.</span><span class="n">Id</span> <span class="p">}</span> <span class="p">}</span> <span class="p">};</span> <span class="p">}</span> </code></pre></div></div> <p>Note we kept all our definitions <code class="language-plaintext highlighter-rouge">static</code> so we could reference other ids that were created during the seed process. Our ids were <em>never</em> hardcoded, to prevent issues when running multiple times. Well, I say <em>never</em>, but that’s a lie - people did, and it did cause issues.</p> <p>Finally, we threw it in a console program which ran after our deployment.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">baseUrl</span> <span class="p">=</span> <span class="n">ConfigurationManager</span><span class="p">.</span><span class="n">AppSettings</span><span class="p">[</span><span class="s">"BaseUrl"</span><span class="p">];</span> <span class="kt">var</span> <span class="n">httpClient</span> <span class="p">=</span> <span class="n">HttpClientFactory</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="k">new</span> <span class="nf">HttpClientHandler</span><span class="p">());</span> <span class="n">httpClient</span><span class="p">.</span><span class="n">BaseAddress</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="n">baseUrl</span><span class="p">);</span> <span class="kt">var</span> <span class="n">productSeedData</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ProjectSeedData</span><span class="p">(</span><span class="n">httpClient</span><span class="p">);</span> <span class="kt">var</span> <span class="n">orderSeedData</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">OrderSeedData</span><span class="p">(</span><span class="n">httpClient</span><span class="p">);</span> <span class="k">await</span> <span class="n">productSeedData</span><span class="p">.</span><span class="nf">SeedAsync</span><span class="p">();</span> <span class="k">await</span> <span class="n">orderSeedData</span><span class="p">.</span><span class="nf">SeedAsync</span><span class="p">();</span> </code></pre></div></div> <p>And we’re one octostep away from done, but that’s an exercise for the reader.</p> <h2 id="some-gotchas">Some gotchas</h2> <p>Of course there were some issues!</p> <p>There were days when we were all checking in frequently - which meant lots of deployments to dev. Which meant our data kept getting blown away. Which meant we couldn’t smoke-test our features in the dev environment easily. If this happened more frequently, we could have mitigated it by making the seed data happen on a nightly deployment, instead of every deployment or something. But it wasn’t a big issue.</p> <p>THe network speed was a limitation - our project had image upload, and we wanted good quality images for the demo, so they were large. We were in a government building so (sigh) we had very limited network speed. One of the biggest speed-ups we had in our seed data was shrinking our sample PNG images down.</p> <p>It’s also minldy annoying when you forget to nuke your storage or run your web server before running locally. Ensure you’ve got all your scripts to reset the world and everything around for this.</p> <p>Happy seeding!</p> Thu, 05 Jan 2017 00:45:00 +0000 http://stevesspace.com/2017/01/seedy-fake-data/ http://stevesspace.com/2017/01/seedy-fake-data/ Seedy Fake Users <p>Ever been on a project where a dev comes on board, and has to clone databases in order to get test data? What about when you just want to nuke all your test data and start afresh - is starting afresh pretty painful?</p> <p>We went whole-hog on seed data and test user generation recently, found it to be <em>incredibly</em> useful, and will be doing it on future projects.</p> <p>This post covers the fake user creation aspect.</p> <!--break--> <h2 id="users">Users</h2> <p>When we first rolled in, the authentication story was up in the air, but a lot of the features relied on being an authenticated party. Rolling a test <a href="https://github.com/IdentityServer/IdentityServer3">IdentityServer</a> was the way to go, but filling it up with users seemed laborious.</p> <p>And I’m a lazy programmer.</p> <p>So I generated them! There we some minimal bits of information we needed per user:</p> <ul> <li>Id (guid)</li> <li>Name</li> <li>Email</li> <li>Role</li> </ul> <p>What worked for us was generating data which matched the domain representation of our users, and then mapping them to an in memory store of users.</p> <p>So, generating customers using <a href="https://github.com/bchavez/Bogus">Bogus</a> looks like this:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">CustomerInformation</span> <span class="p">{</span> <span class="k">public</span> <span class="n">Guid</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">FirstName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">LastName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Email</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Country</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">FakeCustomers</span> <span class="p">{</span> <span class="k">private</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">NumCustomers</span> <span class="p">=</span> <span class="m">100</span><span class="p">;</span> <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">Guid</span> <span class="n">CustomerNameSpace</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="nf">Parse</span><span class="p">(</span><span class="s">"0F6C5C66-C102-4F77-94C6-C772813F21F6"</span><span class="p">);</span> <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">Faker</span><span class="p">&lt;</span><span class="n">CustomerInformation</span><span class="p">&gt;</span> <span class="n">CustomerFaker</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Faker</span><span class="p">&lt;</span><span class="n">CustomerInformation</span><span class="p">&gt;()</span> <span class="p">.</span><span class="nf">StrictMode</span><span class="p">(</span><span class="k">true</span><span class="p">)</span> <span class="p">.</span><span class="nf">RuleFor</span><span class="p">(</span><span class="n">c</span> <span class="p">=&gt;</span> <span class="n">c</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="n">f</span> <span class="p">=&gt;</span> <span class="n">GuidUtility</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="n">CustomerNameSpace</span><span class="p">,</span> <span class="n">f</span><span class="p">.</span><span class="n">Random</span><span class="p">.</span><span class="nf">AlphaNumeric</span><span class="p">(</span><span class="m">20</span><span class="p">)))</span> <span class="p">.</span><span class="nf">RuleFor</span><span class="p">(</span><span class="n">c</span> <span class="p">=&gt;</span> <span class="n">c</span><span class="p">.</span><span class="n">FirstName</span><span class="p">,</span> <span class="n">f</span> <span class="p">=&gt;</span> <span class="n">f</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="nf">FirstName</span><span class="p">())</span> <span class="p">.</span><span class="nf">RuleFor</span><span class="p">(</span><span class="n">c</span> <span class="p">=&gt;</span> <span class="n">c</span><span class="p">.</span><span class="n">LastName</span><span class="p">,</span> <span class="n">f</span> <span class="p">=&gt;</span> <span class="n">f</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="nf">LastName</span><span class="p">())</span> <span class="p">.</span><span class="nf">RuleFor</span><span class="p">(</span><span class="n">c</span> <span class="p">=&gt;</span> <span class="n">c</span><span class="p">.</span><span class="n">Email</span><span class="p">,</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="s">$"</span><span class="p">{</span><span class="n">c</span><span class="p">.</span><span class="n">FirstName</span><span class="p">}</span><span class="s">.</span><span class="p">{</span><span class="n">c</span><span class="p">.</span><span class="n">LastName</span><span class="p">}</span><span class="s">@example.com"</span><span class="p">)</span> <span class="p">.</span><span class="nf">RuleFor</span><span class="p">(</span><span class="n">c</span> <span class="p">=&gt;</span> <span class="n">c</span><span class="p">.</span><span class="n">Country</span><span class="p">,</span> <span class="n">f</span> <span class="p">=&gt;</span> <span class="n">f</span><span class="p">.</span><span class="n">Address</span><span class="p">.</span><span class="nf">Country</span><span class="p">());</span> <span class="k">static</span> <span class="nf">FakeCustomers</span><span class="p">()</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">random</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Random</span><span class="p">(</span><span class="m">1</span><span class="p">);</span> <span class="n">Randomizer</span><span class="p">.</span><span class="n">Seed</span> <span class="p">=</span> <span class="n">random</span><span class="p">;</span> <span class="n">All</span> <span class="p">=</span> <span class="n">CustomerFaker</span><span class="p">.</span><span class="nf">Generate</span><span class="p">(</span><span class="n">NumCustomers</span><span class="p">).</span><span class="nf">ToList</span><span class="p">();</span> <span class="p">}</span> <span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">IReadOnlyCollection</span><span class="p">&lt;</span><span class="n">CustomerInformation</span><span class="p">&gt;</span> <span class="n">All</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>This uses a handy <a href="https://github.com/LogosBible/Logos.Utility/blob/master/src/Logos.Utility/GuidUtility.cs">Guid Utility</a> detailed <a href="https://code.logos.com/blog/2011/04/generating_a_deterministic_guid.html">on this blog post</a> to create namespaced deterministic guids.</p> <p>Note that we set the initial random seed - we want to ensure multiple runs produces the same data, the consistency is very helpful.</p> <p>This approach works well because the shared customer data can be pulled into a separate project and used in tests and seed data generation.</p> <p>After customers are created, we set up our Identity Server to use them:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">Users</span> <span class="p">{</span> <span class="k">public</span> <span class="k">static</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">InMemoryUser</span><span class="p">&gt;</span> <span class="n">All</span> <span class="p">{</span> <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">FakeCustomers</span><span class="p">.</span><span class="n">All</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">c</span> <span class="p">=&gt;</span> <span class="nf">CreateUser</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="n">c</span><span class="p">.</span><span class="n">FirstName</span><span class="p">,</span> <span class="n">c</span><span class="p">.</span><span class="n">LastName</span><span class="p">,</span> <span class="n">c</span><span class="p">.</span><span class="n">Email</span><span class="p">,</span> <span class="s">"Customer"</span><span class="p">))</span> <span class="p">}</span> <span class="p">}</span> <span class="k">private</span> <span class="k">static</span> <span class="nf">CreateUser</span><span class="p">(</span><span class="n">Guid</span> <span class="n">id</span><span class="p">,</span> <span class="kt">string</span> <span class="n">firstName</span><span class="p">,</span> <span class="kt">string</span> <span class="n">lastName</span><span class="p">,</span> <span class="kt">string</span> <span class="n">email</span><span class="p">,</span> <span class="kt">string</span> <span class="n">role</span><span class="p">)</span> <span class="p">{</span> <span class="kt">string</span> <span class="n">username</span> <span class="p">=</span> <span class="s">$"</span><span class="p">{</span><span class="n">firstName</span><span class="p">}</span><span class="s">.</span><span class="p">{</span><span class="n">lastName</span><span class="p">}</span><span class="s">"</span><span class="p">;</span> <span class="kt">var</span> <span class="n">claims</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Claim</span><span class="p">&gt;(</span><span class="k">new</span><span class="p">[]</span> <span class="p">{</span> <span class="k">new</span> <span class="nf">Claim</span><span class="p">(</span><span class="n">Constants</span><span class="p">.</span><span class="n">ClaimTypes</span><span class="p">.</span><span class="n">Subject</span><span class="p">,</span> <span class="n">username</span><span class="p">),</span> <span class="k">new</span> <span class="nf">Claim</span><span class="p">(</span><span class="n">Constants</span><span class="p">.</span><span class="n">ClaimTypes</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="n">id</span><span class="p">.</span><span class="nf">ToString</span><span class="p">(</span><span class="s">"D"</span><span class="p">).</span><span class="nf">ToUpperInvariant</span><span class="p">()),</span> <span class="k">new</span> <span class="nf">Claim</span><span class="p">(</span><span class="n">Constants</span><span class="p">.</span><span class="n">ClaimTypes</span><span class="p">.</span><span class="n">Email</span><span class="p">,</span> <span class="n">email</span><span class="p">),</span> <span class="k">new</span> <span class="nf">Claim</span><span class="p">(</span><span class="n">Constants</span><span class="p">.</span><span class="n">ClaimTypes</span><span class="p">.</span><span class="n">GivenName</span><span class="p">,</span> <span class="n">firstName</span><span class="p">),</span> <span class="k">new</span> <span class="nf">Claim</span><span class="p">(</span><span class="n">Constants</span><span class="p">.</span><span class="n">ClaimTypes</span><span class="p">.</span><span class="n">FamilyName</span><span class="p">,</span> <span class="n">lastName</span><span class="p">),</span> <span class="k">new</span> <span class="nf">Claim</span><span class="p">(</span><span class="n">Constants</span><span class="p">.</span><span class="n">ClaimTypes</span><span class="p">.</span><span class="n">Role</span><span class="p">,</span> <span class="n">role</span><span class="p">),</span> <span class="p">});</span> <span class="k">return</span> <span class="k">new</span> <span class="n">InMemoryUser</span> <span class="p">{</span> <span class="n">Subject</span> <span class="p">=</span> <span class="n">username</span><span class="p">,</span> <span class="n">Username</span> <span class="p">=</span> <span class="n">username</span><span class="p">,</span> <span class="n">Password</span> <span class="p">=</span> <span class="s">"test"</span><span class="p">,</span> <span class="n">Enabled</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">Claims</span> <span class="p">=</span> <span class="n">claims</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">()</span> <span class="p">};</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>And at startup:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">factory</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">IdentityServerServiceFactory</span><span class="p">();</span> <span class="n">factory</span> <span class="p">.</span><span class="nf">UseInMemoryClients</span><span class="p">(</span><span class="cm">/*as required*/</span><span class="p">)</span> <span class="p">.</span><span class="nf">UseInMemoryScopes</span><span class="p">(</span><span class="cm">/*as required*/</span><span class="p">)</span> <span class="p">.</span><span class="nf">UseInMemoryUsers</span><span class="p">(</span><span class="n">Users</span><span class="p">.</span><span class="n">All</span><span class="p">.</span><span class="nf">ToList</span><span class="p">());</span> </code></pre></div></div> <p>Finally, hack some markup so you don’t have to remember the login details. Since this is for dev only, it doesn’t matter!</p> <p>In <code class="language-plaintext highlighter-rouge">templates/_login.html</code>:</p> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;h3</span> <span class="na">style=</span><span class="s">"clear: both;"</span><span class="nt">&gt;</span>Customer<span class="nt">&lt;/h3&gt;</span> <span class="nt">&lt;form</span> <span class="na">ng-repeat=</span><span class="s">"user in model.custom.customers | limitTo:5"</span> <span class="na">method=</span><span class="s">"post"</span> <span class="na">action=</span><span class="s">""</span> <span class="na">class=</span><span class="s">"login-button-form"</span><span class="nt">&gt;</span> <span class="nt">&lt;anti-forgery-token</span> <span class="na">token=</span><span class="s">"model.antiForgery"</span><span class="nt">&gt;&lt;/anti-forgery-token&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"username"</span> <span class="na">value=</span><span class="s">""</span><span class="nt">/&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"password"</span> <span class="na">value=</span><span class="s">"test"</span><span class="nt">/&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"rememberMe"</span> <span class="na">value=</span><span class="s">"true"</span><span class="nt">&gt;</span> <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"form-group"</span><span class="nt">&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">""</span> <span class="na">class=</span><span class="s">"button"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/form&gt;</span> </code></pre></div></div> <p>In a <code class="language-plaintext highlighter-rouge">CustomViewService</code>:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">model</span><span class="p">.</span><span class="n">Custom</span> <span class="p">=</span> <span class="k">new</span> <span class="p">{</span> <span class="n">customers</span> <span class="p">=</span> <span class="n">FakeCustomers</span><span class="p">.</span><span class="n">All</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">c</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="p">{</span> <span class="n">username</span> <span class="p">=</span> <span class="s">$"</span><span class="p">{</span><span class="n">c</span><span class="p">.</span><span class="n">FirstName</span><span class="p">}</span><span class="s">.</span><span class="p">{</span><span class="n">c</span><span class="p">.</span><span class="n">LastName</span><span class="p">}</span><span class="s">"</span><span class="p">,</span> <span class="n">display</span> <span class="p">=</span> <span class="s">$"</span><span class="p">{</span><span class="n">c</span><span class="p">.</span><span class="n">FirstName</span><span class="p">}</span><span class="s"> </span><span class="p">{</span><span class="n">c</span><span class="p">.</span><span class="n">LastName</span><span class="p">}</span><span class="s">"</span> <span class="p">}).</span><span class="nf">ToArray</span><span class="p">()</span> <span class="p">}</span> </code></pre></div></div> <p>These conventions match our user generation, and a form per user means single-button login for everything.</p> <p><img src="/images/2016-01-04-fake-login.png" alt="Login sample" /></p> <p>What a nice dev login experience we have.</p> <p>We also did a small amount of codegen to “hardcode” some of the users by name, by generating a static class like:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">TestUsers</span> <span class="p">{</span> <span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">Customers</span> <span class="p">{</span> <span class="k">public</span> <span class="k">static</span> <span class="n">CustomerInformation</span> <span class="n">JohnSmith</span> <span class="p">=&gt;</span> <span class="n">FakeCustomers</span><span class="p">.</span><span class="n">All</span><span class="p">.</span><span class="nf">Skip</span><span class="p">(</span><span class="m">0</span><span class="p">).</span><span class="nf">First</span><span class="p">();</span> <span class="k">public</span> <span class="k">static</span> <span class="n">CustomerInformation</span> <span class="n">MaryJane</span> <span class="p">=&gt;</span> <span class="n">FakeCustomers</span><span class="p">.</span><span class="n">All</span><span class="p">.</span><span class="nf">Skip</span><span class="p">(</span><span class="m">1</span><span class="p">).</span><span class="nf">First</span><span class="p">();</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Meaning in a test, we can go</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">customer</span> <span class="p">=</span> <span class="n">TestUsers</span><span class="p">.</span><span class="n">Customers</span><span class="p">.</span><span class="n">JohnSmith</span><span class="p">;</span> </code></pre></div></div> <p>Which is more handy than it sounds.</p> <p>This codegen is relatively easy, if you want to go that route. We found we didn’t use many different users, and since our usernames don’t change it would probably have been simpler to just manually add them as we needed them.</p> <p>All in all, this approach worked fantastically - we set up relationships between users and groups using the same approach, and not having to worry about remember user names was great. Especially during our sprint reviews, being able to easily sign in and out of different roles as our stakeholders wanted to see different bits was a life saver.</p> <p>Stay tuned for the next part - exercising our API by generating seed data.</p> Wed, 04 Jan 2017 03:30:00 +0000 http://stevesspace.com/2017/01/seedy-fake-users/ http://stevesspace.com/2017/01/seedy-fake-users/ Put your operations on your client <p>In the last post, <a href="/2016/12/put-your-server-types-on-client/">I showed how we put our server DTOs into our client code</a>, to ensure changes in our data structures didn’t silently fail. In this post, I’ll show you how we protected ourselves against changing API endpoints. <!--break--></p> <h2 id="our-application">Our application</h2> <p>As before, our application is a WebAPI project with an Angular/TypeScript front end.</p> <p>We fully embraced attribute routing for this project, and put <a href="https://github.com/andrewabest/Conventional">convention tests</a> in to ensure every controller and action contains attribute routes. Advantage: We know where to look to find URLs! Handy.</p> <h2 id="our-goal">Our goal</h2> <p>The goal here is to make a module that contains a way to get the URL for each operation. For static URLs, this is just a <code class="language-plaintext highlighter-rouge">const</code>.</p> <p>For dynamic URLs, it is a function that takes in the typed parameters and returns the URL.</p> <p>Examples? Sure, why not!</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export var getOrders = `/api/orders`; export function getOrderDetails(id: string) { return `/api/orders/{id}`.replace(`{id}`, encodeURIComponent(id)); } </code></pre></div></div> <p>Our approach was simply to loop through all these attributes and build URLs for them.</p> <h2 id="the-codes">The codes</h2> <p>First step is looping through all the controllers and grabbing the <code class="language-plaintext highlighter-rouge">RoutePrefix</code> and <code class="language-plaintext highlighter-rouge">Route</code> attributes, joining them together and parsing them into something useful.</p> <p>First we define a handy class to abstract away the route infos</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">RouteInfo</span> <span class="p">{</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">MethodInfo</span> <span class="n">_method</span><span class="p">;</span> <span class="k">public</span> <span class="nf">RouteInfo</span><span class="p">(</span><span class="n">MethodInfo</span> <span class="n">method</span><span class="p">)</span> <span class="p">{</span> <span class="n">_method</span> <span class="p">=</span> <span class="n">method</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">=&gt;</span> <span class="p">(</span><span class="n">_method</span><span class="p">.</span><span class="n">DeclaringType</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="nf">Substring</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">1</span><span class="p">).</span><span class="nf">ToLower</span><span class="p">()</span> <span class="p">+</span> <span class="n">_method</span><span class="p">.</span><span class="n">DeclaringType</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="nf">Substring</span><span class="p">(</span><span class="m">1</span><span class="p">)).</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"Controller"</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">FullRouteTemplate</span> <span class="p">=&gt;</span> <span class="s">$"/</span><span class="p">{</span><span class="n">Prefix</span><span class="p">}</span><span class="s">/</span><span class="p">{</span><span class="n">Route</span><span class="p">}</span><span class="s">"</span><span class="p">;</span> <span class="k">private</span> <span class="kt">string</span> <span class="n">Prefix</span> <span class="p">=&gt;</span> <span class="n">AttributeOnMethodOrType</span><span class="p">&lt;</span><span class="n">RoutePrefixAttribute</span><span class="p">&gt;().</span><span class="n">Prefix</span><span class="p">;</span> <span class="k">private</span> <span class="kt">string</span> <span class="n">Route</span> <span class="p">=&gt;</span> <span class="n">AttributeOnMethodOrType</span><span class="p">&lt;</span><span class="n">RouteAttribute</span><span class="p">&gt;().</span><span class="n">Template</span><span class="p">;</span> <span class="k">private</span> <span class="n">T</span> <span class="n">AttributeOnMethodOrType</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;()</span> <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="n">Attribute</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_method</span><span class="p">.</span><span class="n">GetCustomAttribute</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;()</span> <span class="p">??</span> <span class="n">_method</span><span class="p">.</span><span class="n">DeclaringType</span><span class="p">.</span><span class="n">GetCustomAttribute</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;();</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Then we make route infos from the action methods. We do a poor man’s distinct by on the full template here, perhaps overly and erroneously defensive, but there you have it.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">actionMethods</span> <span class="p">=</span> <span class="k">typeof</span><span class="p">(</span><span class="n">FooController</span><span class="p">).</span><span class="n">Assembly</span> <span class="p">.</span><span class="nf">GetLoadableTypes</span><span class="p">()</span> <span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="n">t</span><span class="p">.</span><span class="nf">IsSubclassOf</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">ApiController</span><span class="p">)))</span> <span class="p">.</span><span class="nf">SelectMany</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="n">t</span><span class="p">.</span><span class="nf">GetMethods</span><span class="p">(</span><span class="n">BindingFlags</span><span class="p">.</span><span class="n">Public</span> <span class="p">|</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">Instance</span> <span class="p">|</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">InvokeMethod</span> <span class="p">|</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">DeclaredOnly</span><span class="p">))</span> <span class="p">.</span><span class="nf">ToList</span><span class="p">();</span> <span class="kt">var</span> <span class="n">routes</span> <span class="p">=</span> <span class="n">actionMethods</span> <span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">m</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="nf">RouteInfo</span><span class="p">(</span><span class="n">m</span><span class="p">))</span> <span class="p">.</span><span class="nf">GroupBy</span><span class="p">(</span><span class="n">r</span> <span class="p">=&gt;</span> <span class="n">r</span><span class="p">.</span><span class="n">FullRouteTemplate</span><span class="p">)</span> <span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">g</span> <span class="p">=&gt;</span> <span class="n">g</span><span class="p">.</span><span class="nf">First</span><span class="p">())</span> <span class="p">.</span><span class="nf">ToList</span><span class="p">();</span> </code></pre></div></div> <p>Then use some rad regex and munging to make typescript from these routes. Here we go…</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">Regex</span> <span class="n">ParamsRegex</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Regex</span><span class="p">(</span><span class="s">@"\{(?&lt;name&gt;[^?\}:]+):?(?&lt;constraint&gt;[^?\}]+)?(?&lt;isOptional&gt;[^\}]+)?\}"</span><span class="p">);</span> <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">Regex</span> <span class="n">ParamsReplaceRegex</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Regex</span><span class="p">(</span><span class="s">"\\:[^}]+"</span><span class="p">);</span> <span class="k">private</span> <span class="k">static</span> <span class="kt">string</span> <span class="nf">BuildRouteLine</span><span class="p">(</span><span class="n">RouteInfo</span> <span class="n">route</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(!</span><span class="n">route</span><span class="p">.</span><span class="n">FullRouteTemplate</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="s">"{"</span><span class="p">))</span> <span class="k">return</span> <span class="s">$"export var </span><span class="p">{</span><span class="n">route</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s"> = `</span><span class="p">{</span><span class="n">route</span><span class="p">.</span><span class="n">FullRouteTemplate</span><span class="p">}</span><span class="s">`;"</span><span class="p">;</span> <span class="kt">var</span> <span class="n">parameters</span> <span class="p">=</span> <span class="n">ParamsRegex</span><span class="p">.</span><span class="nf">Matches</span><span class="p">(</span><span class="n">route</span><span class="p">.</span><span class="n">FullRouteTemplate</span><span class="p">)</span> <span class="p">.</span><span class="n">Cast</span><span class="p">&lt;</span><span class="n">Match</span><span class="p">&gt;()</span> <span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">m</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="p">{</span> <span class="n">Name</span> <span class="p">=</span> <span class="n">m</span><span class="p">.</span><span class="n">Groups</span><span class="p">[</span><span class="s">"name"</span><span class="p">].</span><span class="n">Value</span><span class="p">,</span> <span class="n">Constraint</span> <span class="p">=</span> <span class="n">m</span><span class="p">.</span><span class="n">Groups</span><span class="p">[</span><span class="s">"constraint"</span><span class="p">].</span><span class="n">Value</span><span class="p">,</span> <span class="n">IsOptional</span> <span class="p">=</span> <span class="n">m</span><span class="p">.</span><span class="n">Groups</span><span class="p">[</span><span class="s">"isOptional"</span><span class="p">].</span><span class="n">Success</span> <span class="p">})</span> <span class="p">.</span><span class="nf">ToList</span><span class="p">();</span> <span class="kt">var</span> <span class="n">parameterNamesAndTypes</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="s">", "</span><span class="p">,</span> <span class="n">parameters</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="s">$"</span><span class="p">{</span><span class="n">p</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">: </span><span class="p">{</span><span class="nf">ConvertRouteConstraintToTsType</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">Constraint</span><span class="p">)}</span><span class="s">"</span><span class="p">));</span> <span class="kt">var</span> <span class="n">routeWithoutTypeNames</span> <span class="p">=</span> <span class="n">ParamsReplaceRegex</span><span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="n">route</span><span class="p">.</span><span class="n">FullRouteTemplate</span><span class="p">,</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">);</span> <span class="kt">var</span> <span class="n">functionContents</span> <span class="p">=</span> <span class="n">parameters</span><span class="p">.</span><span class="nf">Aggregate</span><span class="p">(</span><span class="s">$"return `</span><span class="p">{</span><span class="n">routeWithoutTypeNames</span><span class="p">}</span><span class="s">`"</span><span class="p">,</span> <span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">parameter</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nf">ReplaceParameter</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">parameter</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span> <span class="n">parameter</span><span class="p">.</span><span class="n">IsOptional</span><span class="p">))</span> <span class="p">+</span> <span class="s">";"</span><span class="p">;</span> <span class="k">return</span> <span class="s">$"export function </span><span class="p">{</span><span class="n">route</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">(</span><span class="p">{</span><span class="n">parameterNamesAndTypes</span><span class="p">}</span><span class="s">) </span><span class="p">{{{</span><span class="n">Environment</span><span class="p">.</span><span class="n">NewLine</span><span class="p">}</span> <span class="p">{</span> <span class="n">functionContents</span> <span class="p">}{</span><span class="n">Environment</span><span class="p">.</span><span class="n">NewLine</span><span class="p">}}}</span><span class="s">"</span><span class="p">;</span> <span class="p">}</span> <span class="k">private</span> <span class="k">static</span> <span class="kt">string</span> <span class="nf">ConvertRouteConstraintToTsType</span><span class="p">(</span><span class="kt">string</span> <span class="n">constraint</span><span class="p">)</span> <span class="p">{</span> <span class="k">switch</span> <span class="p">(</span><span class="n">constraint</span><span class="p">)</span> <span class="p">{</span> <span class="k">case</span> <span class="s">"alpha"</span><span class="p">:</span> <span class="k">case</span> <span class="s">"guid"</span><span class="p">:</span> <span class="k">case</span> <span class="s">"datetime"</span><span class="p">:</span> <span class="k">return</span> <span class="s">"string"</span><span class="p">;</span> <span class="k">case</span> <span class="s">"decimal"</span><span class="p">:</span> <span class="k">case</span> <span class="s">"double"</span><span class="p">:</span> <span class="k">case</span> <span class="s">"float"</span><span class="p">:</span> <span class="k">case</span> <span class="s">"int"</span><span class="p">:</span> <span class="k">case</span> <span class="s">"long"</span><span class="p">:</span> <span class="k">return</span> <span class="s">"number"</span><span class="p">;</span> <span class="k">case</span> <span class="s">"bool"</span><span class="p">:</span> <span class="k">return</span> <span class="s">"Boolean"</span><span class="p">;</span> <span class="k">default</span><span class="p">:</span> <span class="c1">// No type mappings for constraints like length, max, maxlength, min, minlength, range, &amp; regex</span> <span class="k">return</span> <span class="s">"any"</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Let’s go through it bit by bit:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(!</span><span class="n">route</span><span class="p">.</span><span class="n">FullRouteTemplate</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="s">"{"</span><span class="p">))</span> <span class="k">return</span> <span class="s">$"export const </span><span class="p">{</span><span class="n">route</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s"> = `</span><span class="p">{</span><span class="n">route</span><span class="p">.</span><span class="n">FullRouteTemplate</span><span class="p">}</span><span class="s">`;"</span><span class="p">;</span> </code></pre></div></div> <p>If there are no curlies, then there are no parameters - just output the template as-is - easy as!</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">parameters</span> <span class="p">=</span> <span class="n">ParamsRegex</span><span class="p">.</span><span class="nf">Matches</span><span class="p">(</span><span class="n">route</span><span class="p">.</span><span class="n">FullRouteTemplate</span><span class="p">)</span> <span class="p">.</span><span class="n">Cast</span><span class="p">&lt;</span><span class="n">Match</span><span class="p">&gt;()</span> <span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">m</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="p">{</span> <span class="n">Name</span> <span class="p">=</span> <span class="n">m</span><span class="p">.</span><span class="n">Groups</span><span class="p">[</span><span class="s">"name"</span><span class="p">].</span><span class="n">Value</span><span class="p">,</span> <span class="n">Constraint</span> <span class="p">=</span> <span class="n">m</span><span class="p">.</span><span class="n">Groups</span><span class="p">[</span><span class="s">"constraint"</span><span class="p">].</span><span class="n">Value</span><span class="p">,</span> <span class="n">IsOptional</span> <span class="p">=</span> <span class="n">m</span><span class="p">.</span><span class="n">Groups</span><span class="p">[</span><span class="s">"isOptional"</span><span class="p">].</span><span class="n">Success</span> <span class="p">})</span> <span class="p">.</span><span class="nf">ToList</span><span class="p">();</span> </code></pre></div></div> <p>Here we make anonymous types for all the constraints using our regex. Of course, the regex is the most crazy part of this whole thing, but what it essentially does it grab <code class="language-plaintext highlighter-rouge">id</code>, <code class="language-plaintext highlighter-rouge">guid</code> and <code class="language-plaintext highlighter-rouge">optional</code> from something like <code class="language-plaintext highlighter-rouge">{id:guid?}</code>. It will match multiple in something like <code class="language-plaintext highlighter-rouge">/api/orders/{status:string}/{top:int?}</code> (that’s a terrible URL, don’t use it in your project).</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">parameterNamesAndTypes</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="s">", "</span><span class="p">,</span> <span class="n">parameters</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="s">$"</span><span class="p">{</span><span class="n">p</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">: </span><span class="p">{</span><span class="nf">ConvertRouteConstraintToTsType</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">Constraint</span><span class="p">)}</span><span class="s">"</span><span class="p">));</span> </code></pre></div></div> <p>This chunk makes a string that looks like <code class="language-plaintext highlighter-rouge">status: string, top?: number</code> - these will be the parameters of the generated method.<br /> Note we convert the route types to TS friendly types using a good ol’ <code class="language-plaintext highlighter-rouge">switch</code> statement, falling back to <code class="language-plaintext highlighter-rouge">any</code> when they do not match.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">routeWithoutTypeNames</span> <span class="p">=</span> <span class="n">ParamsReplaceRegex</span><span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="n">route</span><span class="p">.</span><span class="n">FullRouteTemplate</span><span class="p">,</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">);</span> <span class="kt">var</span> <span class="n">functionContents</span> <span class="p">=</span> <span class="n">parameters</span><span class="p">.</span><span class="nf">Aggregate</span><span class="p">(</span><span class="s">$"return `</span><span class="p">{</span><span class="n">routeWithoutTypeNames</span><span class="p">}</span><span class="s">`"</span><span class="p">,</span> <span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">parameter</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nf">ReplaceParameter</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">parameter</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span> <span class="n">parameter</span><span class="p">.</span><span class="n">IsOptional</span><span class="p">))</span> <span class="p">+</span> <span class="s">";"</span><span class="p">;</span> </code></pre></div></div> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">private</span> <span class="k">static</span> <span class="kt">string</span> <span class="nf">ReplaceParameter</span><span class="p">(</span><span class="kt">string</span> <span class="n">current</span><span class="p">,</span> <span class="kt">string</span> <span class="n">paramName</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">isOptional</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">isOptional</span> <span class="p">?</span> <span class="s">$"</span><span class="p">{</span><span class="n">current</span><span class="p">}</span><span class="s">.replace(`/</span><span class="p">{{{</span><span class="n">paramName</span><span class="p">}}}</span><span class="s">`, </span><span class="p">{</span><span class="n">paramName</span><span class="p">}</span><span class="s"> === undefined ? `` : `/$</span><span class="p">{{</span><span class="nf">encodeURIComponent</span><span class="p">({</span><span class="n">paramName</span><span class="p">})}}</span><span class="s">`)"</span> <span class="p">:</span> <span class="s">$"</span><span class="p">{</span><span class="n">current</span><span class="p">}</span><span class="s">.replace(`</span><span class="p">{{{</span><span class="n">paramName</span><span class="p">}}}</span><span class="s">`, encodeURIComponent(</span><span class="p">{</span><span class="n">paramName</span><span class="p">}</span><span class="s">))"</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>This part changes our route <code class="language-plaintext highlighter-rouge">/api/orders/{status:string}/{top:int?}</code> to <code class="language-plaintext highlighter-rouge">/api/orders/{status}/{top}</code>, which makes it suitable for ts substitution.</p> <p>We then just make function contents do a fairly naive string replace of those parameters. It ends up looking like:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> `/api/orders/{status}/{top}`.replace(`{status}`, encodeURIComponent(status)).replace(`{top}`, top === undefined ? `` : encodeURIComponent(top)); </code></pre></div></div> <p>We rarely used optional parameters, so it was pretty naive implementation.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">return</span> <span class="s">$"export function </span><span class="p">{</span><span class="n">route</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">(</span><span class="p">{</span><span class="n">parameterNamesAndTypes</span><span class="p">}</span><span class="s">) </span><span class="p">{{{</span><span class="n">Environment</span><span class="p">.</span><span class="n">NewLine</span><span class="p">}</span> <span class="p">{</span> <span class="n">functionContents</span> <span class="p">}{</span><span class="n">Environment</span><span class="p">.</span><span class="n">NewLine</span><span class="p">}}}</span><span class="s">"</span><span class="p">;</span> </code></pre></div></div> <p>This bit glues all the others bits together, and we’re done making the function contents.</p> <p>:boom:</p> <h2 id="writing-it-out">Writing it out.</h2> <p>We have route infos, and a way of making TS code for those routes. Now let’s jam it in a file:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">builder</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StringBuilder</span><span class="p">();</span> <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">route</span> <span class="k">in</span> <span class="n">routes</span><span class="p">)</span> <span class="p">{</span> <span class="n">builder</span><span class="p">.</span><span class="nf">AppendLine</span><span class="p">(</span><span class="nf">BuildRouteLine</span><span class="p">(</span><span class="n">route</span><span class="p">));</span> <span class="p">}</span> <span class="n">File</span><span class="p">.</span><span class="nf">WriteAllText</span><span class="p">(</span><span class="s">"urls.ts"</span><span class="p">,</span> <span class="n">builder</span><span class="p">.</span><span class="nf">ToString</span><span class="p">());</span> </code></pre></div></div> <p>Done! We win at putting urls on clients!</p> <h2 id="using-it">Using it</h2> <p>We wrapped all our HTTP calls into service classes, which looked thus:</p> <div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">urls</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../urls</span><span class="dl">"</span><span class="p">;</span> <span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderService</span> <span class="p">{</span> <span class="k">static</span> <span class="nx">$inject</span> <span class="o">=</span> <span class="p">[</span><span class="dl">"</span><span class="s2">$http</span><span class="dl">"</span><span class="p">];</span> <span class="kd">constructor</span><span class="p">(</span><span class="k">private</span> <span class="nx">$http</span><span class="p">:</span> <span class="nx">ng</span><span class="p">.</span><span class="nx">IHttpService</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span> <span class="nx">orders</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">$http</span><span class="p">.</span><span class="kd">get</span><span class="o">&lt;</span><span class="nx">Api</span><span class="p">.</span><span class="nx">OrderSummary</span><span class="p">[]</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">urls</span><span class="p">.</span><span class="nx">getOrders</span><span class="p">)</span> <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">result</span> <span class="o">=&gt;</span> <span class="nx">result</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span> <span class="p">}</span> <span class="nx">getOrder</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">$http</span><span class="p">.</span><span class="kd">get</span><span class="o">&lt;</span><span class="nx">Api</span><span class="p">.</span><span class="nx">OrderDetails</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">urls</span><span class="p">.</span><span class="nx">getOrderDetails</span><span class="p">(</span><span class="nx">id</span><span class="p">))</span> <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">result</span> <span class="o">=&gt;</span> <span class="nx">result</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span> <span class="p">}</span> <span class="nx">saveOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Api</span><span class="p">.</span><span class="nx">OrderDetails</span><span class="p">)</span> <span class="p">:</span> <span class="nx">ng</span><span class="p">.</span><span class="nx">IPromise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">$http</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="nx">urls</span><span class="p">.</span><span class="nx">saveOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">),</span> <span class="nx">params</span><span class="p">)</span> <span class="p">.</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="p">}));</span> <span class="c1">// Makes return type void</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <h2 id="going-further">Going further</h2> <p>This could be automated too - by looking at the HTTP method and body-valued things, but we had enough custom logic in our services that this wasn’t worthwhile. For instance, some HTTP calls would show/hide loading indicators, or handle errors differently. We felt this was a happy medium with a relatively simple implementation.</p> <p>Automating this bit would have taken a fair chunk of time - we would have had to marry up the route infos with the controller actions/types - and while this is fairly straight forward for return types, parameter types get pretty hairy with <code class="language-plaintext highlighter-rouge">[FromBody]</code> and <code class="language-plaintext highlighter-rouge">[FromUri]</code> attributes, optional arguments, primitive vs class identification and the different handling for POST and GET.</p> <p>And even then we’d still have to wrap <em>that</em> for reasons mentioned above. In the end, compile time URL validation was good enough, and saved us tonnes of time. You should do it too!</p> Tue, 03 Jan 2017 03:30:00 +0000 http://stevesspace.com/2017/01/put-your-operations-on-your-client/ http://stevesspace.com/2017/01/put-your-operations-on-your-client/ Put your server types on your client <p>A recent gig I was involved in relied fairly heavily on code generation in order to make our client/server communications type safe.</p> <p>We were using <a href="https://www.typescriptlang.org">TypeScript</a> so a lot of our safety could be guaranteed at compile time - as long as the types were on the client. Since we’re fallibe, and computers like doing things repetitively, we used some codegen to do this for us.</p> <p>Our goals?</p> <ul> <li>Generate DTOs for all models (and dependants) going to/from API controllers</li> <li>For all enums in the models, create a way to get names, values and descriptions</li> <li>Avoid TypeScript’s <code class="language-plaintext highlighter-rouge">any</code> keyword, which breaks this whole approach</li> </ul> <!--break--> <p>I will be covering URL and client generation in another post - this is about the <em>types</em> not the <em>operations</em>.</p> <h2 id="generating-defintions-typelite">Generating Defintions: TypeLite</h2> <p><a href="http://type.litesolutions.net/">TypeLite</a> did the bulk of the heavy lifting for us - it had appropriate extensibility points, making it easy to customise things like camel casing and type names.</p> <p>We found the easiest thing to do was to add a command like project to our solution, and a wrapper powershell script to compile and execute it. This project could then just point to the assembly we wanted types for and away we go!</p> <p>Here’s what our generator looked like:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Program</span> <span class="p">{</span> <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span><span class="p">()</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">apiTypes</span> <span class="p">=</span> <span class="k">typeof</span><span class="p">(</span><span class="n">FooController</span><span class="p">).</span><span class="n">Assembly</span><span class="p">.</span><span class="nf">GetExportedTypes</span><span class="p">()</span> <span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="n">t</span><span class="p">.</span><span class="nf">IsSubclassOf</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">ApiController</span><span class="p">)))</span> <span class="p">.</span><span class="nf">SelectMany</span><span class="p">(</span><span class="n">type</span> <span class="p">=&gt;</span> <span class="n">type</span><span class="p">.</span><span class="nf">GetMethods</span><span class="p">(</span><span class="n">BindingFlags</span><span class="p">.</span><span class="n">Public</span> <span class="p">|</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">Instance</span> <span class="p">|</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">InvokeMethod</span><span class="p">))</span> <span class="p">.</span><span class="nf">SelectMany</span><span class="p">(</span><span class="n">ParameterAndReturnTypes</span><span class="p">)</span> <span class="p">.</span><span class="nf">SelectMany</span><span class="p">(</span><span class="n">Unwrap</span><span class="p">)</span> <span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="p">!</span><span class="n">t</span><span class="p">.</span><span class="n">IsPrimitive</span> <span class="p">&amp;&amp;</span> <span class="n">t</span> <span class="p">!=</span> <span class="k">typeof</span><span class="p">(</span><span class="kt">string</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="n">t</span> <span class="p">!=</span> <span class="k">typeof</span><span class="p">(</span><span class="kt">object</span><span class="p">))</span> <span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="p">!</span><span class="n">t</span><span class="p">.</span><span class="n">Namespace</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"System."</span><span class="p">))</span> <span class="c1">// Customize this bit to suit your app</span> <span class="p">.</span><span class="nf">Distinct</span><span class="p">()</span> <span class="p">.</span><span class="nf">OrderBy</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="n">t</span><span class="p">.</span><span class="n">FullName</span><span class="p">)</span> <span class="c1">// Makes output better for diff</span> <span class="p">.</span><span class="nf">ToList</span><span class="p">();</span> <span class="kt">var</span> <span class="n">typeScriptFluent</span> <span class="p">=</span> <span class="n">TypeScript</span><span class="p">.</span><span class="nf">Definitions</span><span class="p">()</span> <span class="p">.</span><span class="nf">WithIndentation</span><span class="p">(</span><span class="s">" "</span><span class="p">)</span> <span class="p">.</span><span class="nf">WithModuleNameFormatter</span><span class="p">(</span><span class="n">tsModule</span> <span class="p">=&gt;</span> <span class="s">"Api"</span><span class="p">)</span> <span class="p">.</span><span class="n">WithConvertor</span><span class="p">&lt;</span><span class="n">DateTimeOffset</span><span class="p">&gt;(</span><span class="n">obj</span> <span class="p">=&gt;</span> <span class="s">"string"</span><span class="p">)</span> <span class="c1">// You may need to add more of these</span> <span class="p">.</span><span class="n">WithConvertor</span><span class="p">&lt;</span><span class="n">Guid</span><span class="p">&gt;(</span><span class="n">obj</span> <span class="p">=&gt;</span> <span class="s">"string"</span><span class="p">)</span> <span class="p">.</span><span class="nf">WithMemberFormatter</span><span class="p">(</span><span class="n">mf</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="c1">// Hack to ignore statics - limitation of TypeLite</span> <span class="k">if</span> <span class="p">((</span><span class="n">mf</span><span class="p">.</span><span class="n">MemberInfo</span> <span class="k">as</span> <span class="n">PropertyInfo</span><span class="p">)?.</span><span class="nf">GetGetMethod</span><span class="p">().</span><span class="n">IsStatic</span> <span class="p">??</span> <span class="k">false</span><span class="p">)</span> <span class="k">return</span> <span class="s">$"// Ignore static: </span><span class="p">{</span><span class="n">mf</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">"</span><span class="p">;</span> <span class="c1">// Hack to mark nullables as such- limitation of TypeLite</span> <span class="kt">var</span> <span class="n">suffix</span> <span class="p">=</span> <span class="p">((</span><span class="n">mf</span><span class="p">.</span><span class="n">MemberInfo</span> <span class="k">as</span> <span class="n">PropertyInfo</span><span class="p">)?.</span><span class="n">PropertyType</span><span class="p">.</span><span class="nf">IsNullable</span><span class="p">()</span> <span class="p">??</span> <span class="k">false</span><span class="p">)</span> <span class="p">?</span> <span class="s">"?"</span> <span class="p">:</span> <span class="s">""</span><span class="p">;</span> <span class="k">return</span> <span class="s">$"</span><span class="p">{</span><span class="kt">char</span><span class="p">.</span><span class="nf">ToLower</span><span class="p">(</span><span class="n">mf</span><span class="p">.</span><span class="n">Name</span><span class="p">[</span><span class="m">0</span><span class="p">])}{</span><span class="n">mf</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="nf">Substring</span><span class="p">(</span><span class="m">1</span><span class="p">)}{</span><span class="n">suffix</span><span class="p">}</span><span class="s">"</span><span class="p">;</span> <span class="p">})</span> <span class="p">.</span><span class="nf">AsConstEnums</span><span class="p">(</span><span class="k">false</span><span class="p">);</span> <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">type</span> <span class="k">in</span> <span class="n">apiTypes</span><span class="p">)</span> <span class="p">{</span> <span class="n">typeScriptFluent</span> <span class="p">=</span> <span class="n">typeScriptFluent</span><span class="p">.</span><span class="nf">For</span><span class="p">(</span><span class="n">type</span><span class="p">);</span> <span class="p">}</span> <span class="kt">var</span> <span class="n">tsModel</span> <span class="p">=</span> <span class="n">typeScriptFluent</span><span class="p">.</span><span class="n">ModelBuilder</span><span class="p">.</span><span class="nf">Build</span><span class="p">();</span> <span class="n">File</span><span class="p">.</span><span class="nf">WriteAllText</span><span class="p">(</span><span class="s">"api.d.ts"</span><span class="p">,</span> <span class="n">typeScriptFluent</span><span class="p">.</span><span class="nf">Generate</span><span class="p">());</span> <span class="p">}</span> <span class="k">private</span> <span class="k">static</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Type</span><span class="p">&gt;</span> <span class="nf">ParameterAndReturnTypes</span><span class="p">(</span><span class="n">MethodInfo</span> <span class="n">method</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">method</span><span class="p">.</span><span class="nf">GetParameters</span><span class="p">().</span><span class="nf">Select</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">ParameterType</span><span class="p">)</span> <span class="p">.</span><span class="nf">Concat</span><span class="p">(</span><span class="k">new</span><span class="p">[]</span> <span class="p">{</span> <span class="n">method</span><span class="p">.</span><span class="n">ReturnType</span> <span class="p">})</span> <span class="p">.</span><span class="nf">Distinct</span><span class="p">();</span> <span class="p">}</span> <span class="k">private</span> <span class="k">static</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Type</span><span class="p">&gt;</span> <span class="nf">Unwrap</span><span class="p">(</span><span class="n">Type</span> <span class="n">type</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">type</span><span class="p">.</span><span class="n">IsGenericType</span> <span class="p">?</span> <span class="nf">UnwrapGeneric</span><span class="p">(</span><span class="n">type</span><span class="p">)</span> <span class="p">:</span> <span class="n">type</span><span class="p">.</span><span class="n">IsArray</span> <span class="p">?</span> <span class="nf">UnwrapArray</span><span class="p">(</span><span class="n">type</span><span class="p">)</span> <span class="p">:</span> <span class="k">new</span><span class="p">[]</span> <span class="p">{</span> <span class="n">type</span> <span class="p">};</span> <span class="p">}</span> <span class="k">private</span> <span class="k">static</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Type</span><span class="p">&gt;</span> <span class="nf">UnwrapArray</span><span class="p">(</span><span class="n">Type</span> <span class="n">type</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">Unwrap</span><span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="nf">GetElementType</span><span class="p">());</span> <span class="p">}</span> <span class="k">private</span> <span class="k">static</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Type</span><span class="p">&gt;</span> <span class="nf">UnwrapGeneric</span><span class="p">(</span><span class="n">Type</span> <span class="n">type</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">type</span><span class="p">.</span><span class="n">GenericTypeArguments</span><span class="p">.</span><span class="nf">SelectMany</span><span class="p">(</span><span class="n">Unwrap</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>A few tidbits with this config:</p> <ul> <li>We do our own assembly scanning - TypeLite didn’t do it the way we wanted, so we scanned it ourselves</li> <li><code class="language-plaintext highlighter-rouge">System.</code> namespaces classes are ignored - you can whitelist this bit to your own assembly if that suits better</li> <li>Generic types and arrays need to be unwrapped recursively to ensure they get included explicitly</li> <li>Certain types need to be overridden because <code class="language-plaintext highlighter-rouge">Date</code> and <code class="language-plaintext highlighter-rouge">Guid</code> are not things in JSON land</li> <li>Order by fullname makes diffs nice!</li> <li>Spaces, not tabs.</li> </ul> <h3 id="aside-why-not-nswag">Aside: Why not NSwag?</h3> <p>This isn’t the only option - <a href="https://github.com/NSwag/NSwag">NSwag</a> can do a similar thing in a few lines of code:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">controllers</span> <span class="p">=</span> <span class="k">typeof</span><span class="p">(</span><span class="n">FooController</span><span class="p">).</span><span class="n">Assembly</span><span class="p">.</span><span class="nf">GetExportedTypes</span><span class="p">()</span> <span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="n">t</span><span class="p">.</span><span class="nf">IsSubclassOf</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">ApiController</span><span class="p">)));</span> <span class="kt">var</span> <span class="n">document</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">WebApiToSwaggerGenerator</span><span class="p">(</span><span class="k">new</span> <span class="nf">WebApiToSwaggerGeneratorSettings</span><span class="p">())</span> <span class="p">.</span><span class="nf">GenerateForControllers</span><span class="p">(</span><span class="n">controllers</span><span class="p">);</span> <span class="kt">var</span> <span class="n">code</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SwaggerToTypeScriptClientGenerator</span><span class="p">(</span><span class="n">document</span><span class="p">,</span> <span class="k">new</span> <span class="nf">SwaggerToTypeScriptClientGeneratorSettings</span><span class="p">())</span> <span class="p">.</span><span class="nf">GenerateFile</span><span class="p">();</span> <span class="n">File</span><span class="p">.</span><span class="nf">WriteAllText</span><span class="p">(</span><span class="s">"api.ts"</span><span class="p">,</span> <span class="n">code</span><span class="p">);</span> </code></pre></div></div> <p>Unfortunately it didn’t fill the bill for us for a few reasons:</p> <ul> <li>No customization of client side type names - specifically we wanted classes to be <code class="language-plaintext highlighter-rouge">PascalCase</code> and properties / parameters to be <code class="language-plaintext highlighter-rouge">camelCase</code>, which didn’t seem possible</li> <li>Generated rather verbose classes for contracts instead of simple interfaces that would not be in the output. <ul> <li>These classes were also very <code class="language-plaintext highlighter-rouge">any</code> friendly, which made it too easy to dodge</li> </ul> </li> <li>We were going to write our own abstraction over the HTTP services anyway, for more simply customizable loading bars and things</li> <li>No insight into original C# types. The <code class="language-plaintext highlighter-rouge">enum</code> support was pretty important to us</li> </ul> <h2 id="generating-enums-enumgenie">Generating Enums: EnumGenie</h2> <p>This worked fine for our model, and we got enum declarations as a result, but code like <code class="language-plaintext highlighter-rouge">let status = OrderStatus.InProgress</code> cannot be used - the generated file isn’t compiled to anything.</p> <p>So we wrote some code to generate all the useful enum bits. I’ve since made a library out of it, <a href="https://github.com/xwipeoutx/EnumGenie">EnumGenie</a>.</p> <p>A bit of code added to the above</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">new</span> <span class="n">EnumGenie</span><span class="p">.</span><span class="nf">EnumGenie</span><span class="p">()</span> <span class="p">.</span><span class="n">SourceFrom</span><span class="p">.</span><span class="nf">List</span><span class="p">(</span><span class="n">tsModel</span><span class="p">.</span><span class="n">Enums</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">e</span> <span class="p">=&gt;</span> <span class="n">e</span><span class="p">.</span><span class="n">Type</span><span class="p">))</span> <span class="p">.</span><span class="n">WriteTo</span><span class="p">.</span><span class="nf">File</span><span class="p">(</span><span class="s">"enums.ts"</span><span class="p">,</span> <span class="n">cfg</span> <span class="p">=&gt;</span> <span class="n">cfg</span><span class="p">.</span><span class="nf">TypeScript</span><span class="p">())</span> <span class="p">.</span><span class="nf">Write</span><span class="p">();</span> </code></pre></div></div> <p>and whamo! Suddenly we have a magical file letting us loop through enum values, grab descriptions and use the enums as we want!</p> <p>One cool little feature of TypeScript is that these are treated as equivalent, and can be assigned to each other:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Api.d.ts</span> <span class="nx">declare</span> <span class="k">export</span> <span class="kr">enum</span> <span class="nx">Status</span> <span class="p">{</span> <span class="nx">InProgress</span><span class="p">,</span> <span class="nx">Complete</span> <span class="p">}</span> <span class="c1">// Enums.ts</span> <span class="k">export</span> <span class="kr">enum</span> <span class="nx">Status</span> <span class="p">{</span> <span class="nx">InProgress</span><span class="p">,</span> <span class="nx">Complete</span> <span class="p">}</span> </code></pre></div></div> <h2 id="conclusion">Conclusion</h2> <p>There’s a fair chunk of code here - most of it reflecting over WebAPI and fiddling with formatting. The result is a tonne of generated goodies for use on the client.</p> <p>Stay tuned for the next post, where we’ll look at getting the operations themselves down to the client.</p> Tue, 20 Dec 2016 03:30:00 +0000 http://stevesspace.com/2016/12/put-your-server-types-on-client/ http://stevesspace.com/2016/12/put-your-server-types-on-client/ Chaining Expressions in C# <p>More and more I’ve been using projections to handle the <em>query</em> side of my applications, which of course includes a lot of <code class="language-plaintext highlighter-rouge">Expression</code> objects.</p> <p>The problem with <code class="language-plaintext highlighter-rouge">Expression</code> objects is they’re non-trivial to chain together and combine, because they’re data structures, not code.</p> <p>I recently had to implement a simple report filter that had optional date ranges on 4 different date fields - each with an optional <strong>From</strong> and <strong>To</strong> date. Of course, there were other requirements of this feature, too, which makes it a bit more interesting:</p> <ul> <li>Run the whole query in SQL - I don’t want to materialize my enumerable to perform the filtering</li> <li>Somewhat simple SQL query - of course I can be clever with <code class="language-plaintext highlighter-rouge">GroupBy</code> and <code class="language-plaintext highlighter-rouge">SelectMany</code>, but I’d prefer my SQL to just say <code class="language-plaintext highlighter-rouge">WHERE date &lt;= @p0</code> if possible.</li> <li>Clean code - DRY, reusable, terse <em>et. al.</em></li> </ul> <!--break--> <p>This is a faithful reproduction of my filter:</p> <div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">CarFilter</span> <span class="p">{</span> <span class="k">public</span> <span class="n">DateTime</span><span class="p">?</span> <span class="n">RegistrationDateFrom</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="n">DateTime</span><span class="p">?</span> <span class="n">RegistrationDateTo</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="n">DateTime</span><span class="p">?</span> <span class="n">PurchaseDateFrom</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="n">DateTime</span><span class="p">?</span> <span class="n">PurchaseDateTo</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>When I design tthings that I want to be highly readable, I generally write the calling code first:</p> <div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Car</span><span class="p">&gt;</span> <span class="nf">GetCars</span><span class="p">(</span><span class="n">CarFilter</span> <span class="n">carFilter</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">carFilter</span><span class="p">.</span><span class="nf">ApplyTo</span><span class="p">(</span><span class="n">_carQuery</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>See how clean that is? Ok ok, so that was cheating….</p> <div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">IQueryable</span><span class="p">&lt;</span><span class="n">Car</span><span class="p">&gt;</span> <span class="nf">ApplyTo</span><span class="p">(</span><span class="n">IQueryable</span><span class="p">&lt;</span><span class="n">Car</span><span class="p">&gt;</span> <span class="n">carQuery</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">carQuery</span> <span class="p">.</span><span class="nf">WhereDateBetween</span><span class="p">(</span><span class="n">car</span> <span class="p">=&gt;</span> <span class="n">car</span><span class="p">.</span><span class="n">RegistrationDate</span><span class="p">,</span> <span class="n">RegistrationDateFrom</span><span class="p">,</span> <span class="n">RegistrationDateTo</span><span class="p">)</span> <span class="p">.</span><span class="nf">WhereDateBetween</span><span class="p">(</span><span class="n">car</span> <span class="p">=&gt;</span> <span class="n">car</span><span class="p">.</span><span class="n">PurchaseDate</span><span class="p">,</span> <span class="n">PurchaseDateFrom</span><span class="p">,</span> <span class="n">PurchaseDateTo</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>I thought an extension method was better suited for the example here, but the real solution used a seperate Query object. Just so ya know.</p> <div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="n">IQueryable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">WhereDateBetween</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">IQueryable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">source</span><span class="p">,</span> <span class="n">Expression</span><span class="p">&lt;</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">DateTime</span><span class="p">&gt;&gt;</span> <span class="n">getDate</span><span class="p">,</span> <span class="n">DateTime</span><span class="p">?</span> <span class="n">fromDate</span> <span class="n">DateTime</span><span class="p">?</span> <span class="n">toDate</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="k">from</span> <span class="p">==</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="n">to</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="k">return</span> <span class="n">source</span><span class="p">;</span> <span class="c1">// The simplest query is no query</span> <span class="c1">// Uhhh...</span> <span class="p">}</span> </code></pre></div></div> <p>At this point I thought I’d pull out a predicate expression - that is <code class="language-plaintext highlighter-rouge">Expression&lt;Func&lt;DateTime, bool&gt;&gt;</code>.</p> <div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="n">Expression</span><span class="p">&lt;</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">DateTime</span><span class="p">,</span> <span class="kt">bool</span><span class="p">&gt;&gt;</span> <span class="nf">DateBetween</span><span class="p">(</span><span class="n">DateTime</span><span class="p">?</span> <span class="n">fromDate</span><span class="p">,</span> <span class="n">DateTime</span><span class="p">?</span> <span class="n">toDate</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">toDate</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="k">return</span> <span class="n">date</span> <span class="p">=&gt;</span> <span class="n">fromDate</span> <span class="p">&lt;=</span> <span class="n">date</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">fromDate</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="k">return</span> <span class="n">date</span> <span class="p">=&gt;</span> <span class="n">toDate</span> <span class="p">&gt;=</span> <span class="n">date</span><span class="p">;</span> <span class="k">return</span> <span class="n">date</span> <span class="p">=&gt;</span> <span class="n">fromDate</span> <span class="p">&lt;=</span> <span class="n">date</span> <span class="p">&amp;&amp;</span> <span class="n">toDate</span> <span class="p">&gt;=</span> <span class="n">date</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>I do love that <code class="language-plaintext highlighter-rouge">date =&gt; fromDate &lt;= date</code> is perfectly valid code.</p> <p>Essentially what we have now are 2 expressions: <code class="language-plaintext highlighter-rouge">GetDate(T) : DateTime</code> and <code class="language-plaintext highlighter-rouge">Predicate(Date) : bool</code>. What we <em>want</em> is an expression that represents <code class="language-plaintext highlighter-rouge">Predicate(GetDate(T)) : bool</code>. Thus our <code class="language-plaintext highlighter-rouge">WhereDateBetween</code> function will become:</p> <div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="n">IQueryable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">WhereDateBetween</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">IQueryable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">source</span><span class="p">,</span> <span class="n">Expression</span><span class="p">&lt;</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">DateTime</span><span class="p">&gt;&gt;</span> <span class="n">getDate</span><span class="p">,</span> <span class="n">DateTime</span><span class="p">?</span> <span class="n">fromDate</span> <span class="n">DateTime</span><span class="p">?</span> <span class="n">toDate</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">fromDate</span> <span class="p">==</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="n">toDate</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="k">return</span> <span class="n">source</span><span class="p">;</span> <span class="c1">// The simplest query is no query</span> <span class="kt">var</span> <span class="n">predicate</span> <span class="p">=</span> <span class="nf">DateBetween</span><span class="p">(</span><span class="n">fromDate</span><span class="p">,</span> <span class="n">toDate</span><span class="p">);</span> <span class="k">return</span> <span class="n">source</span><span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">getDate</span><span class="p">.</span><span class="nf">Chain</span><span class="p">(</span><span class="n">predicate</span><span class="p">));</span> <span class="p">}</span> </code></pre></div></div> <p>If you don’t want to understand expression trees, then implementing <code class="language-plaintext highlighter-rouge">Chain</code> can be done by <a href="http://stackoverflow.com/questions/7873448/create-dynamic-expression-lambda-from-two-others-chaining-the-expressions">copy/pasting from stack overflow</a> and attributing appropriately. Unfortunately the code is so straight forward it’s going to look like that’s what I did, but I swear I found this <em>afterwards</em> - my GoogleFu was weak in this instance.</p> <p>Before we move on, let’s get a bit more generic and mathematical. We have 2 expressions, <code class="language-plaintext highlighter-rouge">F(a)</code> and <code class="language-plaintext highlighter-rouge">G(b)</code>, and want one expression, <code class="language-plaintext highlighter-rouge">G(F(a))</code>. This can be done by replacing the parameter <code class="language-plaintext highlighter-rouge">b</code> of <code class="language-plaintext highlighter-rouge">G</code> with the body of <code class="language-plaintext highlighter-rouge">F</code>.</p> <p>The simplest way to replace parts of expression trees is to use the <code class="language-plaintext highlighter-rouge">ExpressionVisitor</code> base class that was introduced back in .Net 3.5. It just provides a bunch of methods that you can override to substitute parts of trees from others. In our case, we’re just need to swap one node with another.</p> <div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">class</span> <span class="nc">SwapVisitor</span> <span class="p">:</span> <span class="n">ExpressionVisitor</span> <span class="p">{</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">Expression</span> <span class="n">_source</span><span class="p">,</span> <span class="n">_replacement</span><span class="p">;</span> <span class="k">public</span> <span class="nf">SwapVisitor</span><span class="p">(</span><span class="n">Expression</span> <span class="n">source</span><span class="p">,</span> <span class="n">Expression</span> <span class="n">replacement</span><span class="p">)</span> <span class="p">{</span> <span class="n">_source</span> <span class="p">=</span> <span class="n">source</span><span class="p">;</span> <span class="n">_replacement</span> <span class="p">=</span> <span class="n">replacement</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="k">override</span> <span class="n">Expression</span> <span class="nf">Visit</span><span class="p">(</span><span class="n">Expression</span> <span class="n">node</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">node</span> <span class="p">==</span> <span class="n">_source</span> <span class="p">?</span> <span class="n">_replacement</span> <span class="p">:</span> <span class="k">base</span><span class="p">.</span><span class="nf">Visit</span><span class="p">(</span><span class="n">node</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>And then implement the <code class="language-plaintext highlighter-rouge">Chain</code> method</p> <div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="n">Expression</span><span class="p">&lt;</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">TIn</span><span class="p">,</span> <span class="n">TOut</span><span class="p">&gt;&gt;</span> <span class="n">Chain</span><span class="p">&lt;</span><span class="n">TIn</span><span class="p">,</span> <span class="n">TInterstitial</span><span class="p">,</span> <span class="n">TOut</span><span class="p">&gt;(</span> <span class="k">this</span> <span class="n">Expression</span><span class="p">&lt;</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">TIn</span><span class="p">,</span> <span class="n">TInterstitial</span><span class="p">&gt;&gt;</span> <span class="n">inner</span><span class="p">,</span> <span class="n">Expression</span><span class="p">&lt;</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">TInterstitial</span><span class="p">,</span> <span class="n">TOut</span><span class="p">&gt;&gt;</span> <span class="n">outer</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">visitor</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SwapVisitor</span><span class="p">(</span><span class="n">outer</span><span class="p">.</span><span class="n">Parameters</span><span class="p">[</span><span class="m">0</span><span class="p">],</span> <span class="n">inner</span><span class="p">.</span><span class="n">Body</span><span class="p">);</span> <span class="k">return</span> <span class="n">Expression</span><span class="p">.</span><span class="n">Lambda</span><span class="p">&lt;</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">TIn</span><span class="p">,</span> <span class="n">TOut</span><span class="p">&gt;&gt;(</span><span class="n">visitor</span><span class="p">.</span><span class="nf">Visit</span><span class="p">(</span><span class="n">outer</span><span class="p">.</span><span class="n">Body</span><span class="p">),</span> <span class="n">inner</span><span class="p">.</span><span class="n">Parameters</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>This creates a <code class="language-plaintext highlighter-rouge">SwapVisitor</code> that replaces the first parameter of <code class="language-plaintext highlighter-rouge">outer</code> (the <code class="language-plaintext highlighter-rouge">b</code> in <code class="language-plaintext highlighter-rouge">G(b)</code>) with the body of <code class="language-plaintext highlighter-rouge">inner</code> (the <code class="language-plaintext highlighter-rouge">F</code> in <code class="language-plaintext highlighter-rouge">F(a)</code>). It then creates the new lambda expression with the body <code class="language-plaintext highlighter-rouge">G∘F</code> (wiki: <a href="https://en.wikipedia.org/wiki/Function_composition">function composition</a>) and the parameter from <code class="language-plaintext highlighter-rouge">inner</code> (the <code class="language-plaintext highlighter-rouge">a</code> of <code class="language-plaintext highlighter-rouge">F(a)</code>).</p> <p>And we’re done! Very simple, type safe code (except for the expression tree twiddling) that produces nice, fast queries!</p> <p>I’ve uploaded a <a href="https://gist.github.com/xwipeoutx/962b205324017c000c75899a8b5016d9">gist of all the implementing files</a> if you want to see it all without distractions.</p> Mon, 06 Jun 2016 12:00:00 +0000 http://stevesspace.com/2016/06/chaining-expressions-in-c/ http://stevesspace.com/2016/06/chaining-expressions-in-c/ Comment Away <p>I’ve finally bitten the bullet and added <a href="https://disqus.com">Disqus</a> support to the site. I guess my idea of wanting to control 100% of my site’s content is a dream anyway.</p> <p>So go crazy with comments!</p> Wed, 02 Mar 2016 12:00:00 +0000 http://stevesspace.com/2016/03/comment-away/ http://stevesspace.com/2016/03/comment-away/ Cop this - a starter for EF6 and MVC5 <p><a href="https://github.com/xwipeoutx/CopThis">Just take me to the code</a></p> <p>As many of you know, I’ve recently started at <a href="https://ssw.com.au">SSW</a>, and of course that means I get to go File-&gt;New project much more frequently than when I was 100% product development.</p> <p>I have a few rules when it comes to project layout which should be par for the course</p> <ul> <li>All domain logic is in its own project</li> <li>All database logic is in its own project</li> <li>All UI logic is in its own project</li> </ul> <p>Unfortunately, people doing the scaffolding for MVC and EF6 seem to have a different set of priorities</p> <ul> <li>All views should be modelled straight off an entity</li> <li>All entities should contain all the validation required for the UI</li> </ul> <p>Of course, this works fine for CRUD cases, but the moment you need some business logic, you’re in the deep end with no handrail.</p> <!--break--> <p>Now, this is a project where the business logic is not so crazy that it warrants going full DDD - but it still deserves to have the domain easily tested and separated from the view, for maintainability.</p> <p>I went for a design that gave View Models, Command Handlers, Repositories and Entities, but was limited to Transactional Consistency. It also allowed fast read-models and flexible Unit of Work usage. I thought I’d share the layout here. It is in EF6 code-first on ASP.NET MVC5, and is available on <a href="https://github.com/xwipeoutx/CopThis">GitHub</a></p> <h1 id="1-domain">1. Domain</h1> <p>The sample domain is an application for police to record speeding tickets. They enter in the license plate, the speed limit and the vehicles speed. They may optionally enter the vehicle’s make and model.</p> <p>If a vehicle in the system already has the license plate, then the ticket is linked to the corresponding vehicle. Otherwise, a new vehicle is created and will eventually be synced with an external system.</p> <p>Further, if the vehicle was <em>not</em> in the system, and the make and model was provided, it is used as the initial values for the vehicle. Otherwise, it is ignored.</p> <p>At a later date, the officer may mark the speed ticket as paid.</p> <h2 id="entities">Entities</h2> <p>There are two entities here - a speed ticket and a vehicle.</p> <p>These will exist as POCOs in the domain project.</p> <figure class="highlight"><pre><code class="language-cs" data-lang="cs"><span class="k">public</span> <span class="k">class</span> <span class="nc">Ticket</span> <span class="p">{</span> <span class="k">public</span> <span class="kt">int</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="n">Vehicle</span> <span class="n">Vehicle</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">int</span> <span class="n">SpeedLimit</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">int</span> <span class="n">ActualSpeed</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">bool</span> <span class="n">IsPaid</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">Vehicle</span> <span class="p">{</span> <span class="k">public</span> <span class="kt">int</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">LicensePlate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Make</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Model</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span></code></pre></figure> <h2 id="commands">Commands</h2> <p>All commands are in the domain project, and are simply serialized method calls.</p> <figure class="highlight"><pre><code class="language-cs" data-lang="cs"><span class="k">public</span> <span class="k">class</span> <span class="nc">IssueTicketCommand</span> <span class="p">{</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">LicensePlate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">int</span> <span class="n">SpeedLimit</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">int</span> <span class="n">ActualSpeed</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Make</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Model</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">PayTicketCommand</span> <span class="p">{</span> <span class="k">public</span> <span class="kt">int</span> <span class="n">TicketId</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span></code></pre></figure> <h2 id="command-handlers">Command Handlers</h2> <p>Note this is <em>not</em> CQS. DB-generated ids are prevelant, and I thought it an ok tradeoff for the nicer URL that it offers over using guids. So commands that create entities will return the entity that was created.</p> <p>This also lives in the domain project.</p> <figure class="highlight"><pre><code class="language-cs" data-lang="cs"><span class="k">public</span> <span class="n">Ticket</span> <span class="nf">Handle</span><span class="p">(</span><span class="n">IssueTicketCommand</span> <span class="n">command</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">command</span><span class="p">.</span><span class="n">ActualSpeed</span> <span class="p">&lt;=</span> <span class="n">command</span><span class="p">.</span><span class="n">SpeedLimit</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">SpeedLimitNotExceededException</span><span class="p">();</span> <span class="kt">var</span> <span class="n">vehicle</span> <span class="p">=</span> <span class="n">_vehicleRepository</span><span class="p">.</span><span class="nf">Find</span><span class="p">(</span><span class="n">command</span><span class="p">.</span><span class="n">LicensePlate</span><span class="p">)</span> <span class="p">??</span> <span class="nf">CreateVehicle</span><span class="p">(</span><span class="n">command</span><span class="p">.</span><span class="n">LicensePlate</span><span class="p">,</span> <span class="n">command</span><span class="p">.</span><span class="n">Make</span><span class="p">,</span> <span class="n">command</span><span class="p">.</span><span class="n">Model</span><span class="p">);</span> <span class="kt">var</span> <span class="n">ticket</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Ticket</span> <span class="p">{</span> <span class="n">Vehicle</span> <span class="p">=</span> <span class="n">vehicle</span><span class="p">,</span> <span class="n">SpeedLimit</span> <span class="p">=</span> <span class="n">command</span><span class="p">.</span><span class="n">SpeedLimit</span><span class="p">,</span> <span class="n">ActualSpeed</span> <span class="p">=</span> <span class="n">command</span><span class="p">.</span><span class="n">ActualSpeed</span> <span class="p">};</span> <span class="n">_ticketRepository</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="n">ticket</span><span class="p">);</span> <span class="k">return</span> <span class="n">ticket</span><span class="p">;</span> <span class="p">}</span> <span class="k">private</span> <span class="n">Vehicle</span> <span class="nf">CreateVehicle</span><span class="p">(</span><span class="kt">string</span> <span class="n">licensePlate</span><span class="p">,</span> <span class="kt">string</span> <span class="n">make</span><span class="p">,</span> <span class="kt">string</span> <span class="n">model</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">vehicle</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Vehicle</span> <span class="p">{</span> <span class="n">LicensePlate</span> <span class="p">=</span> <span class="n">licensePlate</span><span class="p">,</span> <span class="n">Make</span> <span class="p">=</span> <span class="n">make</span><span class="p">,</span> <span class="n">Model</span> <span class="p">=</span> <span class="n">model</span> <span class="p">};</span> <span class="n">_vehicleRepository</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="n">vehicle</span><span class="p">);</span> <span class="k">return</span> <span class="n">vehicle</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">Handle</span><span class="p">(</span><span class="n">PayTicketCommand</span> <span class="n">command</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">ticket</span> <span class="p">=</span> <span class="n">_ticketRepository</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="n">command</span><span class="p">.</span><span class="n">TicketId</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">ticket</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">TicketDoesNotExistException</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="n">ticket</span><span class="p">.</span><span class="n">IsPaid</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">TicketIsAlreadyPaidException</span><span class="p">();</span> <span class="n">ticket</span><span class="p">.</span><span class="n">IsPaid</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span> <span class="n">_ticketRepository</span><span class="p">.</span><span class="nf">Save</span><span class="p">(</span><span class="n">ticket</span><span class="p">);</span> <span class="p">}</span></code></pre></figure> <p>These methods are trivially testable, and do not make any assumptions about persistence - in fact, they’re quite close to the usual DDD repository pattern, with a repository per aggregate root.</p> <p>However, I’ve been loose with transactions here - there are none in the command handlers, so it’s up to the application to set up the <code class="language-plaintext highlighter-rouge">TransactionScope</code> or <code class="language-plaintext highlighter-rouge">UnitOfWork</code> or whatever is cool.</p> <p>The sample application will use a per-request UnitOfWork that’s committed in the controller.</p> <p>Of course, in a perfect DDD world, the vehicle would likely be eventually consistent, and not related via a primary key, but I feel that’s complicating this simple project.</p> <h1 id="2-data-access">2. Data Access</h1> <p>I have purposely not extracted a generic EntityFrameworkRepository as base classes for these, for brevity.</p> <figure class="highlight"><pre><code class="language-cs" data-lang="cs"><span class="k">public</span> <span class="k">interface</span> <span class="nc">IUnitOfWork</span> <span class="p">{</span> <span class="k">void</span> <span class="nf">Commit</span><span class="p">();</span> <span class="p">}</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">CopContext</span> <span class="p">:</span> <span class="n">DbContext</span><span class="p">,</span> <span class="n">IUnitOfWork</span> <span class="p">{</span> <span class="k">public</span> <span class="nf">CopContext</span><span class="p">()</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="s">"CopContext"</span><span class="p">)</span> <span class="p">{</span> <span class="n">Configuration</span><span class="p">.</span><span class="n">LazyLoadingEnabled</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span> <span class="n">Configuration</span><span class="p">.</span><span class="n">AutoDetectChangesEnabled</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="n">DbSet</span><span class="p">&lt;</span><span class="n">Ticket</span><span class="p">&gt;</span> <span class="n">Tickets</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="n">DbSet</span><span class="p">&lt;</span><span class="n">Vehicle</span><span class="p">&gt;</span> <span class="n">Vehicles</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">Commit</span><span class="p">()</span> <span class="p">{</span> <span class="nf">SaveChanges</span><span class="p">();</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">EntityFrameworkTicketRepository</span> <span class="p">:</span> <span class="n">ITicketRepository</span> <span class="p">{</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">CopContext</span> <span class="n">_context</span><span class="p">;</span> <span class="k">public</span> <span class="nf">EntityFrameworkTicketRepository</span><span class="p">(</span><span class="n">CopContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span> <span class="n">_context</span> <span class="p">=</span> <span class="n">context</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="n">Ticket</span> <span class="nf">Get</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_context</span><span class="p">.</span><span class="n">Tickets</span><span class="p">.</span><span class="nf">AsNoTracking</span><span class="p">().</span><span class="nf">FirstOrDefault</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="n">t</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">id</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">Create</span><span class="p">(</span><span class="n">Ticket</span> <span class="n">ticket</span><span class="p">)</span> <span class="p">{</span> <span class="n">_context</span><span class="p">.</span><span class="n">Tickets</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">ticket</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">Save</span><span class="p">(</span><span class="n">Ticket</span> <span class="n">ticket</span><span class="p">)</span> <span class="p">{</span> <span class="n">_context</span><span class="p">.</span><span class="n">Tickets</span><span class="p">.</span><span class="nf">Attach</span><span class="p">(</span><span class="n">ticket</span><span class="p">);</span> <span class="n">_context</span><span class="p">.</span><span class="nf">Entry</span><span class="p">(</span><span class="n">ticket</span><span class="p">).</span><span class="n">State</span> <span class="p">=</span> <span class="n">EntityState</span><span class="p">.</span><span class="n">Modified</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">EntityFrameworkVehicleRepository</span> <span class="p">:</span> <span class="n">IVehicleRepository</span> <span class="p">{</span> <span class="c1">// ... etc.</span> <span class="k">public</span> <span class="n">Vehicle</span> <span class="nf">Find</span><span class="p">(</span><span class="kt">string</span> <span class="n">licensePlate</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_context</span><span class="p">.</span><span class="n">Vehicles</span><span class="p">.</span><span class="nf">AsNoTracking</span><span class="p">().</span><span class="nf">FirstOrDefault</span><span class="p">(</span> <span class="n">v</span> <span class="p">=&gt;</span> <span class="n">v</span><span class="p">.</span><span class="n">LicensePlate</span> <span class="p">==</span> <span class="n">licensePlate</span> <span class="p">);</span> <span class="p">}</span> <span class="c1">// ... etc.</span> <span class="p">}</span></code></pre></figure> <p>The design here is that the repositories don’t do persistence themselves, but rely on the contexts to be the same instance, and be commited externally via the unit of work implementation.</p> <p>To be honest, the unit of work implementation here is pretty close to pure laziness, but it works well enough for our use case.</p> <p><strong>Important</strong>: Search methods in this pattern should <em>never</em> return tracked entities Save methods need to ensure that they mark the model as modified after attaching, or it will never be saved.</p> <h1 id="3-ui">3. UI</h1> <p>The views are trivial given the right models, so here’s the controller</p> <figure class="highlight"><pre><code class="language-cs" data-lang="cs"><span class="c1">// View Model example</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">TicketRow</span> <span class="p">{</span> <span class="k">public</span> <span class="kt">int</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">int</span> <span class="n">SpeedExceededBy</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">LicensePlate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Make</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Model</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">Expression</span><span class="p">&lt;</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">Ticket</span><span class="p">,</span> <span class="n">TicketRow</span><span class="p">&gt;&gt;</span> <span class="n">Projection</span> <span class="p">=</span> <span class="n">ticket</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="n">TicketRow</span> <span class="p">{</span> <span class="n">Id</span> <span class="p">=</span> <span class="n">ticket</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="n">SpeedExceededBy</span> <span class="p">=</span> <span class="n">ticket</span><span class="p">.</span><span class="n">ActualSpeed</span> <span class="p">-</span> <span class="n">ticket</span><span class="p">.</span><span class="n">SpeedLimit</span><span class="p">,</span> <span class="n">LicensePlate</span> <span class="p">=</span> <span class="n">ticket</span><span class="p">.</span><span class="n">Vehicle</span><span class="p">.</span><span class="n">LicensePlate</span><span class="p">,</span> <span class="n">Make</span> <span class="p">=</span> <span class="n">ticket</span><span class="p">.</span><span class="n">Vehicle</span><span class="p">.</span><span class="n">Make</span><span class="p">,</span> <span class="n">Model</span> <span class="p">=</span> <span class="n">ticket</span><span class="p">.</span><span class="n">Vehicle</span><span class="p">.</span><span class="n">Model</span> <span class="p">};</span> <span class="p">}</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">TicketsController</span> <span class="p">{</span> <span class="k">public</span> <span class="nf">TicketsController</span><span class="p">(</span><span class="n">TicketCommandHandler</span> <span class="n">ticketCommandHandler</span><span class="p">,</span> <span class="n">IUnitOfWork</span> <span class="n">unitOfWork</span><span class="p">,</span> <span class="n">CopContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span> <span class="n">_ticketCommandHandler</span> <span class="p">=</span> <span class="n">ticketCommandHandler</span><span class="p">;</span> <span class="n">_unitOfWork</span> <span class="p">=</span> <span class="n">unitOfWork</span><span class="p">;</span> <span class="n">_context</span> <span class="p">=</span> <span class="n">context</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="n">ActionResult</span> <span class="nf">Index</span><span class="p">()</span> <span class="p">{</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">TicketRow</span><span class="p">&gt;</span> <span class="n">tickets</span> <span class="p">=</span> <span class="n">_context</span><span class="p">.</span><span class="n">Tickets</span><span class="p">.</span><span class="nf">Include</span><span class="p">(</span><span class="s">"Vehicle"</span><span class="p">)</span> <span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="p">!</span><span class="n">t</span><span class="p">.</span><span class="n">IsPaid</span><span class="p">)</span> <span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">TicketRow</span><span class="p">.</span><span class="n">Projection</span><span class="p">);</span> <span class="k">return</span> <span class="nf">View</span><span class="p">(</span><span class="n">tickets</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="n">ActionResult</span> <span class="nf">Create</span><span class="p">([</span><span class="n">Bind</span><span class="p">]</span><span class="n">IssueTicketCommand</span> <span class="n">issueTicket</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">ticket</span> <span class="p">=</span> <span class="n">_ticketCommandHandler</span><span class="p">.</span><span class="nf">Handle</span><span class="p">(</span><span class="n">issueTicket</span><span class="p">);</span> <span class="n">_unitOfWork</span><span class="p">.</span><span class="nf">Commit</span><span class="p">();</span> <span class="k">return</span> <span class="nf">RedirectToAction</span><span class="p">(</span><span class="s">"View"</span><span class="p">,</span> <span class="k">new</span> <span class="p">{</span><span class="n">id</span> <span class="p">=</span> <span class="n">ticket</span><span class="p">.</span><span class="n">Id</span><span class="p">});</span> <span class="p">}</span> <span class="k">public</span> <span class="n">ActionResult</span> <span class="nf">Pay</span><span class="p">([</span><span class="n">Bind</span><span class="p">]</span><span class="n">PayTicketCommand</span> <span class="n">payTicket</span><span class="p">)</span> <span class="p">{</span> <span class="n">_ticketCommandHandler</span><span class="p">.</span><span class="nf">Handle</span><span class="p">(</span><span class="n">payTicket</span><span class="p">);</span> <span class="n">_unitOfWork</span><span class="p">.</span><span class="nf">Commit</span><span class="p">();</span> <span class="k">return</span> <span class="nf">Redirect</span><span class="p">(</span><span class="s">"Index"</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span></code></pre></figure> <p>We’ve made a critical decoupling here that will save your views from polluting the domain - view models are simply projections from the db sets - with <code class="language-plaintext highlighter-rouge">.AsNoTracking()</code>. Of course, this could be abstracted again.</p> <p>This is pretty powerful - the projections will be done in the database query, so you’ll only be getting what you need for the view model, not everything from every entitiy. Filtering is also trivially done from expressions - so again, in the query itself.</p> <h1 id="4-setup-injection">4. Setup injection</h1> <p><a href="http://autofac.org">Autofac</a> will be our container of choice - specifically the <a href="http://docs.autofac.org/en/latest/integration/mvc.html#quick-start">Autofac MVC QuickStart</a>.</p> <p>Beyond this though, let’s look at the additional setups - command handlers, repositories, and our data layers all need to be set up</p> <figure class="highlight"><pre><code class="language-cs" data-lang="cs"><span class="k">public</span> <span class="k">class</span> <span class="nc">CopModule</span> <span class="p">:</span> <span class="n">Module</span> <span class="p">{</span> <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">Load</span><span class="p">(</span><span class="n">ContainerBuilder</span> <span class="n">builder</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">domainAssembly</span> <span class="p">=</span> <span class="k">typeof</span><span class="p">(</span><span class="n">Ticket</span><span class="p">).</span><span class="n">Assembly</span><span class="p">;</span> <span class="kt">var</span> <span class="n">dataAssembly</span> <span class="p">=</span> <span class="k">typeof</span><span class="p">(</span><span class="n">CopContext</span><span class="p">).</span><span class="n">Assembly</span><span class="p">;</span> <span class="c1">// Register command handlers</span> <span class="n">builder</span><span class="p">.</span><span class="nf">RegisterAssemblyTypes</span><span class="p">(</span><span class="n">domainAssembly</span><span class="p">)</span> <span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="n">t</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="nf">EndsWith</span><span class="p">(</span><span class="s">"CommandHandler"</span><span class="p">))</span> <span class="p">.</span><span class="nf">AsSelf</span><span class="p">().</span><span class="nf">AsImplementedInterfaces</span><span class="p">();</span> <span class="c1">// Register repositories</span> <span class="n">builder</span><span class="p">.</span><span class="nf">RegisterAssemblyTypes</span><span class="p">(</span><span class="n">dataAssembly</span><span class="p">)</span> <span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="n">t</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="nf">EndsWith</span><span class="p">(</span><span class="s">"Repository"</span><span class="p">))</span> <span class="p">.</span><span class="nf">AsImplementedInterfaces</span><span class="p">();</span> <span class="c1">// Register database stuff</span> <span class="n">builder</span><span class="p">.</span><span class="n">RegisterType</span><span class="p">&lt;</span><span class="n">CopContext</span><span class="p">&gt;()</span> <span class="p">.</span><span class="nf">InstancePerRequest</span><span class="p">()</span> <span class="p">.</span><span class="nf">AsSelf</span><span class="p">().</span><span class="n">As</span><span class="p">&lt;</span><span class="n">IUnitOfWork</span><span class="p">&gt;();</span> <span class="p">}</span> <span class="p">}</span></code></pre></figure> <p>Everything except the context and unit of work are transient dependencies by default, and we’ve made our per request.</p> <h1 id="5-win">5. Win!</h1> <p>That’s it! Run your project and be happy that you’ve isolated your domain, used entity framework and MVC, got dependency injection and everything is super squeaky clean.</p> <p>Be sure to check out the <a href="https://github.com/xwipeoutx/CopThis">sample project on GitHub</a>!</p> <p>Please if you have any suggestions for improvement - especially a more reliable (but still simple) transactional consistency, let me know on the twitters!</p> Tue, 23 Feb 2016 12:00:00 +0000 http://stevesspace.com/2016/02/cop-this-ef6-mvc5-starter/ http://stevesspace.com/2016/02/cop-this-ef6-mvc5-starter/ Making WebStorm Friendlier <p>There’s a few settings I recently tweaked in <a href="http://www.jetbrains.com/webstorm/">WebStorm</a> that vastly improved my development experience.<br /> Here’s a quick rundown.</p> <!--break--> <h2 id="ensure-show-reformat-code-is-disabled">Ensure “Show Reformat Code” is disabled</h2> <p><img src="/images/2014-06-04-reformat-code.png" alt="Show &quot;Reformat Code&quot; dialog" /></p> <p>Make sure these guys are off to get the popups out of your way. I also remap the commands to <code class="language-plaintext highlighter-rouge">Ctrl+K, D</code> and <code class="language-plaintext highlighter-rouge">Ctrl+R, S</code> to match my Visual Studio setup.</p> <h2 id="tweak-code-completion-popups">Tweak code completion popups</h2> <p>This is the setup I use:</p> <p><img src="/images/2014-06-04-code-completion.png" alt="Code Completion" /></p> <p>The most important one is the Parameter Info popup - I really like having the method signatures there all the time, so making this nice and small makes it much more useful. I’d recommend trying “<code class="language-plaintext highlighter-rouge">Insert selected variant by typing dot, space, etc</code>” as well, for more natural completions. Can be annoying, though</p> <h2 id="key-bindings">Key Bindings</h2> <p>The default key bindings are a little all over the shop. Here’s some handy ones:</p> <ul> <li><code class="language-plaintext highlighter-rouge">Ctrl+T</code>: Navigate to symbol</li> <li><code class="language-plaintext highlighter-rouge">Ctrl+Shift+T</code>: Navigate to file</li> <li><code class="language-plaintext highlighter-rouge">F2</code> and <code class="language-plaintext highlighter-rouge">Ctrl+R, R</code>: Rename (works for files and symbols)</li> <li><code class="language-plaintext highlighter-rouge">Alt+Enter</code>: Autocomplete</li> <li><code class="language-plaintext highlighter-rouge">Alt+Home</code>: Super types hierarchy</li> <li><code class="language-plaintext highlighter-rouge">Alt+End</code>: Sub types hierarchy</li> <li><code class="language-plaintext highlighter-rouge">Ctrl+R, S</code>: Optimise Imports</li> <li><code class="language-plaintext highlighter-rouge">Ctrl+K, D</code>: Reformat Document</li> </ul> <p>Some of these are muscle memory from my Visual Studio settings at work, but be sure to make them something you use, because they are all VERY useful commands.</p> <h2 id="plugins">Plugins</h2> <p>The best plugin I’ve seen is <a href="http://plugins.jetbrains.com/plugin/4455?pr=webStorm">Key Promoter</a>. This handy extension shows a popup whenever you do something inefficiently. For example, if I go to Settings via the menu, instead of <code class="language-plaintext highlighter-rouge">Alt+F7</code>, it’ll show a blocker for 1s that shows the shortcut in an impossible to miss way. Recently, it noticed that I use “Close all documents” a lot, and nagged me to make a shortcut for it. I can now use <code class="language-plaintext highlighter-rouge">Ctrl+Shift+W</code> for that purpose, and it will nag me if I don’t.</p> Wed, 04 Jun 2014 12:00:00 +0000 http://stevesspace.com/2014/06/making-webstorm-friendlier/ http://stevesspace.com/2014/06/making-webstorm-friendlier/ Foray into node.js on Windows 7 <p>I’ve decided to learn about <a href="http://nodejs.org/">node.js</a> - it’s about time, really. I’ve used it - but only so much as to run a grunt task to bundle a specific revision of an Angular directive.</p> <p>I found <a href="http://cwbuecheler.com/web/tutorials/2013/node-express-mongo/">this neat tutorial</a> for picking up some node.js know-how - looks pretty decent, and I’ll give feedback on that in a subsequent post. Meanwhile, here’s a rundown of getting the basic environment up for those on Windows who are having issues (as I did)</p> <!--break--> <h2 id="installing-nodejs-with-npm">Installing NodeJS with NPM</h2> <p>I must have had this idea a little while ago, because I thought my first step should be to find out what I’ve got installed so far - it turns out I have 2 versions installed from <a href="https://chocolatey.org/">Chocolatey</a>, and one from the official installer, all old versions. So I got rid of them all, and discovered from the helpful comments at the <a href="https://chocolatey.org/packages/nodejs">package page for nodejs</a> that I should use <a href="https://chocolatey.org/packages/nodejs.install">nodejs.install</a> instead.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>c:\Development\node-learning&gt;node -v v0.10.28 c:\Development\node-learning&gt;npm -v 1.4.9 </code></pre></div></div> <p>Woohoo!</p> <h2 id="npm-issues">NPM issues</h2> <p>Installing <a href="http://expressjs.com/">express</a> proved difficult - the mime package kept giving me permission errors and this:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm ERR! Error: ENOENT, chmod 'C:\Users\*****\AppData\Roaming\npm\node_modules\express\node_modules\accepts\node_modules\mime\README.md' </code></pre></div></div> <p>Whatever that means.</p> <p>I kept going around in circles - the prevailing wisdom seems to be to <a href="http://alicoding.com/how-to-fix-error-enoent-lstat-npm-when-trying-to-install-modules/">clean the npm cache</a> or to <a href="http://stackoverflow.com/questions/15272542/socket-io-installation-fails-on-windows-7-32-bit/23569456#23569456">run as administrator</a>.</p> <p>I tried these (alongside an <code class="language-plaintext highlighter-rouge">npm uninst -g</code> for everything to get a clean start), and it worked a treat. I then removed all the modules again, cleaned my cache, and tried again, to make sure my solution was valid.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm ERR! EEXIST, mkdir 'C:\Users\*****\AppData\Roaming\npm\node_modules\express\node_modules\send\node_modules\mime' File exists: C:\Users\*****\AppData\Roaming\npm\node_modules\express\node_modules\send\node_modules\mime Move it away, and try again. </code></pre></div></div> <p>It wasn’t. I’ve made sure <code class="language-plaintext highlighter-rouge">%appdata%/npm-cache</code> is empty as well, to no avail. I sometimes get a few other errors, which seem to cycle around - even though the cache and modules are empty</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm ERR! Error: ENOENT, chmod 'C:\Users\*****\AppData\Roaming\npm\node_modules\express\node_modules\type-is\node_modules\mime\LICENSE' npm ERR! Error: ENOENT, lstat 'C:\Users\*****\AppData\Roaming\npm\node_modules\express\node_modules\send\node_modules\mime\types\mime.types' </code></pre></div></div> <p>I’m sure I got an <code class="language-plaintext highlighter-rouge">EPERM</code> here as well.</p> <p>I’ve since learned that <code class="language-plaintext highlighter-rouge">ENOENT</code> means “No such file or directory”. I’m well into yak-shaving territory at this point.</p> <h2 id="the-light">The light!</h2> <p>In the end, I just kept running <code class="language-plaintext highlighter-rouge">npm install express -g</code> until it worked. It’s intermittent, which makes me think this stuff installs in parallel or there’s a load balancer serving the wrong file or something. Not much of a light, but them’s the breaks.</p> <p>Running <code class="language-plaintext highlighter-rouge">npm install</code> for the package had similar issues. As before the “keep on trying” Method works, and works in fewer iterations if you install it package-at-a-time. I couldn’t find any documentation about parallel installs, so I’ve no idea what’s going on.</p> <p>Be sure to let me know if you know what’s going on here!</p> <p>In any case, it was easy sailing from here on out. The tutorial was straight forward, though it reminded me of my PHP days (jam the DB object into the request object? <em>Really</em>?)</p> Mon, 19 May 2014 14:00:00 +0000 http://stevesspace.com/2014/05/foray-into-nodejs-on-windows-7/ http://stevesspace.com/2014/05/foray-into-nodejs-on-windows-7/ Undirect updated to 1.1.1, and some "gotchas" <p>I’ve updated <a href="https://chrome.google.com/webstore/detail/dohbiijnjeiejifbgfdhfknogknkglio">Undirect</a>, a Chrome plugin that prevents annoying redirects on the google search page, because it had stopped working. I ran into a few surprises along the way, and some old frustrations cropped up again, here’s a rundown to help the next person who tries.</p> <!--break--> <h2 id="chrome-store-feedback">Chrome store feedback</h2> <p>Since I’d released this in 2011, with a small update in 2012 to add RegEx to the pattern, I’d basically stopped checking the store’s feedback. I couldn’t find a way to get notifications of comments (apparently <a href="https://productforums.google.com/forum/#!topic/chrome/JidW4GlebXE">I’m not the only one</a>) - and checking every few months is something that I probably could have done, but it never crossed my mind.</p> <p>Now I wish I had, because apparently the plugin hasn’t been working since about August 2013 - and I hadn’t even noticed. Which is especially concerning, because I use the plugin on all my machines, and so do a lot of friends of mine. Being able to get notifications of reviews would have served me well here, since the average Joe won’t exactly raise an issue on GitHub</p> <h2 id="manifest-json">Manifest JSON</h2> <p>The <code class="language-plaintext highlighter-rouge">manifest.json</code> file has been updated to v2, and v1 can no longer be updated on the Web Store. Not a big deal, but there were a few new things about the configuration file which were non-obvious.</p> <ul> <li><code class="language-plaintext highlighter-rouge">default_locale</code> is noted as “recommended” field, but without also having a <code class="language-plaintext highlighter-rouge">_locales</code> directory with the localisation, you get a confusing error when trying to load it.</li> <li>The <a href="https://developer.chrome.com/extensions/manifest#overview">documentation page</a> for it looks good for a sample, but it took me a while to realise that the field names are clickable. If you’re looking for documentation, click those links!</li> <li>The sample uses comments to mark off sections - but keep in mind this isn’t valid JSON! You’ll need to take them out before uploading to the store</li> </ul> <h2 id="isolated-world---why-it-broke">Isolated world - why it broke</h2> <p>The reason for the extension breaking was a “security” feature they’ve added to the extension. I put security in quotes, because it’s easy to circumvent.</p> <p>Content scripts now run in an <a href="https://developer.chrome.com/extensions/content_scripts#execution-environment">isolated world</a> - which means that while it has access to the page and the DOM and all the jazz, it’s a different view than the page. Any scripts or variables that are added by the page are inaccessible from the script - in my case, the <code class="language-plaintext highlighter-rouge">onmousedown</code> attribute of the link tags didn’t exist from the content script’s perspective.</p> <p>I thought all hope was lost, then came across a <a href="http://stackoverflow.com/a/9777536/185422">neat little workaround</a>, which is basically to add a <code class="language-plaintext highlighter-rouge">&lt;script&gt;</code> tag to the head element, causing it to execute in page context. Circumvented!</p> <p>In my case, I wrote the function I wanted to execute as usually, then added the script to the page by simple calling <code class="language-plaintext highlighter-rouge">.toString()</code> on the function. Simple, and I get the full power of my IDE while I’m at it! Here’s an example</p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">fnToRunOnPage</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// code goes here</span> <span class="p">};</span> <span class="kd">var</span> <span class="nx">fnContents</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">(</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">fnToRunOnPage</span><span class="p">.</span><span class="nx">toString</span><span class="p">()</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">)();</span><span class="dl">'</span><span class="p">;</span> <span class="kd">var</span> <span class="nx">scriptTag</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">script</span><span class="dl">'</span><span class="p">);</span> <span class="nx">scriptTag</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">executeFnScript</span><span class="p">;</span> <span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">head</span> <span class="o">||</span> <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">).</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">scriptTag</span><span class="p">);</span> <span class="nx">scriptTag</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">removeChild</span><span class="p">(</span><span class="nx">scriptTag</span><span class="p">);</span></code></pre></figure> <p>It’s the same thing that jQuery does when you add an element with a script tag in it. Works a treat!</p> <h2 id="the-new-solution">The new solution</h2> <p><a href="https://github.com/xwipeoutx/undirect/blob/d6b613afa1201fa4fbd378c017cdc73dbfc74494/undirect.js">Previously</a>, my plugin would search all the anchor tags, look for ones that had an <code class="language-plaintext highlighter-rouge">onmousedown</code> script with <code class="language-plaintext highlighter-rouge">return rwt</code> in it, and remove the value. Every time a DOM element was added or removed, I would run it again.</p> <p><a href="https://github.com/xwipeoutx/undirect/blob/544aefe555181fa823804a25fa30f6e0dfa2515e/undirect.js">That’s updated now</a> to simply overwrite the <code class="language-plaintext highlighter-rouge">rwt</code> function on <code class="language-plaintext highlighter-rouge">window</code> to one that does nothing. I (perhaps unnecessarily) future-proofed it against overwrites by making it a property with <code class="language-plaintext highlighter-rouge">writeable: false</code> as well. Futile, probably - I’m sure they could get around that simple enough, but I have nothing to lose by doing that.</p> Sat, 17 May 2014 14:00:00 +0000 http://stevesspace.com/2014/05/undirect-updated-some-gotchas/ http://stevesspace.com/2014/05/undirect-updated-some-gotchas/ Static Jekyll Comments <p>Those of you paying very close attention will have noticed that posts now have a comment section at the bottom. It’s currently reading the comments reading in from specially named YAML files in the _data directory of the site, which I have to put in manually.</p> <!--break--> <p>You can find my comment <a href="https://github.com/xwipeoutx/xwipeoutx.github.io/blob/master/_includes/comments.html">HTML</a> and <a href="https://github.com/xwipeoutx/xwipeoutx.github.io/blob/master/_data">YAML</a> on GitHub, hopefully it’s pretty straight self-explanatory.</p> <p>To do it, I had to get a filename-friendly page identifier from the, which I did by replacing slashes with dashes and capturing it in a comment id.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{% capture commentid %}comments{{ page.id | replace: '/','-'}}{% endcapture %} </code></pre></div></div> <p>From here, I read the data from <code class="language-plaintext highlighter-rouge">site.data[commentid]</code> and display it on the page. I wanted to allow some formatting in comments, so I pass the content through the <code class="language-plaintext highlighter-rouge">| markdownify</code> filter. The comment contents in the YAML have to be multi line as well, ending up with a YAML file with a bunch of entries like so:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- author: Steve date: 2014-04-02 10:00 +1000 contents: | I've added comment support to the site now! See [my post](/2014/04/static-jekyll-comments) to see how I went about it. You'll note that it has *markdown* support too! I'll moderating it though, so don't try any dirty hacks... </code></pre></div></div> <p>The plan now is to make use of the GitHub API to allow comment submission by creating issues. I think this means that the commenter requires GitHub account, but that’s ok. I don’t think I’ll get enough comments to need more automation than that - I will want to moderate them to ensure there’s nothing malicious in the comment anyway, so I don’t mind having to copy-paste the markdown from the GitHub issue into a YAML file.</p> <p>For now, you’ll have to create the GitHub issue yourself!</p> Wed, 02 Apr 2014 10:45:00 +0000 http://stevesspace.com/2014/04/static-jekyll-comments/ http://stevesspace.com/2014/04/static-jekyll-comments/ Jekyll Me This <p>So, I’ve moved the blog over to <a href="http://jekyllrb.com/">Jekyll</a>, a static site generator that’s pretty well suited to doing blogs, if you’re a tech person, like myself. No, this is not an April fool’s joke!</p> <!--break--> <p>Moving over was a little arduous, not least of all because all the Jekyll tooling (including Ruby) really targets Linux or Mac users more than Windows users.</p> <p>So, if you’re thinking of doing the same, here’s a few tips:</p> <ol> <li>Use the guide by juthilo on GitHub on how to <a href="https://github.com/juthilo/run-jekyll-on-windows/">run Jekyll on Windows</a></li> <li>Pay attention to the version numbers in the article above, as they are written for a reason. When I set mine up, it was Jekyll v1.4.2, but it looks like he’s now recommending 1.5.1.</li> <li>Consider using <a href="http://google-code-prettify.googlecode.com/svn/trunk/README.html">Javascript code prettifier</a> instead of Pygments if you don’t want to install Python.</li> <li>GitHub pages does NOT support most plugins, only the <a href="https://github.com/jekyll/jekyll/issues/325">ones found in the master repository</a>. So either avoid using them, or generate the HTML locally and commit that</li> <li>While there are a tonne of themes at <a href="http://jekyllthemes.org/">Jekyll Themes</a>, it’s very hard to find a good one - with no search or rating system, it’s really a list of the latest submitted ones. A lot of them have hardcoded site names and categories. Avoid the hassle and style it yourself.</li> </ol> <p>I have <a href="https://github.com/xwipeoutx/xwipeoutx.github.io">a GitHub repository</a> for this blog if you want a fairly simple setup or template to base it off. I’m not using <a href="http://jekyllbootstrap.com/">Jekyll Bootstrap</a>, so it shows how easy it is to make a very functional blog with vanilla Jekyll.</p> <p>I still don’t know what I want to do with comments, I may simply avoid using them, as I’d have to hand them over to a 3rd party (like <a href="https://disqus.com/">Disqus</a>), something I’d rather not do.</p> <p>If you have any helpful comments or find any problems, let me know by <a href="https://github.com/xwipeoutx/xwipeoutx.github.io/issues/new">raising an issue</a> or submitting a pull request.</p> Tue, 01 Apr 2014 10:30:00 +0000 http://stevesspace.com/2014/04/jekyll-me-this/ http://stevesspace.com/2014/04/jekyll-me-this/ "Mejh" Dev Log - Part 3 <p>I decided it was time to start getting the Mejh to look around and guess their positions. That got messy pretty fast - I want to be able to swap out the "find the closest stuff to you" part very quickly, which meant creating a World class for the Mejh to live in, and a "Finder" interface, which has a "findNearbyMejh" method. If I need to optimise to a quad tree or something, it should be rather simple, success!</p> <!--break--> <p>Unfortunately, this is where the success starts to run out. Because the next feature I wanted was to be able to hover over a Mejh, and have nearby Mejh glow. As much as I trust my tests, I do like to see this progress on the screen, and this is a stepping stone to having an interactive sort of game. So I go in and start writing tests for the renderer to have interactivity.</p> <p>When a circle is clicked it appears selected</p> <p>And I've already hit a wall, which is d3.js. I either haven't found out how to test it easily yet, or it's a difficult API to write tests for. Or both. See, d3.js works by selecting a bunch of elements, binding them to data, and specifying behaviour for the nodes, with special cases for entering nodes and exiting nodes. So the rendering code of last week was:</p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="nx">Renderer</span><span class="p">.</span><span class="nx">prototype</span> <span class="o">=</span> <span class="p">{</span> <span class="na">render</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">allMejh</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_world</span><span class="p">.</span><span class="nx">allMejh</span><span class="p">();</span> <span class="kd">var</span> <span class="nx">selection</span> <span class="o">=</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">select</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_canvas</span><span class="p">)</span> <span class="p">.</span><span class="nx">selectAll</span><span class="p">(</span><span class="dl">'</span><span class="s1">circle</span><span class="dl">'</span><span class="p">)</span> <span class="p">.</span><span class="nx">data</span><span class="p">(</span><span class="nx">allMejh</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">m</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">m</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span> <span class="p">});</span> <span class="nx">selection</span><span class="p">.</span><span class="nx">enter</span><span class="p">().</span><span class="nx">append</span><span class="p">(</span><span class="dl">'</span><span class="s1">circle</span><span class="dl">'</span><span class="p">);</span> <span class="nx">selection</span><span class="p">.</span><span class="nx">exit</span><span class="p">().</span><span class="nx">remove</span><span class="p">();</span> <span class="k">this</span><span class="p">.</span><span class="nx">_formatter</span><span class="p">.</span><span class="nx">format</span><span class="p">(</span><span class="nx">selection</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="nx">Formatter</span><span class="p">.</span><span class="nx">prototype</span> <span class="o">=</span> <span class="p">{</span> <span class="na">format</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">selection</span><span class="p">)</span> <span class="p">{</span> <span class="nx">selection</span> <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="dl">'</span><span class="s1">r</span><span class="dl">'</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span> <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="dl">'</span><span class="s1">cx</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">m</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">m</span><span class="p">.</span><span class="nx">position</span><span class="p">().</span><span class="nx">x</span><span class="p">;</span> <span class="p">})</span> <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="dl">'</span><span class="s1">cy</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">m</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">m</span><span class="p">.</span><span class="nx">position</span><span class="p">().</span><span class="nx">y</span><span class="p">;</span> <span class="p">});</span> <span class="p">}</span> <span class="p">};</span></code></pre></figure> <p>Pretty simple code. Pain in the butt to test though, because you have to consider the case where a node is entering, where one is exiting, and that each one is formatting. While the implementation is about 15 lines of code, the tests take up 55 or so. Of course, lines of code aren't a good way of measuring quality or anything, but it just felt *wrong*.</p> <p>Updating for hover code was simple, but writing tests for it first proved even more difficult, as the mouse events are bound to the d3 selection, and it's all very d3-selectiony, not DOM elementy, and gets confusing fast.</p> <p>So I thought to myself, let's try out something that's NOT d3.js. I hit up <a href="http://angularjs.org/" title="AngularJS">http://angularjs.org/</a></p> <h2>Angular JS</h2> <p>In brief AngularJS provides a way of specifying views using markup, and having it bind to a model (ish), with a controller floating around there to give it all some behaviour. It's fairly impressive I have to admit. <a href="http://www.egghead.io/">egghead</a> has some fantastic video tutorials on it.</p> <p>So I watched a few videos, did some dodgy demo apps with ridiculously simple models and repeat sections and thought "yes, let's go!". I had trouble doing it in SVG, so I just used divs with text displaying the model position - I figured I could switch out the rendering easy enough. And that WAS easy enough - it just worked. Well, as long as I made sure my application attached the world onto some global scope because angular didn't play nice with InjectJS. I figured I could work on DI integration later, global scope was fine while researching this stuff. And it's just the world, not a big deal, right? </p> <p>Next was the hover behaviour, and I realised I also had to expose my finder - since what I wanted to do is find the Mejh that are close to the clicked node, and add them to a list of 'nearby' Mejh (or mark them as nearby some other way). That's 2 in the global scope, but it worked well enough. I didn't attempt testing just now - still just researching and seeing if it's worth pursuing)</p> <p>Time to work on SVG support. I wire up the same repeat infrastructure, but with circles instead of divs, and run it. Woo! There they are, a circle for each Mejh. Just for posterity, I put in 2 more repeats - one for the highlighted one, another for the 'nearby' ones. Bam, they show, the click events work, too easy!</p> <p>But, I don't want 3 repeats. I want 1 list of Mejh, and the format of it dependant on whether it's selected, nearby, or neither. Which means a directive - &lt;mejh-circle&gt;. Test it in HTML, all good, do it in SVG. Nothing. I inspect the DOM using Chrome's dev tools, all the circle elements are there with perfect markup, why isn't it showing?</p> <p>Fast forward half an hour or so, turns out my directive makes HtmlUnknownElement instead of SvgCircleElement. Because that's what happens when you use strings to build SVG - something that Angular wasn't really designed for (understandably). So I have to throw Angular out for this project, too painful.</p> <p>Ahh well, live and learn. I think what I'll do is make a view model for the world, test the view model, and not test the bit that actually makes DOM elements out of the view model. Stay tuned!</p> Tue, 21 May 2013 00:00:00 +0000 http://stevesspace.com/2013/05/mejh-dev-log-part-3/ http://stevesspace.com/2013/05/mejh-dev-log-part-3/ "Mejh" Dev Log - Part 2 <p>I've done my first chunk of code, and TDD has blown me away yet again.</p> <p>So I created a class for my Mejh dudes, a "placer" to just randomly put them all over the bounding area for now, a renderer to create SVG elements for each of them, and a formatter to give the SVG elements what they need (radius, position, etc.). And an application to tie it all together.</p> <p>Once the application was finished, and I had 100% code coverage and DI everywhere, I created an index.html, set up the DI container how I wanted it, and it worked (well, not quite - I forgot to set cx and cy attributes of the circles, so they all appeared at 0,0). What's more, the container setup only required 3 manual registrations - everything else was automatic.</p> <p>It may not seem like much - a handful of classes which, when put together, do nothing more than display a bunch of random dots on the screen - but having any app "Just Work" feels damn good.</p> <!--break--> <p>Here's the libraries I'm using to do the bits:</p> <ul> <li><a href="https://github.com/soxtoby/InjectJs">InjectJS</a>: DI Container for Javascript. Written just last week by a coworker, it's brilliant. Highly recommended. <li><a href="http://d3js.org/">d3js</a>: Library to bind data to DOM elements. Chose this one because I knew it, and it works well. <li><a href="https://github.com/xwipeoutx/basil">Basil</a>, <a href="http://chaijs.com/">ChaiJS</a>, <a href="http://sinonjs.org/">SinonJS</a>: Trinity of Javascript testing, I talk about these enough so I'll leave it at that</li> </ul> <p>And to finish it off, some things I've learnt</p> <h2>Dependencies</h2> <p>When everything is tested, the list of scripts in test.html match exactly to the list in index.html. The only difference in the real version is the instantiation and starting of the Application. Maybe I should be testing my container setup/initial resolve?</p> <h2>Testing the DOM is very fiddly</h2> <p>I'm using d3 to render the svg circles, and so all my rendering tests have to be integration tests - I can't feasibly mock that thing. It makes it quite painful to test, especially since pulling attributes out of Svg elements return animated strings, so the easiest way to assert on them is with d3.select(el).attr("r").should.equal("3"). Of course, .attr always returns strings, not numbers... I'm glad I won't have much render code!</p> <h2>Slowdowns</h2> <p>My environment is slowing me down a lot. Manually making class methods instead of just pressing alt+enter takes time. Not having the tests Autorun takes time (I really need to update that chrome extension...). Not being terribly familiar with WebStorm's keyboard shortcuts takes time. Heck, even not knowing d3/sinon/chai/injectjs off the top of my head slows me down plenty (not to mention the bits and bobs of missing functionality, like array matchers in chai - .deep isn't the answer)! Hopefully I'll get faster, and nut a lot of this down, but right now, it's annoying.</p> <p>So what's next? I think I'll get the Mejh guessing their positions now. This will mean adding neighbour discovery, and what I think will be a visitor pattern for the Mejh to add different guessing behaviours, not certain yet. Going mejh.Visit(this._positionGuesser) just seems sensible, and sets me up for adding movement later.</p> <p>I'll also spend some time on configuring the IDE to do more stuff for me.</p> <p>Until next time!</p> Thu, 11 Apr 2013 00:00:00 +0000 http://stevesspace.com/2013/04/mejh-dev-log-part-2/ http://stevesspace.com/2013/04/mejh-dev-log-part-2/ "Mejh" Dev Log - Part 1 <p>I was talking with a coworker the other day, and we lamenting about how bad GPS is in the city - what with all the buildings and reflections messing everything up. We came to the realization that there are a tonne of phones all around, each with a pretty-good-but-not-perfect idea of where it's located. So we got all excited about the idea of creating an app which uses all the clients to build mesh-based location service for phones with that app. It's like bittorrent, for GPS!</p> <!--break--> <p>We figured it would only take a few 'seed' nodes (that is, nodes with 100% certainty of their location) to stabilize a large mesh of nodes with fairly-certain distances to neighbouring nodes, and low certainty of its own location.</p> <p>Of course, it will be damn difficult to get the distances to neighbouring nodes. Bluetooth is too low range, wifi signal strength is not very accurate, tone measuring will be ridiculously annoying and GPS cannot be considered - or we wouldn't need a mesh at all!</p> <p>Which makes me want to test the idea in a game. The idea will be a community of "Mejh" (the jh being pronounced as the 'g' in 'genre'), where each member:</p> <ol> <li>Does not know their own location</li> <li>Can make a guess at their location, with a certainty level (or error margin)</li> <li>Knows the rough distance and (maybe) direction of nearby Mejh friends</li> <li>Can communicate with nearby Mejh to get their calculated positions</li> </ol> <p>There will also be some sort of oracle-Mejh, which has 100% certainty of location, but must remain stationary.</p> <p>The goal of the game will be to expand your colony as large as possible. Find resources, have the Mejh workers mine them and take them back home, in order to build more Mejh (and oracles), to find more resources, ad infinitum.</p> <p>I think it's a fairly innovative idea, so we'll see how it turns out! The main thing I like about this idea is it isn't dependent on a highly skilled graphics or sound person - I can really get away with some nicely themed dev art for it - think Darwinia. </p> <p>Not yet sure if I'll throw the code on GitHub, or keep it all to myself, but I'll certainly be keeping this up to date with how development is going. I'm leaning towards having it open, though.</p> <p>As for development, here's a bunch of details about my approach, and some rules I'm setting myself:</p> <ul> <li>100% browser based. No server component at all (at least, until I want multiplayer or high score tracking)</li> <li>Frequent releases, possibly continuous deployment when I figure out how</li> <li>100% DI. In my 80ish lines of JS at the moment, I have a single 'new' statement. I plan to keep it that low</li> <li>100% TDD. No code unless I have first written a test for it. This excludes DI container setup. But this <em>includes</em> DOM manipulation</li> <li>Maximum of 4 args per constructor. No logic beyond assigning instance variables in the constructor</li> <li>No jQuery. I shouldn't need it, and I want to use js without it for once</li> <li>A blog post per night of coding. Two for tonight, because it's my first go :)</li> </ul> <p>I'm setting pretty stringent rules for DI and testing for a few reasons. There really isn't that much of either in browser-based Javascript going on in the world. Heck, I doubt there's even that much of it going on in Node.js land. I'm hoping to show how it can be done successfully for a non-trivial project, and perhaps give reference to those who want it (contingent on me opening up the code, of course). I've had a lot of success with DI and TDD in my professional career thus far (DI primarily in C#, TDD in both personal and work projects), but I often get lazy and thing "not a big deal this time..." and regret it later. Especially with constructor parameter counts. I want to push myself further, and this will provider a great opportunity to.</p> <p>Stay tuned!</p> Wed, 10 Apr 2013 00:00:00 +0000 http://stevesspace.com/2013/04/mejh-dev-log-part-1/ http://stevesspace.com/2013/04/mejh-dev-log-part-1/ Asynchronous Tests and Javascript <p>A lot of Javascript unit testing frameworks out there provide an "asynchronous testing" feature, seemingly to get around the issue of testing things that use setTimeout, or uses xhr. The idea being if you want to test code like this, you can:</p> <!--break--> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="kd">function</span> <span class="nx">setFoo</span><span class="p">()</span> <span class="p">{</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nb">window</span><span class="p">.</span><span class="nx">foo</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">;</span> <span class="p">},</span> <span class="mi">2000</span><span class="p">);</span> <span class="p">}</span></code></pre></figure> <p>Replace setTimeout with $.ajax if you must, it amounts to the same thing.</p> <p>Obviously the following test won't work, since it needs a 2 second break:</p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="nx">setFoo</span><span class="p">();</span> <span class="nb">window</span><span class="p">.</span><span class="nx">foo</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">);</span></code></pre></figure> <p>Qunit gets around this by having a stop() and start() call</p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="nx">setFoo</span><span class="p">();</span> <span class="nx">stop</span><span class="p">();</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">start</span><span class="p">();</span> <span class="nb">window</span><span class="p">.</span><span class="nx">foo</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">);</span> <span class="p">},</span> <span class="mi">2000</span><span class="p">);</span></code></pre></figure> <p>Jasmine has a similar thing, but with a waitsFor function</p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="nx">runs</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">setFoo</span><span class="p">();</span> <span class="p">});</span> <span class="nx">waitsFor</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="nb">window</span><span class="p">.</span><span class="nx">foo</span> <span class="o">==</span> <span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">;</span> <span class="p">},</span> <span class="dl">'</span><span class="s1">window.foo should be set</span><span class="dl">'</span><span class="p">,</span> <span class="mi">2000</span><span class="p">);</span> <span class="nx">runs</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nb">window</span><span class="p">.</span><span class="nx">foo</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">);</span> <span class="p">});</span></code></pre></figure> <p>I don't know about you, but I don't particularly want to have to wait around for 2 seconds to see if this test succeeds. The whole point of unit testing is to get rapid feedback when things break - 2 seconds per test is not what I'd call rapid!</p> <p>There's a much better way; take advantage of Javascript's ability to replace practically anything - include setTimeout and XmlHttpRequest. Or, be lazy and let <a href="http://sinonjs.org/docs/#clock">SinonJS</a> do the walking for you:</p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">clock</span> <span class="o">=</span> <span class="nx">sinon</span><span class="p">.</span><span class="nx">useFakeTimers</span><span class="p">();</span> <span class="nx">setFoo</span><span class="p">();</span> <span class="nx">clock</span><span class="p">.</span><span class="nx">tick</span><span class="p">(</span><span class="mi">2000</span><span class="p">);</span> <span class="nb">window</span><span class="p">.</span><span class="nx">foo</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">);</span> <span class="nx">clock</span><span class="p">.</span><span class="nx">restore</span><span class="p">();</span></code></pre></figure> <p>Simple! Best of all, it's synchronous! Debugging this sort of test, should you need to, is simple.</p> <p>Basil comes with a Sinon adapter, which handles the setup and restoration of the timers for every test, so it boils down to:</p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="nx">setFoo</span><span class="p">();</span> <span class="k">this</span><span class="p">.</span><span class="nx">clock</span><span class="p">.</span><span class="nx">tick</span><span class="p">(</span><span class="mi">2000</span><span class="p">);</span> <span class="nb">window</span><span class="p">.</span><span class="nx">foo</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">);</span></code></pre></figure> <p>For completeness, if you have web service calls as well:</p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="kd">function</span> <span class="nx">someRequest</span><span class="p">()</span> <span class="p">{</span> <span class="nx">$</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">http://example.com/getSomeVariable</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">result</span><span class="p">)</span> <span class="p">{</span> <span class="nb">window</span><span class="p">.</span><span class="nx">foo</span> <span class="o">=</span> <span class="nx">result</span><span class="p">;</span> <span class="p">});</span> <span class="p">}</span></code></pre></figure> <p>Using async support (in this case, of Jasmine):</p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="nx">runs</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">someRequest</span><span class="p">();</span> <span class="p">});</span> <span class="nx">waitsFor</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nb">window</span><span class="p">.</span><span class="nx">foo</span> <span class="o">!=</span> <span class="kc">undefined</span><span class="p">;</span> <span class="p">},</span> <span class="dl">'</span><span class="s1">foo should be set</span><span class="dl">'</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span> <span class="nx">runs</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nb">window</span><span class="p">.</span><span class="nx">foo</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="dl">'</span><span class="s1">server result</span><span class="dl">'</span><span class="p">);</span> <span class="p">});</span></code></pre></figure> <p>This bit actually has a few big problems, I'm not sure how people get around this:<br /> 1) What timeout should I use for waitsFor? What is a reasonable amount of time?<br /> 2) How do I know what the server returns? Why do I even care?<br /> 3) I cannot run this test locally (from a file:// URL) - it has a hard dependency on my web app running, which may not be desirable</p> <p>So, let's let SinonJS do the heavy lifting</p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">fakeServer</span> <span class="o">=</span> <span class="nx">sinon</span><span class="p">.</span><span class="nx">fakeServer</span><span class="p">.</span><span class="nx">create</span><span class="p">();</span> <span class="nx">fakeServer</span><span class="p">.</span><span class="nx">respondWith</span><span class="p">(</span><span class="dl">'</span><span class="s1">http://example.com/getSomeVariable</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">fake server content</span><span class="dl">'</span><span class="p">);</span> <span class="nx">someRequest</span><span class="p">();</span> <span class="nb">window</span><span class="p">.</span><span class="nx">foo</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">be</span><span class="p">.</span><span class="kc">undefined</span><span class="p">;</span> <span class="nx">fakeServer</span><span class="p">.</span><span class="nx">respond</span><span class="p">();</span> <span class="nb">window</span><span class="p">.</span><span class="nx">foo</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="dl">'</span><span class="s1">fake server content</span><span class="dl">'</span><span class="p">);</span> <span class="nx">fakeServer</span><span class="p">.</span><span class="nx">restore</span><span class="p">();</span></code></pre></figure> <p>Again, Basil can take away the create() and restore() of the fake server, making it a whole lot leaner. </p> <p>This test no longer has any connectivity constraints or timeout dilemmas and enabled control over expected return values.</p> Mon, 18 Mar 2013 00:00:00 +0000 http://stevesspace.com/2013/03/asynchronous-tests-and-javascript/ http://stevesspace.com/2013/03/asynchronous-tests-and-javascript/ Robust Dependency Injection in MVC <p>I'm of the mindset that absolutely every programmer should have read Mark Seeman's <a href="http://www.manning.com/seemann/" target="_blank">Dependency  Injection in .NET</a>.  Or at the very least, <a href="http://www.manning.com/seemann/DIi.NET_sample_ch04.pdf" target="_blank">the free Chapter 4 excerpt</a> (the best chapter!)</p> <!--break--> <p>Perhaps then, I could do a search for resolving dependencies in SignalR clients (such as a hub) without being bombarded with the <a href="http://msdn.microsoft.com/en-us/library/system.web.http.dependencies.idependencyresolver(v=vs.108).aspx" target="_blank">blight that is IDependencyResolver</a>. Though it looks like they've done a bit of work on it since MVC3 (what the book was written for) - adding lifetime scopes and disposal - it's still not really enough. The <a href="http://msdn.microsoft.com/en-us/library/system.web.http.dependencies.idependencyscope(v=vs.108).aspx" target="_blank">IDependencyScope</a> interface lacks the ability to specify child scopes. You want 3 levels (say, application, session, request)?  Too bad.</p> <p>To set the record straight, the proper way to do it for MVC is by replacing the implementation of the controller factory with your own dependency resolving one. Observe something like this:</p> <figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">class</span> <span class="nc">DependencyResolvingControllerFactory</span> <span class="p">:</span> <span class="n">DefaultControllerFactory</span> <span class="p">{</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">IContainer</span> <span class="n">_container</span><span class="p">;</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="n">IController</span><span class="p">,</span> <span class="n">ILifetimeScope</span><span class="p">&gt;</span> <span class="n">_scopesForControllers</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="n">IController</span><span class="p">,</span> <span class="n">ILifetimeScope</span><span class="p">&gt;();</span> <span class="k">public</span> <span class="nf">DependencyResolvingControllerFactory</span><span class="p">(</span><span class="n">IContainer</span> <span class="n">container</span><span class="p">)</span> <span class="p">{</span> <span class="n">_container</span> <span class="p">=</span> <span class="n">container</span><span class="p">;</span> <span class="p">}</span> <span class="k">protected</span> <span class="k">override</span> <span class="n">IController</span> <span class="nf">GetControllerInstance</span><span class="p">(</span><span class="n">RequestContext</span> <span class="n">requestContext</span><span class="p">,</span> <span class="n">Type</span> <span class="n">controllerType</span><span class="p">)</span> <span class="p">{</span> <span class="n">ILifetimeScope</span> <span class="n">scope</span> <span class="p">=</span> <span class="n">_container</span><span class="p">.</span><span class="nf">BeginLifetimeScope</span><span class="p">();</span> <span class="n">IController</span> <span class="n">controller</span> <span class="p">=</span> <span class="p">(</span><span class="n">IController</span><span class="p">)</span><span class="n">scope</span><span class="p">.</span><span class="nf">Resolve</span><span class="p">(</span><span class="n">controllerType</span><span class="p">);</span> <span class="n">_scopesForControllers</span><span class="p">[</span><span class="n">controller</span><span class="p">]</span> <span class="p">=</span> <span class="n">scope</span><span class="p">;</span> <span class="k">return</span> <span class="n">controller</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">ReleaseController</span><span class="p">(</span><span class="n">IController</span> <span class="n">controller</span><span class="p">)</span> <span class="p">{</span> <span class="n">ILifetimeScope</span> <span class="n">scope</span> <span class="p">=</span> <span class="n">_scopesForControllers</span><span class="p">[</span><span class="n">controller</span><span class="p">];</span> <span class="n">_scopesForControllers</span><span class="p">.</span><span class="nf">Remove</span><span class="p">(</span><span class="n">controller</span><span class="p">);</span> <span class="n">scope</span><span class="p">.</span><span class="nf">Dispose</span><span class="p">();</span> <span class="k">base</span><span class="p">.</span><span class="nf">ReleaseController</span><span class="p">(</span><span class="n">controller</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span></code></pre></figure> <p>and override the default inside Application_Start</p> <figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="n">ContainerBuilder</span> <span class="n">containerBuilder</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ContainerBuilder</span><span class="p">();</span> <span class="n">containerBuilder</span><span class="p">.</span><span class="nf">RegisterAssemblyTypes</span><span class="p">(</span><span class="nf">GetType</span><span class="p">().</span><span class="n">Assembly</span><span class="p">).</span><span class="nf">AsImplementedInterfaces</span><span class="p">().</span><span class="nf">AsSelf</span><span class="p">();</span> <span class="n">IContainer</span> <span class="n">container</span> <span class="p">=</span> <span class="n">containerBuilder</span><span class="p">.</span><span class="nf">Build</span><span class="p">();</span> <span class="n">ControllerBuilder</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="nf">SetControllerFactory</span><span class="p">(</span><span class="k">new</span> <span class="nf">DependencyResolvingControllerFactory</span><span class="p">(</span><span class="n">container</span><span class="p">));</span></code></pre></figure> <p>Cut down to bare minimum for brevity. Substitute your own container in as necessary.</p> <p>If you're not in MVC Land, and you need to do it with the ASP.NET Web API, <a href="http://blog.ploeh.dk/2012/09/28/DependencyInjectionandLifetimeManagementwithASP.NETWebAPI/">Mark Seeman has the solution</a>, as always.</p> Tue, 05 Mar 2013 00:00:00 +0000 http://stevesspace.com/2013/03/robust-dependency-injection-in-mvc/ http://stevesspace.com/2013/03/robust-dependency-injection-in-mvc/ Test Setup <p>It's painful. You're writing tests for your app, as you should be doing, and the intent of the test is lost in all the test setup.  You have mocks being initialized here, ambient contexts being switched there, test variables being created over here, test fakes being generated over there. There's a lot to it!</p> <p>What's more, tests often have a common test setup, so you'll extract that to a method, maybe mark it with a [TestInitialize] attribute or something, and hope that the setup is used across all tests in that fixture.  Of course, this creates a large amount of separation between the test setup and the test assertion, but hey, our tests are <a href="http://en.wikipedia.org/wiki/Don't_repeat_yourself">DRY</a>!</p> <p>Of course, people have come up with methods to try and fix this.  One is to create a test fixture per setup, to increase cohesiveness.  Another is to use something like <a href="https://github.com/AutoFixture/AutoFixture">AutoFixture</a>, to inject your setup and test variables. These are well and good, but I don't think they're quite there. It's still not readable enough.</p> <!--break--> <p>We need more hierarchical test setup support.  An example is worth much, so here's one in the traditional (flat) style.  Thanks to <a href="http://qunitjs.com/">Qunit </a>for the syntax:</p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">somePerson</span><span class="p">,</span> <span class="nx">sut</span><span class="p">;</span> <span class="nx">module</span><span class="p">(</span><span class="dl">"</span><span class="s2">Person Repository</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="na">setup</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">somePerson</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Person</span><span class="p">(</span><span class="dl">"</span><span class="s2">Foo</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Bar</span><span class="dl">"</span><span class="p">);</span> <span class="nx">sut</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PersonRepository</span><span class="p">();</span> <span class="p">});</span> <span class="nx">test</span><span class="p">(</span><span class="dl">"</span><span class="s2">Person can be added to repository</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">sut</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">somePerson</span><span class="p">);</span> <span class="nx">equal</span><span class="p">(</span><span class="nx">sut</span><span class="p">.</span><span class="nx">count</span><span class="p">(),</span> <span class="mi">1</span><span class="p">);</span> <span class="p">});</span> <span class="nx">test</span><span class="p">(</span><span class="dl">"</span><span class="s2">Person can be loaded from repository</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">sut</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">somePerson</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">loadedPerson</span> <span class="o">=</span> <span class="nx">sut</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="nx">somePerson</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span> <span class="nx">equal</span><span class="p">(</span><span class="nx">loadedPerson</span><span class="p">,</span> <span class="nx">somePerson</span><span class="p">);</span> <span class="p">});</span> <span class="nx">test</span><span class="p">(</span><span class="dl">"</span><span class="s2">Person can be deleted from repository</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">sut</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">somePerson</span><span class="p">);</span> <span class="nx">sut</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">somePerson</span><span class="p">);</span> <span class="nx">equal</span><span class="p">(</span><span class="nx">sut</span><span class="p">.</span><span class="nx">count</span><span class="p">(),</span> <span class="mi">0</span><span class="p">);</span> <span class="p">});</span></code></pre></figure> <p>Yes, I could have made the default setup add the person, so there's be less stub setup in the functions, but then the intent of the tests would be less clear - the module would need to be "Person Added Tests".</p> <p>Compare with input from <a href="https://github.com/xwipeoutx/basil">basil</a></p> <figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="nx">describe</span><span class="p">(</span><span class="dl">"</span><span class="s2">Person</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">person</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Person</span><span class="p">(</span><span class="dl">"</span><span class="s2">Foo</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Bar</span><span class="dl">"</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">sut</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PersonRepository</span><span class="p">();</span> <span class="nx">when</span><span class="p">(</span><span class="dl">"</span><span class="s2">adding person</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">sut</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">person</span><span class="p">);</span> <span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">sut</span><span class="p">.</span><span class="nx">count</span><span class="p">()).</span><span class="nx">to</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="p">});</span> <span class="nx">when</span><span class="p">(</span><span class="dl">"</span><span class="s2">loading person</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">loadedPerson</span> <span class="o">=</span> <span class="nx">sut</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="nx">person</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span> <span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">loadedPerson</span><span class="p">).</span><span class="nx">to</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="nx">person</span><span class="p">);</span> <span class="p">});</span> <span class="p">});</span> <span class="nx">when</span><span class="p">(</span><span class="dl">"</span><span class="s2">deleting person</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">sut</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">person</span><span class="p">);</span> <span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">sut</span><span class="p">.</span><span class="nx">count</span><span class="p">()).</span><span class="nx">to</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="p">});</span> <span class="p">});</span> <span class="p">});</span> <span class="p">});</span></code></pre></figure> <p>Isn't that nicer? Of course I cheated and used <a href="http://chaijs.com/">Chai's</a> fluent assertions too (which gives an opportunity for almost test script readability in the test output). This is a much simpler example than the real world too, with only 2 levels of test setup (adding-&gt;loading, adding-&gt;deleting). Hierarchical setup just scales so well.</p> <p>What's more, most IDEs allow you to collapse functions - it's very easy to collapse and ignore all the unimportant bits and focus on the rest.</p> <p>The pains of this test setup and readability was (and still is) my main motivation for making Basil JS.  Other frameworks required calling specific setup methods, and still didn't support arbitrary nesting.</p> <p>My hope is to have these sorts of features available in other languages - I'd love to see a similar approach in C#, for example.  Maybe that'll be my next project.</p> <p>Expect to see some screencasts highlighting the usage of basil in the near future.</p> Fri, 15 Feb 2013 00:00:00 +0000 http://stevesspace.com/2013/02/test-setup/ http://stevesspace.com/2013/02/test-setup/ Baptism <p> So as most of the folks who read this will already know, Paula and I have recently had a daughter.   Now for background, we go to a Lutheran church but don't really agree with infant baptism. </p> <p> The result? Many many conversations and lots of research about baptism! While I think I can understand the viewpoint of baptism as an act of salvation a lot more now, I still can't quite agree with it. </p> <!--break--> <p>It seems the main point is that baptism saves in the same way that the gospel saves, not in the same way that Jesus saves.  What that means is that it is another thing that <em>leads</em> to salvation, it <em>leads</em> to Jesus, but it doesn't actually perform the saving act - that's Jesus' death on the cross, no ifs, buts or maybes about it.</p> <p>But is baptism a necessary step on this road to salvation? Of course not! Thief on the cross is the most obvious example, but there are more than enough modern accounts of people coming to Jesus without knowing about baptism that the question seems somewhat ludicrous.</p> <p>So is it helpful on the road to salvation? Now this is where I start diverging from the Lutheran viewpoint.  The main points are there are promises made that apply directly to the baptised - <a href="http://www.biblegateway.com/passage/?search=romans%206:1-4&amp;version=NIV" target="_blank">Romans 6</a> and <a href="http://www.biblegateway.com/passage/?search=Colossians%202:9-12&amp;version=NIV" target="_blank">Colossians 2</a> are examples of this.  Now, reading this verses, I doubt very much they can be taken in the context of non-believers - these very verses are in letters written <em>to</em> believers, and the grammar does not really extend to "you" in the general world-wide sense.</p> <p>I find it fascinating that this viewpoint comes from the denomination that's most renown for its focus on grace.  If one subscribes to the view that through the act of baptism, the child is given promises, it makes baptism a "work".  <a href="http://en.wikipedia.org/wiki/Sola_gratia" target="_blank">Sola gratia</a> be damned, baptise your child so they can get God! It flies in the face of "God, not us".  Now, it is generally said that it's God that's at work in the baptism, but waving your hand and saying "Oh, it's God" doesn't make that true.  The fact is, for baptism to occur, someone has to sprinkle (or dunk) water on the recipient. And if it's only through that task that God can do his magical baptism work, then "grace alone" is not being adhered to.</p> <p>So what's my take on this whole baptism stuff? It's very simple. If you come to Christ, and you have not yet been baptised, you need to take a look at your beliefs. It's a very obvious command, and such a simple thing to do that not getting baptised immediately should make you question your devotion to obeying Christ's commands.  It's right there in <a href="http://www.biblegateway.com/passage/?search=matt%2028:16-20&amp;version=NIV" target="_blank">Matt 28</a>, in <a href="http://www.biblegateway.com/passage/?search=Mark%2016:15-18&amp;version=NIV" target="_blank">Mark 16</a>, in <a href="http://www.biblegateway.com/passage/?search=john%203:22-26&amp;version=NIV" target="_blank">John 3</a>, in <a href="http://www.biblegateway.com/passage/?search=acts%202:38-41&amp;version=NIV" target="_blank">Acts 2</a> - in many other places! There are few commands that are so clear for a believer to follow, and so simple to perform, that I sometimes wonder if it's sole purpose is to just be a test of belief.</p> <p>So, we're not going to be baptising Sophie until she can make that decision for herself.  We will certainly dedicate her, teach her and guide her.  We will pray for her and not leave her out in a spiritual wasteland (yes, we've had that accusation).  And when (if) she comes to faith, she can get baptised, and know she is obedient to the simple command.</p> <div class="comments"> <h2>Old comments</h2> <div class="comment"> <div class="comment-author">Jon</div> <div class="comment-contents"><p>Baptism at birth doesn’t seem relevant, or necessary. I was baptised at birth and am a Christian, yet I know people who were also baptised at birth and are not. So I don’t think baptism (at birth) alone saves anyone at all. It doesn’t even sway their spiritual walk at all. I think the environment the child grows up in will help push them towards Christ, but He alone is the only one who can save the child. I think that’s right? :S</p> <p>Should I bother being baptised again as an adult? Would it really make any difference if I have already been baptised? I don’t see the point in repeating it. Besides, I am already saved.</p> <p>On a side note: <a href="">http://www.biblegateway.com/passage/?search=Mark%2016:15-18&amp;version=NIV</a> I think it is interesting that I have never seen those signs.</p> </div> <div class="comment-date">Sun 17 February 2013, 06:29 AEST</div> </div> </div> Thu, 14 Feb 2013 00:00:00 +0000 http://stevesspace.com/2013/02/baptism/ http://stevesspace.com/2013/02/baptism/ Latest Projects <p>A quick run down on what I've been working on since my last update of ages ago. My last project-related post was 3 years ago, when I was doing the 3D version of Zombies are Attacking My Fridge.</p> <!--break--> <p>Since then, I've worked on the following the following:</p> <ul> <li>(Removed) <strike>ZAMF2D: 2D version of ZAMF - ZAMF2D @ Steve's Space</strike></li> <li>jFluidic: Javascript version of Fluidic - jFluidic. <a href="/projects/jfluidic">jFluidic @ Steve's Space/</a> and <a href="https://github.com/xwipeoutx/jFluidic">https://github.com/xwipeoutx/jFluidic</a></li> <li>Basil: Javascript testing framework with Hierarchical test setup - <a href="https://github.com/xwipeoutx/basil">https://github.com/xwipeoutx/basil</a></li> <li>Chalkboard Parkour: Entry to an XBox game programming competition - <a href="http://www.youtube.com/watch?v=GjeR6GP4OiU">http://www.youtube.com/watch?v=GjeR6GP4OiU</a></li> </ul> <p>So I've been fairly busy, arguably not busy enough!</p> <p>My priority at the moment is to finish Basil - it's in a workable enough state now, but the filtering needs work before it can be used in any reasonably large project (such as what we have at Zap).  I'm quite proud of Basil - I'm unaware of any other testing framework in any language that matches it in terms of readability of the test.  Hopefully it takes off in popularity when I'm finished with it and the documentation.</p> <div class="comments"> <h2>Old comments</h2> <div class="comment"> <div class="comment-author">Jon</div> <div class="comment-contents"><p>Basil sounds like such a good project. I assume there aren’t any JS libraries out there doing what you are doing? I think it’s cool that it stemmed from a need you had at work but that you are also willing to share it with the rest of the public. I am struggling to think of something. I wish I could contribute but I have no idea where to start, and I don’t really use Javascript at my job at all. I would be more of a burden than help., just slowing you down.</p> <p>I am itching to start some kind of open source programming project, but I have like a back log of other projects I need to finish first. Like a heavy sack just weighing me down. I need to prioritize them and finish them off. For me at the moment the attitude is not to think of the whole project, like some big monolith task I need to complete. Instead, my attitude towards projects I want to finish is to just spend time on it every day. If I continually do this, the project should just finish itself. It’s so easy to get optimistic and excited about newer projects, but it side tracks me so much. Essentially it’s procrastination.</p> <p>Late last year I was thinking about projects I wanted to do this year and one was to start blogging more. I want to code it up in php, just so I can exercise that language again, because it feels feint and distant. It’s like maths, I can remember studying it at uni, however if you threw a problem infront of me I would not remember how to solve it straight away.</p> <p>I look forward to reading you future posts :)</p> </div> <div class="comment-date">Thu 07 February 2013, 08:24 AEST</div> </div> <div class="comment"> <div class="comment-author">Steve</div> <div class="comment-contents"><p>There are a couple of libraries out there that do similar - the most famous one is <a href="http://jasmine.github.io" rel="nofollow">Jasmine</a>, which you can see has a similar grammar to Basil (very common grammar to have in behaviour driven libraries). But notice in order to have setup, you need to have these ugly beforeEach and afterEach functions? Makes it harder to read. for no real gain. Basil also is meant to just be a test runner - you can plug in your own assertions and mocking. I prefer best of breed over a monolithic project (similar to TFS vs Git/TeamCity etc.)</p> <p>As for projects, I’d suggest lowering the scope/size. I never finished the ZAMF projects because they’re too darn large. Basil is already in a very usable state, and that motivates me to just add more to it when I get the time.</p> </div> <div class="comment-date">Thu 07 February 2013, 08:37 AEST</div> </div> </div> Sun, 03 Feb 2013 00:00:00 +0000 http://stevesspace.com/2013/02/latest-projects/ http://stevesspace.com/2013/02/latest-projects/ A Stale Start? <p>Yeah... stale start, as opposed to fresh start - because I'm not going to remove any of the previous posts, but I shall be writing again!</p> <!--break--> <p>I found it hard to keep up with a blog that was almost entirely filled with Christian posts and apologetics. If I hadn't had a particularly enthralling conversation or anything, it was very hard to write anything. And writing something when the last post is several months ago is quite difficult, so I basically stopped writing anything.</p> <p>So I'm going to refocus and expand a little now. I'll still have any religious tidbits I have to share coming along, but I imagine this will have a lot more programming adventures in it.</p> <p>So keep tuned :) I'll be posting again shortly.</p> <div class="comments"> <h2>Old comments</h2> <div class="comment"> <div class="comment-author">Paula</div> <div class="comment-contents"><p>You know, you could always post all that stuff you’ve learnt/been talking about baptism recently. :P</p> </div> <div class="comment-date">Thu 07 February 2013, 17:36 AEST</div> </div> </div> Sun, 03 Feb 2013 00:00:00 +0000 http://stevesspace.com/2013/02/a-stale-start/ http://stevesspace.com/2013/02/a-stale-start/ Church and Damnation <p>Someone recently mentioned a description of church to me in part as "Oppressive Culture". I haven't yet had a chance to clarify that description with them, but it struck me as a very odd description, and I'm seeking to understand what was meant by it.</p> <!--break--> <p>The only thing I can think of is the idea that's common among the unchurched* of church being a place where we are told to behave, and if we don't then hell awaits. I think this is a fundamental misunderstanding of the goals, actions and role of the Church. I can understand, however, where this idea comes from; we see it often in street preachers and tele-evangelists, we hear it when we think of Westboro Baptist Church* and, of course, we hear it from the unchurched. When we think of "Church History", we also conjure images of crusades, witch hunts and inquisitions. so it's not as if these ideas of "behave or suffer" are unfounded. It's not indicative of the Church as a whole however - merely indicative of vocal minority groups (of which most Christians would not condone), or indicative of horribly mistakes made by the organised Church.</p> <p>I've been an active church member for around 5.5 years now, and never have I been threatened to go to hell. Never have I been manipulated into doing anything that I didn't want to, with the pain of excommunication or damnation. There has never been a "turn or burn" preached at my church, or any church I've attended. Indeed, I believe that kind of behavioural conditioning is completely unbiblical and anti-thetical to the Christian message.</p> <p>I struggled with attempting to correct other people's actions for a long time. I would say to coworkers "watch your language," not because it was unprofessional, but because I don't think it's right. I would call people out on piracy and excessive drinking, because I somehow thought that "behaving better" was a goal congruent to Christ's.</p> <p>I've realised since that my ideas there were completely bass-ackwards (I think I'm far more tolerable/tolerant now!). Christ did not create His Church in order to make people good citizens, or helpful people. His goal was far more radical than that - to bring people unto Himself. You see, it's rather unique as a religion - the idea that no matter how much we alter our behaviour, we cannot redeem ourselves. Everyone is in the same boat - whether they're Mother Theresa or Stalin.</p> <p>The problem with thinking altered behaviour is a goal is the receding horizon. </p> <p>Let's say an unchurched person wanders into a church that says don't be bad, or you'll be dining in hell. They start treating their kids with respect again instead of beating them. They stop holding up liquor stores, and get a job instead, because they know that stealing and beating will send them to hell. Is that enough? Are they good yet?</p> <p>He returns to church, which says that even little bad things will send him to hell. This person stops gambling, stops lying to their coworkers and boss for gain because they know they'll go to hell if they continue to do these things. Is that enough? Are they good yet? </p> <p>The church now says that it's not enough to simply be "not bad", but they have to be good, or else hell awaits. They start giving to charity, they volunteer at homeless shelters. Is that enough? Are they good?</p> <p>I'd say a church doing the above has probably done a good job at getting someone to behave correctly, but they've missed the point. The person is not obedient due to a moral sense, but because of fear of reprisal. Outwardly, it may look ok, but inwardly, it's not.</p> <p>That's why the Church's goal is not to condition behaviour. It doesn't change a person's heart, and it presents a legalistic view of morality. Jesus' main beef against the church leaders of the time (Pharisees and Sadducees) was this legalism. No, Jesus said instead to believe in Him, and the rest will happen. Change the heart, and the body will follow. The reverse isn't strictly true.</p> <p>Which is precisely why you don't see Churches today saying "Stop doing X or you'll go to hell". It's futile. If X is gone, Y, Z, alpha, beta and epsilon will still remain, and the person won't truly be changed. Plus, the commandments "Love the Lord your God" and "Love your neighbour" (which are the pillars of all the other commands) cannot be done out of fear, or even effort. We are ALL in the boat of not being "good enough", and no behavioural conditioning will change that.</p> <p>But in following Christ, asking forgiveness for our wrong doings (which is offered freely), and receiving His grace (again, offered freely), we can be free of the cycle of trying to be good enough. And with the love of Christ to guide and transform us, it will come naturally to "behave". THAT is the goal of the Church. Turn to Christ. Then, being good will follow.</p> <p>The simplest response to this is "Well, you've just moved the line from "Be good or burn" to "Follow Christ or burn". How is that not oppressive? Well, the argument is then moved from morality to the truthfulness of Jesus as God, which is another story altogether.</p> <p>I heard an atheist claim in a debate which was, in part, about the hope given in salvation. The claim was that atheists do not like the fact that the earth will be kaput in 5 billion years, but are under no illusions that it will remain forever, and it's not a negative thing to be grounded in reality.</p> <p>The same can be said for a Christian. We don't like the idea that people don't take the a gift of eternal life, freely offered, and thus have to face up to God's other side - Justice and Holiness - and held to account for it all. But we're under no illusions that this is the not case.</p> <p>I made claims here that some will have scoffed at. Accept that under the Christian world view, these views are all internally consistent (with itself) and externally consistent (with reality). There's no incongruency at all. At this point, the only beef you can have is with the truth of the claims of Jesus Christ. Saying "you will burn in hell if you don't follow Jesus" is not immoral if it is true. Maybe it's not sugar coated, tactful or politically correct - but it is the height of morality.</p> <p>If anyone has any statements, questions or responses to this (or indeed, anything I've written), I would love to hear - either by comment, email, twitter, whatever. I'm putting my views and beliefs out there as a way for others to gain understanding of my point of view. I'm very interested in learning other's point of view too, so please, let me know!</p> <p>I think I'll move away from these morality posts, and do a bit about evidence and rational reasons for believing. I believe following God is more rational than not, given the evidence (and lack of any <i>real</i> response to them - so often the response just comes back to morality).</p> <p>Thanks for reading :)</p> <p>1. I will use the term "unchurched" throughout this, as it's the most neutral word I can think of to describe someone who has never been an active member of a Christian congregation.<br /> 2. Wow, I just noticed the wiki article for that 'church' says it's a hate group. Not that I dispute that as such (I find their actions abhorrent), I would have thought that kind of description was against wiki's NPOV pillar. Maybe not though, reading the wiki entry for "hate group".</p> <div class="comments"> <h2>Old comments</h2> <div class="comment"> <div class="comment-author">Simon</div> <div class="comment-contents"><p>Hi Steve,</p> <p>I think you’ve gotten the correct gist of what I meant by “oppressive culture”. To clarify: I was referring to the church as an institution, rather than individual churches, or churches in general. I gather that individual churches have enough independence that they don’t have to preach the ‘party line’, so to speak, and you’re right that directly telling people “Stop doing X or you’ll go to hell” would be counter-productive.</p> <p>Even without explicit threats however, you can still tell people how to think. If they don’t think that way, then they must be ‘sinning’. Sure, they could ask forgiveness, but they’re still going to be left with the guilt that they’ve somehow wronged Christ (never mind whoever else they may have affected). And so they try not to think that way anymore. I would argue that the stick of immediate guilt is much stronger than the stick of distant, and abstract, hell, which can be avoided by asking for forgiveness anyway.</p> <p>There will also be an effect of peer pressure within an individual church which makes it difficult to speak out against authority. This is by no means unique to religion of course. Authority by its nature creates this kind of environment - more often than not, if you disagree with the authority, then you’ll be wrong, right? They wouldn’t be in charge if they were wrong, surely. The difference here between the church and say, the workplace, is that the church teaches morals, which you will use as the basis of your behaviour, while the workplace teaches how to properly fill out your TPS form, which has a much more shallow effect.</p> <p>How open to disagreement is a church? If your priest makes a point with a passage from the bible, do you feel able to question him about a contradicting passage? If he teaches a certain morality that you don’t necessarily agree with, do you feel free to debate him? I’m not talking about you asking him a question, and him giving you an answer, but rather a proper rational debate.</p> <p>Oppression does not have to be in overt form, and this is why I used the phrase “oppressive culture”. The teachings of a church can be purely positive, but still foster an oppressive culture, if the negative aspects are not openly discussed.</p> <p>I’m not really trying to refute your assertions here, I’ll leave that sort of thing to your future evidence and rationality-based posts, where arguments on both sides can be more direct. I just thought I would attempt to give you a better idea of what I meant by “oppressive culture”. Obviously it would have been difficult to fit all of this into 140 characters :).</p> <ul> <li>Simon</li> </ul> <p>PS. I have actually been ‘churched’ somewhat while I was in school in England. We were taught that Christmas should always be spelled with a capital C, and never ever X-mas, because Christmas is about Christ. Of course no mention was made to its roots in paganism. Anyone who didn’t agree with singing the weekly christian hymns, or reenacting the nativity play, was politely asked to leave for the rest of the assembly/class. Everything was kept positive of course, but free-thinking was hardly encouraged. This is just my brief experience of course, and I don’t put too much weight into it. You can still count me as one of the unchurched.</p> </div> <div class="comment-date">Tue 13 April 2010, 18:20 AEST</div> </div> </div> Mon, 12 Apr 2010 00:00:00 +0000 http://stevesspace.com/2010/04/church-and-damnation/ http://stevesspace.com/2010/04/church-and-damnation/ OT Laws - Good Then, Revised Now <p>I think I need to clarify on the Old Testament laws, and their application today. This is by no means a complete look at OT laws, it's just a slice of some of them that helps us find perspective. It will mainly focus on laws that we look at today and think "That's bad". There are a lot of other ones we may say "That'd odd" or "That's pointless" to today, but I won't be covering them here.</p> <!--break--> <p>Matthew 19:1-12 contains a conversation between Pharisees and Jesus. It basically goes like this:</p> <p>Pharisees: "So, like, is divorce ok for any/every reason?"<br /> Jesus: "No"<br /> Pharisees: "Then why does the Law of Moses say it's ok?" (see: Deut 24:1-4)<br /> Jesus: "That Law was given because of your hard hearts, but it was not this way from the beginning."</p> <p>Yes, I missed a lot, but this is the part that pertains to my point. Read the passage, the rest doesn't change the scope. God has a view of marriage - it is not in His design for separation to occur in any circumstance*. However, God knew that the people of the time would not accept that law, so He gave them a more relaxed law, that he knew they could obey. Namely, divorce only if something "indecent" is found about her, and no remarrying if she's remarried and divorced/widowed.</p> <p>It's very clear that the laws given in Deut regarding this are less strict than that given by Jesus. "Indecent" changed to "Marital Unfaithfulness". Has God's standard for marriage changed? No, but He believes our hearts are softer regarding this - so He gives us a more strict moral standard, one which holds up to even today's standards*. Indeed, this is much more moral than today's standards, which state, "get a divorce whenever you want, really. It's cool." (If you disagree here about what is more moral, it's probably because of different views of what love and marriage are and entail, so don't jump on it straight away.)</p> <p>This can't be the only Law that was given that catered for the people of the time, certainly not. Take the slavery laws - Deut 21 vs Ephesians 9. For the times these laws were given, slavery was pretty much the global civic. It happened. The sermon on the mount (Matt 5) is almost exclusively dedicated to redefining the laws in a more moral sense - ones that stand up to even our greater global morals today.</p> <p>So the most that can be said about the apparent harshness of the laws in the OT is that it was given in a context where people had hard hearts, and wouldn't accept laws that were stricter than those.</p> <p>Even with the Jews' hard hearts, the laws given were better than what else was around then. You cannot equate laws of 5000 years ago with laws of today without taking cultural maturity into consideration. The laws were good, for their time. Now, well that's another kettle of fish.</p> <p>Furthermore, the 10 commandments are in play. The laws given aren't the focus, the commandments are. The first 2 commandments alone are all that is necessary for all the other commandments, all the Old Testament and New Testament laws. Think of the 10 Commandments as the "What" and the Law as the "How". Others see the 10 Commandments as "God's" law, and Moses' law as the "State" law. Church / State separation perhaps?</p> <p>When it comes to today, the laws still have to be interpreted via the 10 commandments, and must pass the "filter" of New Testament teachings - I say filter, because nothing is taken away*, only narrowed down and simplified. Remember, God's Law hasn't changed, but our ability to respond to them has.</p> <p>While I say nothing is taken away, a lot of stuff is rendered irrelevant. "Masters, love your slaves" obliterates the relevance of "If your slave loses an eye because of you, they can go free". Of course, if you're respecting them, then they won't lose an eye in the first place. "Don't Murder" is supplanted by "Don't be angry with anyone". And both of these are covered by "Love your neighbours as you love yourselves", and more so, "Love your enemies".</p> <p>Stop focussing on OT law. It's much clearer in the New Testament, from the same source (God), and is more catered to the state of our hearts. The very laws that govern our society come from these roots, and there's very-near parity between them. Saying God's laws are immoral, while touting our society's laws is a lesson in contradiction.</p> <p>If someone has objections about the morality of the standards put forth by God to us today as explained in Bible, I would seriously question their motives. The standards put to us (and lived out by Jesus to show as possible) are far better than any other set of moral laws ever invented.</p> <h3>Footnotes</h3> <p>1. Yes, it says except for marital unfaithfulness, so I'll grant that exception. But then, I don't think unfaithfulness is in God's design for marriage either, so it's a moot point.<br /> 2. Though, admittedly, there is controversy over this too - what about an abusive partner, for example? That's another argument which won't be covered here, as it's a bit off-topic.<br /> 3. Not strictly true - the ritualistic cleanliness / worship ones are taken away, for example, because that context for worship is gone.</p> Mon, 22 Mar 2010 00:00:00 +0000 http://stevesspace.com/2010/03/ot-laws-good-then-revised-now/ http://stevesspace.com/2010/03/ot-laws-good-then-revised-now/ Slavery in Exodus 21: A response <p>Before I get into it, I think I'll shed some light on why I'm responding to this in particular. The following twitter 'conversation' happened today, between me and a follower of a work colleague. I'm xwipeoutx, and I've changed the other guy's name to "Other Guy", in case he wants anonymity (though his twitter is public, so I'm pretty sure it doesn't matter). Excuse my poor formatting... the style sheet on this blog only does so much.</p> <p> <!--break--> <b>Other guy: </b> #Religous should not have these jobs.RT @BibleAlsoSays: Pharmacist refuses to issue pill because of her religion http://tinyurl.com/ykq5hbj<br /> <b>xwipeoutx: </b> @[other guy] via [colleague]: Way to generalise. How about #thatperson shouldn't have that job?<br /> <b>Other Guy: </b> no! no religous person should have jobs like that if they don't understand the basics about how the world works. #christianfools<br /> <b>xwipeoutx: </b> no athiest person should have jobs like that if they don't understand the basics about how the world works. #athiestfools<br /> <b>Other Guy: </b> your stupid.B original @least loser bitch. How many children your religion has killed.Ill send u a news article that will tell u<br /> <b>xwipeoutx: </b> Yeah, that's not worth replying to. And they say we're the intolerant ones.<br /> <b>Other Guy: </b> you can't. You need to have a base knowledge of reality. Something evolution has left out in you.</p> <p>What followed was a long uhh 'discussion' between my work colleague and 'other guy' that didn't end well, and then "Other Guy" followed saying something along the lines of "You will be hearing from me shortly." I said I'd be happy to have a civilised conversation with him, and a few hours later, I got another tweet:</p> <p> <b>Other Guy: </b> Its time so remind some atheist why we do this. Some are apologist. Like @[colleague]. #Atheist reads the bible http://bit.ly/a0akHj<br /> <b>xwipeoutx: </b> Ooh, nice. Exodus. Read sermon on the mount? Jesus' interpretation. Also, Eph 6:9.<br /> <b>Other Guy: </b> how much do you enjoy cherry picking. Your doing it now.<br /> <b>xwipeoutx: </b> I don't actually have headphones, I'm at work, so I'm going from the descriptions of the video<br /> <b>xwipeoutx: </b> But twitter is not long enough to explain this in detail, in any case. I'll PM you my email if you're truly interested<br /> <b>Other Guy: </b> I see. Here's another for when u have time http://bit.ly/9hcquU The Genealogy of Jesus<br /> <b>xwipeoutx: </b> I'll respond to them tonight, when I get home, probably via a blog post so it's public.</p> <p> I won't have time to respond fully to the genealogy video tonight - and frankly, I couldn't be bothered responding to it in depth at all, unless some more substance shows up. My quick response to that video is "...and?" Who's idea was "Jesus' sacred bloodline"? Who cares if Gentiles are in the mix? Did you see what happened AFTER David's little midnight frolic? How does the usual response fail to take into account the exact thing the usual response is addressing? I don't think Christians have anything to "apologise" for from the genealogy video...so on to the slavery one.</p> <h3>Response</h3> <p>First up, I now realise how off base my response about the Sermon on the Mount and Ephesians (well, that bit was slightly relevant). My excuse is I was going from the video summary, not the video itself, and even then, I only gleaned. I got out of it "slavery", "eye for an eye" and "unborn children", not realising 2 of these were describing omissions, content. Whoops! My apologies, I can see how my response seemed like cherry picking, I was responding to slavery and "eye for an eye" as quickly as I could.</p> <p>My method of response will be simple. I'll deal with the video in its time context, then deal with it in today's context, and then I'll deal with the over-arching topic of morality in the bible, and its applicability to proving/disproving God and God's morality.</p> <p>The video deals with these passages: Ex 21:1-6, 20-21, 26-27</p> <h3>Time Context</h3> <p>Ex 21:1-6. I don't have a problem with any of these, I don't really see why anyone would. As far as I know, Hebrews were the only one with a year of Jubilee - slaves can go free. Please correct me if I'm wrong on this. The parts regarding wives is solely to do with this, so if you think it unjust, keep that in mind - letting the slave's wife leave as well is more graceful than anything else at the time.</p> <p>The term for "Wife" used in this passage isn't wife in the strict sense we know - we'd probably use something closer to "a woman" now. I'll presume for this sake that the passage DOES mean wife as we know it, because that's in the Other Guy's favour, but keep it in mind</p> <p>So, the wife leaving with him is graceful, no arguments there. But giving him a wife just to take it away? Well, that seems harsh. My take on it is that, in slavery, the master would only give the slave a 'wife' for the sole purpose of bearing sons (and, less so, daughters) - and since the 'slave' is property (let's face it, back then, that's what they were considered), is this really surprising? There was a lot of things unjust about slavery in the day*, this potentially being one of them. You can't chalk that up to religion though - slavery was rampant almost everywhere. I'll reiterate here that it was far LESS harsh than slave rights anywhere else. Besides, it doesn't prohibit the master from sending them both (or all, in the case of a kid) away, if he so desires.</p> <p>Next, if the slave really can't bear to leave the wife given to him*, the law gives him an opportunity to come back - albeit, for a price - slave for life, by choice. This is on equal footing with other slave purchases, with the added benefit of it being a choice. A tough choice, but a choice nonetheless. The animation depicted the Awl pretty gruesomely, and that's something I reject. I'd say it was through the earlobe, like a modern day piercing (with more crude equipment!) - a mark to say you're a slave for life. Again, equivalent to what was going on at the time - there was always some form of branding for slaves. Seeing a pattern here?</p> <p>The "daughter" section was conveniently cut off before the part addressing the caring and rights of said daughter. Please, atheist evangelists - if you're going to quote the bible, don't cherry pick. We probably know it better than you, and besides, it's easy to check when chunks are missing*. The bit that <i>was</i> included - how is it any different to an arranged marriage? Remember, polygamy was in and widely practised then. Oh, right, the emotionally loaded animations that muddy the interpretation. Of course.</p> <p>Now on to verses 20 and 21. In 20, the master being punished if the slave is killed; again, better than anything else at the time. In 21 (master not punished if the slave survives), it even rationalises it there - because it is his property. Property - that's what slaves were considered <i>at the time</i>. No other culture had any punishments for this at all, yet the Hebrews did.</p> <p>Finally, if the slave loses an eye or tooth, they are set free. It heartily discourages beating - if you accidentally knock a tooth out (I'm sure a slave's tooth in 3000BC wasn't exactly strong), you've lost a lot of money. If nothing else, this would limit whipping to the torso area. Better than the surrounding, religion-free regions.</p> <p>So in summary, most of the rules here are far better than the (lack of?) rules for slaves in other cultures. At this stage, the Jews would be very aware of slavery practices - they'd only just escaped as slaves from Egypt 7 chapters earlier. In any case, if you were going to be a slave, being a slave for the Hebrew was the way to go. It's temporary, and masters are encouraged to look after you. Find me another culture of the time that's the same, and I'll salute you.</p> <h3>Modern interpretation</h3> <p>Today, slavery is almost completely outlawed. Yes, there are areas where slavery is still practised, but I'm not sure that's relevant to us, because there's certainly not any that I come in to contact with. Why is slavery gone now? A chief driving force for it was Christian Abolishinists, at least for Europe. I don't know about other countries. Who knows, were it not for these Christians, we may not even find slavery that abominable - it could still be a fact of life. I said "could be" - it is, of course, unknowable if that is the case or not.</p> <p>But despite that, what's the take on those Exodus passages today? The New Testament writings neither condone nor condemn slavery - but rather, put frameworks in place to ensure there's nothing inhumane going on there. Ephesians 6:5-9. This is <b>not</b> contrary to the Exodus teachings. If indeed that law is still applicable, the teachings in Ephesians only serve to strengthen them, making them by FAR the most humane slavery the world has seen. You do know what era this stuff was being written, right? It was pretty much the height of the Roman empire. Why not have a look at their treatment of slaves? No deity set their rules. Christian-owned slaves (again, the greek is closer to servants) are treated more like household servants; cared for - not threatened. There's not even any favouritism allowed! Find me a more humane slave system.</p> <p>In Christianity, any New Testament interpretation generally trumps "Traditional" interpretations of the old testament - sermon on the mount, and Jesus' dealings with Pharisees are excellent examples of this. The new light cast on the old laws gives them strength that holds up to the world's even more refined moral views today. When interpreting Old Testament laws, we must read them in light of Jesus, his teachings and his character. To do anything less is simply lacking context, and I'd go as far as saying irresponsible</p> <h3>On Biblical Morality</h3> <p>So often, I see Atheists say "God did/allowed this bad thing in the bible. Therefore, God is bad." I can't <b>believe</b> how often this comes up, and it's just ridiculous - I honestly can't make any sense of it.</p> <p>First, let it be known the Bible is being used here - atheists are using the bible as a basis for argument against God. In order to do this, the atheist <i>cannot</i> say "but the bible isn't true", or that throws his own argument out the window. He can't even say "God isn't real" for this argument, because the bible says He is, and, well, the whole argument is built around the assumption that the bible is true. Saying "no, I'm just these verses as an example, the rest isn't true" is surely a fallacy. So don't go all "oh my gosh, he's placing such faith in the bible, doesn't he know it's just a book, not true and not relevant?" because that doesn't make sense here. The book being true is an axiom of the argument put forth by the atheist.</p> <p>In the bible, it says who God is. Loving. Powerful. Holy. Those 3 sum is up beautifully. Oh, and the bit about how He's the creator*.</p> <p>The difference in being between God and us is so insanely crazy, that it's unimaginable. Read Job 40 and 41 for some scope on God's power and knowledge, as described in the bible. Can you even begin to comprehend His ways? And even if you can, and fully, what right do you have to question that anyway? He's the creator of everything, and the judge over everything! It's like me writing a program on a computer, and then it saying to me that perhaps I shouldn't have implemented something the way I did. It's ridiculous! Humans, and our pride and self-inflated image of ourselves thing we know better for ourselves than God does.</p> <p>Look, if the bible is used for an argument, all of it must be - Christians get called out on "cherry picking" all the time, well, it's a 2 way street. In it, God is far far beyond our comprehension. It is not for us to decide what is moral and what isn't, because it's not our world - it's His. What is left for us is seeing HOW something there is moral - a task made a lot easier, since Jesus' revelations. When read in this light, it's amazing how often biblical teaching lines up with modern day morals.</p> <h3>Footnotes</h3> <p> 1. One wonders how civilisation would have fared without slavery at all. We'll never know, but it's possible that it was necessary <i>for the time</i>. I'm not condoning it, just wondering.<br /> 2. One also wonders how many of the slaves even liked the wife they were assigned? How many would come back? Remember, the wife/children here is for the benefit of the master, not the slave.<br /> 3. The obvious omissions that atheists leave out when they're arguing these things is ... odd. Do you think we won't notice? Do you want to present a slanted view? Do you care about finding the truth, or are you just being a fundamentalist? This particular one couldn't have been accidental - the slavery passages were picked out from the chapter, and the rest were summarized. So it had been read numerous times.<br /> 4. Ahh, bible creation, another argument. It's not relevant here - the bible doesn't mention HOW, just THAT he did. If it makes you feel better, assume that God has a noodly appendage, and created it with that.</p> Thu, 11 Mar 2010 00:00:00 +0000 http://stevesspace.com/2010/03/slavery-in-exodus-21-a-response/ http://stevesspace.com/2010/03/slavery-in-exodus-21-a-response/ On holding to account... <p>The other day, Paula and I went to the youth bible study, which we've been helping to lead over the past couple of weeks. This week, our Pastor was there and the idea of the study was to hold some of the youth leaders to account - we basically went through 1 Tim 3, which defines some attributes that a leader must have, and talked about each point.</p> <!--break--> <p>Now, it seems that some or more of the potential youth leaders haven't particularly been living up to these ideals, but what I found most interesting was the Pastor's struggle with telling the youth leaders that.</p> <p>You see, he said that 2 verses had come to mind when doing this:</p> <ul> <li><a href="http://www.biblegateway.com/passage/?search=1%20Peter%204:17a&version=NIV">1 Pet 4:17</a>: It is time for judgement to begin in the house of God [...]</li> <li><a href="http://www.biblegateway.com/passage/?search=Matthew+7%3A3-5&version=NIV">Matt 7:5</a>: [...] First, take the plank from your own eye [...]</li> </ul> <p>These aren't particularly surprising - generally they're at the forefront of most people's mind when they feel moved to point out errors in people, especially the 2nd one. What was most surprising though was the conviction and emotion which the Pastor displayed when he was telling the kids. It was the most gracious, moving and heartfelt admonition I've ever seen, and probably ever will. He was quite distraught at the prospect of being a 'judge', because he knew that he wasn't necessarily better.</p> <p>It made me think, you know - whenever I'm pointing out sin in others (which isn't very often, really), why do I it? Is it with pride or humility? Do I think I'm better than them? Do I even have a right at all to admonish anyone at all? When I'm being held to account, do I ever think "You're no better yourself, you have no right to judge me"?</p> Sat, 24 Oct 2009 00:00:00 +0000 http://stevesspace.com/2009/10/on-holding-to-account/ http://stevesspace.com/2009/10/on-holding-to-account/ Rantings of a Microsoft Slave: Part 1 - Initial Reaction <p>So, I've recently moved to a new job - a job where I'm now to be working in Microsoft products, seemingly only. I'm not kidding - from a complete OSS job in PHP, linux (RHEL and Ubuntu), MySQL using Eclipse, Joomla, vTiger and so on, to a complete .NET/Microsoft centric place: Vista, Outlook, OneNote, VS2008, C#.NET, MSSQL, TFS, IE8 - you name it, Microsoft is the answer</p> <!--break--> <p>A few people have said they'd be interested in hearing about the transition from OSS to .NET, and how it stacks up, so I'm starting a web series which I'll update whenever I feel like it. The series may only be 1 long, who knows? I am struggling to do my best to leave my prejudice at the door but...well, it's not easy :P</p> <p>My initial reactions are almost entirely negative. The first "what the?" that jumped out at me was the compilation and deployment process. I dealt wtih PHP, a scripting language. Upload it, and it's there. A web app that required compilation and deployment was relatively new to me, but as I'd dealt in C++ and even C# in desktop apps before, it wasn't a big deal. What WAS a big deal was the speed of the first page load - Easily 5x as long as the compilation process</p> <p>Y'see, for some unsightly reason, on the first page load it has to go through and recompile it. The WHOLE application - I think it's JIT compiling it this time around, to change it from the CLR to native code, for speed. It may be that the compilation process in VS just copies the script files, and the asp.net handler needs to compile it. I'm not totally sure, I just know it's a pain in the butt to have to wait 5 mins between compile and test, especially when dealing with simple web services. Why can't they give us an option for an interpretted mode, which just scripts it. Would certainly make my recompiles easier</p> <p>I really, really miss the terminal. Whether it's to grep inside files, check the processes running, hack up a quick test script or simply just navigate the file system, the linux terminal, with those excellent tools brought to us by GNU, is much MUCH nicer than anything windows has thrown at me. Or, heck, piping to the "less" command. I know, I know, I could use cygwin or gnuwin32, but for anyone who's been even exposed for a brief amount of time to the windows command line (what? I can't change the width?) knows it's not a valid comparison.</p> <p>So a general feel is that everything is somewhat clunkier. MSDN documentation is stupid to navigate around (doxygen output is quite clean), and usually lacking vital information (custom attributes that you won't expose the keys for? Why?). SVN vs TFS (I really like the TFS job list support/integration, but damn it's slow, and why doesn't double click open up my merge editor? And don't get me started on the in built editor...). Getting the latest .csproj file (if someone has added a file, for example) means I need to re-import the project. Fair enough. Why does that take forever? It's really not that big.</p> <p>Finally, the fact that it's closed source. I has some problems with Joomla sessions and SSO previously. I was able to trace through the code to fully understand what it was doing and get it solved. I am currently having some trouble with a .NET library feature that's not exposing internal calculations I need for the UI. Oh well, nothing I can do about it. Unless it reflects...</p> <p>It's just a lot of little things that makes me feel that the solutions offered are about 90% done. Ideas that work well, that just aren't executed. What confuses me is how MS developers are ok with the sub-par developer support - don't they use this stuff daily?</p> <p>Oh, linux, how I miss thee. And you, LGPL, with your open code and navigatable (if sometimes lacking) documentation. And you, eclipse, with your perspectives and myriad of plugins</p> Thu, 30 Jul 2009 00:00:00 +0000 http://stevesspace.com/2009/07/rantings-of-a-microsoft-slave-part-1-initial-reaction/ http://stevesspace.com/2009/07/rantings-of-a-microsoft-slave-part-1-initial-reaction/ Almost ready to maze <p>So I've updated yet another uhh...update. This time around I've neatened things up, fixed some bugs, and added some polish. Here's what's changed:</p> <!--break--> <ul> <li>Objects now snap-to-grid</li> <li>Weapons, walls, zombies and player all properly collide / slide (sliding is a little jumpy atm, but not too bad)</li> <li>Fixed some crashing that was happening due to physics objects not being properly removed</li> <li>Tried it out on linux - I get segfaults at random, and I have no idea how to debug properly on linux *sigh* </ul> <p>So the reason I've done the snap-to-grid feature is so I can work on mazing. Try it out now - You can build mazes with magnets or turrets, and you can walk through it. However, that is kind of useless without some shortest path algorithm.</p> <p>The best algorithm for this application will be A* - it can find the shortest path to a destination node (the fridge) from every other node. I only need to run it once for every blocking object that gets added, so performance is good, and with proper heuristics, no problemo!</p> <p>I was pondering the idea of using fluid dynamics for the pathfinding. See, the pressure stabalisation step of fluid dynamics essentialls "smooths" things out, and makes the fluid run along boundaries. All I'd have to do is initialise every grid position with a vector pointing to the destination node, and run a few jacobian iterations over the domain, and voila! Done! I want to try it out and see how it goes, seems like a novel approach that could work. The main problems I see are with very complex mazes (fluid would virtually stand still due to compression/numerical accuracy), very thin mazes (if walls are on either side of a single cell, it's hard to solve), and U shaped areas (the fluid may end up just going in circles once inside which would be funny, but not good for gameplay). I'll let you know how it goes :)</p> <p>The only other thing I've done is update the ZAMF page to have the link to the latest at the top, beneath the picture, rather than the bottom. Woohoo!! As always, click the link on the right to check it out</p> Wed, 22 Apr 2009 00:00:00 +0000 http://stevesspace.com/2009/04/almost-ready-to-maze/ http://stevesspace.com/2009/04/almost-ready-to-maze/ Largest Update Yet! <p>Whew, I've just finished my biggest update yet, feature wise. I was surprised at how fast this one came out, but I'm quite pleased with the state of this product - it seems to be rock solid, fast, accurate and *gasp* even a little bit fun!</p> <!--break--> <p>So, since this seems almost like a milestone release, I think I'll write a bit about what's been done. Most of the content of this post will be over on the project's page, too, which will be updated as I go on.</p> <p>As you know, I had this project done a fair bit using my own custom rolled engine, but maintenance became just a tad extreme, and I figured that other people have already done stuff, so why should I have to reinvent the wheel? Some research found me Ogre</p> <p>The hardest thing about changing to Ogre was the way the main loop is done. In my own system, every scene node had an overrideable "Update" function, where I implemented my AI and everything. Basically, I extended scene nodes for every type of actor there was. In Ogre, you can't really extend the scene node class, without overriding the scene manager as well (which I didn't want to do) - instead, "frame listeners" are used - a function is called every tick on a frame listener, and they have to update the scene node. So I wrote a wrapper around all this so I can simply register my own AI classes, and this handled all the keyboard and mouse events</p> <p>While I was at it, I put in a game state manager and level loader - this time actually thinking about the transitions between levels. There's functionality in place now to put loading screens in (but not actually used), and properly clear any cache or anything that I have lying around. Woohoo!</p> <p>The next big task was deciding on a physics engine. My first instincts told me to go for PhysX (aka Nx) - there were binding classes around to do some of the integration work for me, and it wasn't long before I had a dummy app in Ogre that I could throw boxes around and watch them smash each other up.</p> <p>However, after trying to get it into my engine, it became apparent that the binding classes included were too restrictive - it tried to wrap every single function up, and so I was left with a half of a physics engine that I could actually use, with the overhead of the whole library, and it lacked very VERY important features - such as a simulation tick callback, or listeners.</p> <p>I decided to scrap Nx and go for <a href="http://www.bulletphysics.com">bullet</a>. The first set of bindings I used was similar to the nx bindings - tried to wrap every function - so I had issues with that straight away. Then I found a second set, far more slim - more of a converter than integration. It allowed me to throw entities at it, and get back bullet representations of those - cylinders, boxes, spheres, convex hulls, trimeshes, you name it. Brilliant! I now have a physics engine.</p> <p>Ahh, physics is difficult. I'll give a brief rundown on how things work here, but it's a very complicated topic. It took me several forum posts, hours of reading, and hours of "DARGH WHY ISN'T THIS WORKING" until I got it down. So here goes.</p> <p>The bullet physics engine is governed by 4 parts - the world, dispatcher, solver and broadphase. The idea is we create a world (which holds physics objects) and attach a dispatcher to it (which stores and moves collision information around). We also need to set up the broadphase - a topic in and of itself, but this handles the broadphase part of the collisions (checks bounding boxes, or whatever - cull lots of objects quickly). The solver is responsible for resolving collisions - if a truck hits a tennis ball, what is the result?</p> <p>So, how does one set up bullet physics? Simply:</p> <ul> <li>Set up the world, dispatcher, solver and broadphase</li> <li>Attach some form of internal callback function, to handle the results of the collision</li> <li>As the game is going, add collision objects/rigid bodies to the world. Each object should have a motionstate attached to it - which synchronises the scene node position/orientation with the physics object</li> <li>At each timestep, tell bullet to step the simulation</li> </ul> <p>This will get everything set up, and even make things bounce of each other properly. However, that last bullet point is a doozy. I'll put what I think happens inside that step to clarify:</p> <ul> <li>Simulation step is started</li> <li>The broadphase goes through and finds all <i>possible</i> collisions, creating a contact manifold for each and throwing it at the dispatcher</li> <li>The dispatcher looks at each manifold, and performs a low-level collision detection on each primitive (ie. box, sphere), storing the information of any contact points in the contact manifold</li> <li>If dynamics is enabled, solves any collision responses, making each object interact</li> <li>Calls the internal tick function specified by the user, with the collision world as a parameter</li> <li>Here, the user must loop through the manifolds, find all the contact objects and points, and perform any custom collision responses (playing sounds, losing health, whatever). To make this step more possible, one can set up a user pointer (void*!!!), which can hold anything. I chose to make it hold a physics response interface which I can subclass, it works really well. I just need to cast it to this IPhysicsResponse, and call my Collide function. Yay!</li> </ul> <p>And that's that! Physics was definitely the hardest thing to set up - harder than the graphics engine, I would say. The main reason it's so hard is because documentation is really, really bad. It's written by a bunch of academics, and it's more fun to make the physics engine than it is to document it. Oh well. The forums are absolutely awesome though, hurrah!</p> <p>Once I got the physics sorted out, I could move on to implenting decent gameplay. Here's where things sped up somewhat. I was able to perfect the collisions between bullets and zombies, and stop the zombies running through each other, and add a magnetised weapon, and stuff. I tried to add a kinematic player controller (basically, the user is governed by physics rather than by moving it), but it seems I should wait until the next Bullet release for that.</p> <p>My most recent additions to the engine include a config file, so I can much more easily tweak things like zombie health, cooldown rates and starting money, and sound. That's right, the sound that added so much to the previous version is now in this one! And improved - I now have a dying sound, 2 sets of music and a zombie hitting sound.</p> <p>All in all, I'm quite pleased with my progress. Check it out <strike>here</strike> (page removed), and let me know any suggestions/ideas/critcism/anything! Feedback is most welcome!</p> <div class="comments"> <h2>Old comments</h2> <div class="comment"> <div class="comment-author">Jeremy Sandell</div> <div class="comment-contents"><p>Great work; are you planning on doing a Linux build for this? It looks like all of the libraries are compatible.</p> <p>Also, which bullet wrapper did you finally decide on?</p> </div> <div class="comment-date">Thu 05 August 2010, 23:41 AEST</div> </div> </div> Wed, 08 Apr 2009 00:00:00 +0000 http://stevesspace.com/2009/04/largest-update-yet/ http://stevesspace.com/2009/04/largest-update-yet/ Getting there... <p>Whew, so I'm 90% done moving to Ogre. </p> <p>Check it out <strike>here</strike> (Page removed). Ignore what that page says, I haven't updated it yet. </p> <p>Time for sleep. I shall edit this post later.</p> Fri, 03 Apr 2009 00:00:00 +0000 http://stevesspace.com/2009/04/getting-there/ http://stevesspace.com/2009/04/getting-there/ Status Update and New Page <p> So I've been working on moving engines - bigger job than I thought. I've put a state manager in, so I can easily allow the game to be paused, have a menu etc. - it adds quite a bit of polish, really. I've also put in a message dispatcher, so there's no one central hub for all messages, and components can register themselves there in order to receive messages - it makes message handling much easier, and allows me to customise input quite easily.</p> <!--break--> <p> But as for the game itself, well I have a zombie running around in circles, and the ability to create turrets which destroys the zombie that runs around. It shouldn't be long before I have a replica of the old stuff - I only have to add zombie behaviour and scoring, really, and it's done! (ish).</p> <p> In other news, I've updated the <strike>ZAMF page</strike> (Page removed) - it now includes a downloadable version. I figured there's no harm in sharing it with the world! :-)</p> <p> Be sure to give me feedback by commenting here! Tell me what you'd like to see, what you'd like to not see, things to add/remove and all that, I'll do what I can :-)</p> Wed, 04 Mar 2009 00:00:00 +0000 http://stevesspace.com/2009/03/status-update-and-new-page/ http://stevesspace.com/2009/03/status-update-and-new-page/ Moving Engines <p> So, it seems I've bitten the bullet - being so close to the core (working straight from DirectX, and using no wrapper libraries besides what I create myself) has finally done me in. While I know can get the project working continuing on with my current engine, it doesn't seem very efficient for me to do so.</p> <!--break--> <p> For example, for me to get shadows working on my project, it took several days of coding (I was trying out an omni-directional shadow mapping technique). To get it working in <a href="http://www.ogre3d.org/">Ogre 3D</a>, it took 5secs - a call to <b>setShadowTechnique</b>, and then a call <b>setCastShadows(true)</b> for each node I want to cast a shadow. Simple! Time saving!</p> <p> So in order to be able to get results in the project in a timely fashion, I'll be porting all my code over to another engine. At the moment, I'm erring towards Ogre3D - the only drawback is it's graphics only, not sound/input/network/physics as well. But it has a nice plugin system, and can apparently support things like PhysX quite nicely. I was going to use Torque, but it costs money, and I don't too much of that lying around for something that I don't even have example code for to see how good it is...</p> Sun, 22 Feb 2009 00:00:00 +0000 http://stevesspace.com/2009/02/moving-engines/ http://stevesspace.com/2009/02/moving-engines/ Sound DOES help <p>So, I spent today wasting time playing tower defense, and writing a sound manager to plug in to my engine.</p> <!--break--> <p>The way it currently it works is simple. The sound manager has a play sound method, which takes a wav filename as a parameter. This method loops through all the current buffers, and compares the file names, to see if they're the same. If it's not the same, it creates a new buffer, which contains the information to play the file. It then loop though all the sources and tries to find one that is currently not busy playing something. If it can't find one it also creats a new source.</p> <p>Then after figuring out all these sources and buffers, i get the source to play the buffer - and it's done!</p> <p>This still causes some issues though because, apparently, I can't have more than 30 sources at a time, and so I need to prioritise the sound so that closer objects get more priority over further away objects.</p> <p>It's also a little bit annoying because there's no way of stopping sounds part way through as it stands - I may want to cut out a zombie attack sound if he gets shot mid attack, or whatever. Not really possible.</p> <p>The only other thing that I should really do is set this stuff up in some sort of config file, so I can say "play zombie attack sound" instead of "play zombie_attack.wav". Not a huge issue for this game though, be nice for the engine as a whole.</p> Fri, 13 Feb 2009 00:00:00 +0000 http://stevesspace.com/2009/02/sound-does-help/ http://stevesspace.com/2009/02/sound-does-help/ Apparently Sound Helps... <p>So, I figure it's about time I figured out how to put sound effects into my game.  I've heard good things about OpenAL, so I thought I'd give that a go for this project.</p> <!--break--> <p>Starting out with OpenAL is really <a title="Starting OpenAL" href="http://www.gamedev.net/reference/articles/article2008.asp">quite easy</a>, and I got a sound happening in the game (gunshot firing) in an hour or so, not bad for someone who has never dealt with sound before. Turns out OpenAL is very similar to OpenGL in framework, so I'm sure that helped a lot.</p> <p>Anyway, the main problem with what I've got now is that I currently have 1 sound playing at a time, if I send it another sound, the previous sound will stop and this one will take priority - and it sounds really lame when I have 10 turrets firing at the same time. So I need to work out how to get multiple sources playing at the same time. Of course, this means I have to make a sound manager, so instead of just saying "play this sound", I say "this object wants to request playing this sound", and then only play it if there are enough sources available to play it.</p> <p>And the sound manager has to keep track of what sources are playing, and which have finished - so I can free that source up for another sound when it's ready. So that's what I'm up to now, writing a sound manager. I think it will be a while - especially since I can't find any tutorials or anything.</p> Fri, 13 Feb 2009 00:00:00 +0000 http://stevesspace.com/2009/02/apparently-sound-helps/ http://stevesspace.com/2009/02/apparently-sound-helps/ Quinn vs Dawkins <p>FINALLY found someone who's not afraid to go on the offensive against Dawkins, and effectively. Quick summary:</p> <!--break--> <ul> <li>Dawkins constructs a lot of <a href="http://en.wikipedia.org/wiki/Straw_man">straw-men</a></li> <li>Dawkins evades questions on free will, saying it's not important - when it's tantamount to philosophy and morality</li> <li>Dawkins mentions attrocities commited by religious, while saying that atrocities commited by athiests FOR atheism is irrelevant</li> </ul> <p>Yeah, I had a great laugh.</p> <p><a href="http://catholiceducation.org/articles/science/quinndawkins2.MP3">Clicky Linky Download</a> (MP3)</p> <div class="comments"> <h2>Old comments</h2> <div class="comment"> <div class="comment-author">Hermi</div> <div class="comment-contents"><p>Quinn didn’t make a single good point unfortunately, merely repeated his dogma with his ears shut.</p> <p>Dawkins is quite correct that free will is not an important discussion in this case. The universe is just a giant physics simulator; your sense of self is an illusion and there is no absolute morality. Plainly there is no room for free will in the absolute sense, but clearly humans perceive that they have free will in the same way that we perceive that we are conscious beings. These truths don’t lend themselves to the argument because Quinn would reject them out of hand.</p> </div> <div class="comment-date">Tue 15 September 2009, 13:26 AEST</div> </div> <div class="comment"> <div class="comment-author">Steve</div> <div class="comment-contents"><p>Honestly unsure if you even gave it a good listen. Just a short list of the points Quinn gave:</p> <ol> <li> <p>Tooth Fairy” vs “Divine Being”, and growing up. Once people gain the ability to reason properly, they realise the Tooth Fairy isn’t possible. They may continue to believe in God though, as reality and belief in God are consistent. Your challenge: come up with one inconsistency. Just one.</p> </li> <li> <p>Existence of Free Will - which Dawkins <em>completely</em> dodged, saying its unimportant and complicated, but never refuting its existence. Free will is impossible without consciousness - impossible in this “physics simulator” view you mention…which brings us to…</p> </li> <li> <p>Existence of Matter - This “Physics Simulator” has to come from somewhere. Yes, “Science is working on it”, and hopes to find an answer - but the leading hypothesis (that, by the way, cannot currently be <em>refuted</em>, it also cannot be proven) is that something or someone created it. What Quinn called “The Uncaused Cause”</p> </li> <li> <p>The whole point about Dawkins going for pages about atrocities commited in the name of God, but playing down the atrocities commited by atheists against relious people. The fact that Dawkins disagrees with this is kinda weird, has he not read his own book? It’s a double standard if ever I saw one.</p> </li> <li> <p>Evolutionary Scientists thinking they’re qualified to be talking theology - indeed, in Dawkins’ case, thinking he’s more qualified. Eg. “Everyone who doesn’t have the same view of the “Old Testament God” as me must not have read the Old Testament.” Bollocks, he’s come to his opinion of God before he’s read it, read it, and it agreed. Grats, he accused people of getting their morals using the same methodology. What was the point?</p> </li> </ol> <p>Finally, regarding the whole free will/physics simulator thing. You make a claim: “The universe is just a giant physics simulator”. Prove it. That’s a remarkable claim and, if you can prove that, then I’m sure you will win a prize of some sort, and people will be abandoning their religion in droves. Your faith in that statement is as bold as my faith in “The universe was created by a divine, personal being who loves us, despite our desire to go against him - to the point of sending His Son down to die on our behalf, so we might have a relationship with Him.” My statement has indicators too (much like <code class="language-plaintext highlighter-rouge">G = g*M_1*M_2/(d^2)</code> is an indicator of the universe being a physics simulator, for example).</p> <p>If you want to find out about any of these indicators, any of this evidence (which I honestly doubt you will, but I hope you will), I’d be happy to go through them with you, or you could read some of the plethora of books available on the subject. One of the better ones is “Case for Creator” by Lee Strobel. By no means perfect, yes it contains some funky science which causes one to raise ones eyebrows, but most of it is good stuff, and shows you how much of faith is rooted in solid evidence.</p> </div> <div class="comment-date">Wed 16 September 2009, 10:48 AEST</div> </div> </div> Wed, 09 Apr 2008 00:00:00 +0000 http://stevesspace.com/2008/04/quinn-vs-dawkins/ http://stevesspace.com/2008/04/quinn-vs-dawkins/ The Bankruptcy of Atheism <p> So, I went to a public a little while back titled "The Bankruptcy of Atheism", by Dr. Alister McGrath. The title was someone misleading, as he spent most of his time defending theism than he did pointing out why atheism is bankrupt. It was essentially an 'interaction' with Richard Dawkin's book, "The God Delusion"</p> <!--break--> <p> Probably over 500 people came to see the public lecture, which was too much for the room he was in, so a video feed was sent to a second room (where I was), and this room had all the aisles filled up, and people standing outside the door trying to listen in!</p> <p> As a quick overview, he summarised "The God Delusion" in 5 points:</p> <ul> <li>Science naturally leads to Atheism</li> <li>Belief in God is irrational</li> <li>God is a virus of the mind</li> <li>Religion impoverishes view of the universe</li> <li>Religion is a bad thing</li> </ul> <p> Over the course of the lecture, he systematically addressed each of these points. I'll attempt to do the same, basically repeating what Dr Alister said. <p> Before getting into the topic, it's a good idea to define a few terms (ok, one term). So for the purposes of the lecture, Dr. Alister used Dawkin's definition of religion, which was essentially "The belief in a God", but with obvious emphasis on the Abrahamic religions (Judaism, Islam and Christianity).</p> <p> Also, before getting in to anything, he mentioned briefly that Atheism has been around since forever, and there's nothing new about. However, he stated that 1789-1989 was the "Golden Age" of Atheism, starting with the start of the French Revolution. Some event happened in 1989, which I can't remember nor find in research, which perhaps symbolised the end of this Golden Age...it was fairly arbitrary though.</p> <p> Oh, and a disclaimer. I haven't yet read all of "The God Delusion", and I had barely even started it at the time of the lecture. So I will only say what Dr Alister said about it, and may write some more analysis when I finish reading it.</p> <p> Well, on to the first topic.</p> <h2>Science Naturally Leads to Atheism</h2> <p> Dawkins says quite plainly that science naturally leads to Atheism. This seems quite sound on the surface, as evolution and Darwinism quite neatly explains any problems with creation. He also states that science and religion are incompatible - that is, mutually exclusive</p> <p> Dr Alister's first point was to debunk the claim that science and religion are mutually exclusive, and talked about several devout Christian scientists, least of which is not the director of the Human Genome Project, Francis Collins. He mentioned several others, but I didn't write them down. I think anyone who studies any science, and believe in a deity can quite easily debunk the claim they're incompatible. More on this in the "Religion impoverishes the view of the universe" section.</p> <p> I think the crux of what Dawkins was getting at in this point was the science does not point to God, so there mustn't be one. Dr Alister put forth that, if anything, science leads to agnotism. Essentially, the result of science is, and will always be "we don't have enough evidence to comment either way". The scientific method is essential <i>incapable</i> of deciding whether or not God exists. Which is why a survey done on a bunch of scientist people (sorry, no source, but I'm fairly sure they weren't hand picked, and were all quite "prestigious") showed a 50/50 split between theists and atheists among them. </p> <p> Stephen Jay Gould is mentioned a few times by Dawkins, and is Atheist. Gould has written a few books on darwinism and evolution and stuff, however has made it clear that his atheism has nothing to do with science. In fact, he is completely against the notion that darwinism implies atheism.</p> <p> Finally, Dr Alister mentioned stated, for the theist, science is simply modelling God's creation.</p> <h2>Belief in God is irrational</h2> <blockquote><p>[Faith is] blind trust in the absence of evidence even in the face of overwhelming counter evidence</p></blockquote> <p> Dawkins uses this definition of faith (or something similar) to put forth his point that belief in God is irrational. You can see clearly it's quite a loaded definition, and very few (if any) theists would agree with this definition. So I have no idea who what exactly he's trying to say - yes, if that was what faith is about, then I agree, it's irrational. I would call that definition blind faith. Let's see what theists (or rather, Christians) define faith as:</p> <blockquote><p> The theological virtue defined as secure belief in God and a trusting acceptance of God's will.</p></blockquote> <p> <strong>Note:</strong> This definition comes from dictionary.com, but I believe it lines up with what I say. You'll notice a lot of other definitions on the same site say "despite inadequate evidence" and stuff, which is probably true. But it is NEVER true about contrary evidence, and nothing about blind trust. Indeed, I believe the trust has to come before the faith, just like people don't trust traffic lights until they prove their worth.</p> <p> Dr Alister used a better definition of faith, that I can't find unfortunately, but when I saw it, I'm like "yep". I wish I'd written it down :(. But I hope that clears up anything about blind faith vs faith, and the rationality of it. I don't know a single Christian (or indeed, any theist) that has blind faith without sufficient evidence to back up that faith.</p> <p> Dawkins states the belief in God is akin to belief in santa clause or the tooth fairy. To illustrate the fallacy of this argument, Alister asked how many people in the audience still believe in santa and the tooth fairy. He then took that further and asked how many people, before turning 18, didn't believe in santa, but now do. Clearly, it's a completely different kettle of fish.</p> <p> Coming back to the study mentioned in the last heading, scientists are generally split on theism/atheism, so we can safely say it's outside the realm of science to deduce God's existence (or lack thereof). The next step is 1 of 2 possible courses of action - inference to best explanation, or empirical (best) fit. Simply put, the answer cannot come from science, so it must come from somewhere else. Where, though? Well, religion, philosophy and metaphysics</p> <p> I can't remember where this fits in, but he also mentioned around this time that science answers the 'how' questions, and cannot provide answers for 'why'. Questions like "What is the meaning of life?" cannot be answered. Dawkins, and certainly other scientists, state that what science cannot explain, doesn't matter. So, the meaning of life doesn't matter... Just mull over that for a while.</p> <h2>God is a virus of the mind</h2> <p> This is fairly ludicrous, and it wasn't talked about much. But the basic idea was that religion is a virus of the mind, much the same that the plague is a virus of the body. It spreads to other people.</p> <p> Like I said, not much time spent, so here's a quick list</p> <ul> <li>Real viruses are viewable - not so with religion. Dawkins is just hypothesizing</li> <li>Are all beliefs a virus of the mind? Or just some? What about the belief in no God?</li> </ul> <p> Dr Alister said a bit about atheism being a 'faith' of sorts to, since no evidence points to a <i>lack</i> of God either. Which is where the last point in that list fits in. That's all I have to say on that point.</p> <h2>Religion impoverishes view of the universe</h2> <p> Dawkins states that theists have a drab view of the universe, and backs it up with several examples of religious people accepting a mundane creator, and quoting Carl Sagan's "Pale Blue Dot":</p> <blockquote><p>How is it that hardly any major religion has looked at science and concluded, , ‘this is better<br /> than we thought! The Universe is much bigger than our prophets said–grander, more subtle, more<br /> elegant. God must be even greater than we dreamed’? Instead, they say, ‘No, no, no! My god is a<br /> little god, and I want him to stay that way.' </p></blockquote> <p> I can certainly sympathise with Carl Sagan - why do religions shy away from the<br /> beauty and grandeur of what God created, being what the prophets said, and more!<br /> I personally haven't met anyone who is like the theist in Carl Sagan's quote,<br /> though I'm sure they exist. I think it's more of a personal each person<br /> thing than a God thing. But anyway, there's 3 ways that a theist can enjoy nature:</p> <p> <strong>Breathtaking moments:</strong> Those moments when you see an amazing sunset,<br /> and it takes your breath away, and your heart misses a beat. Theists aren't<br /> immune to enjoying it like this, just as noone is - religion doesn't stop that<br /> appreciation (though it does stop the worship of a lovely sunset).</p> <p> <strong>Mathematical Appreciation:</strong> Appreciation for the sheer mathematical<br /> brilliance of how the world works. I can definitely relate to this, having<br /> spent the greater part of this semester investigating fluid dynamics in only<br /> a 2d system. It blows me away at how complicated it is, and only in 2d. This<br /> isn't contrary at all to religion. Indeed, James Clerk Maxwell is just one example of<br /> someone wanting to find out more about a phenomenon explicitly BECAUSE of his beliefs</p> <p> <strong>Evidence of Creator:</strong> This one is unique to theists. We can<br /> appreciate nature stuff on a whole new level, and that is because it points<br /> towards the Creator. Or rather, bears witness to the creator - by finding<br /> out more about nature, more attributes of the creator are realised.</p> <h2>Religion is the root of all evil</h2> <p> The final point and, really, the trump card of Dawkins is that religion is a bad thing.<br /> It's the root of all evil, and only bad things come out of it. This is backed up<br /> by numerous references of instances where religion has caused violence.</p> <p> I'm not going to defend that (and nor did Dr. Alister) - Dawkins is absolutely right.<br /> From crusades, to witch hunts, to the spanish inquisition, and much much more,<br /> religion is the cause of a lot of pain in the world.</p> <p> The question Alister posed was whether this is characteristic of religion, or if<br /> it is of the fanatic? The answer is fairly obvious - while these bad things that<br /> happened are a result of religion, it's definitely not typical. Dawkins doesn't<br /> actually mention any good things that have come out of religion...such as, oh,<br /> the abolishment of slave trading in the British empire, not to mention the<br /> countless charities and...well, etc.</p> <p> The problem with Dawkin's statement here is that it can immediately be turned on<br /> its head. Atheism is the root of all evil. And this can be backed up by just as<br /> many examples of evil coming as a direct result of atheism. I wrote down<br /> "Soviet Union, 20th Century" in my notes here. Unfortunately, my history sucks,<br /> and so too does my google skills, seemingly.</p> <p> However, it's irrational to look at these 'bad eggs' and say "wow, atheism is the<br /> root of evil'. That's just stupid, really :P...That wouldn't prove a point though,<br /> would it? It's much more sensible to say that beliefs inhibit both good and bad<br /> things. A result of our human nature, perhaps?</p> <p> But (Dawkins says) that religion provides transcendant motivation. What does this<br /> mean? Simply that fanatics can go 'God says to do this, and that trumps all'. There<br /> is no authority higher than that of God. I would agree with this statement,<br /> to a degree - I would add that it's a bit rash of one to blow themselves up because<br /> of it, especially since that would be an act that's in direct conflics with, well,<br /> a lot of teachings (being specific to Christians here, really).</p> <p> Dawkins did not talk about Jesus much at all - probably because this blows his<br /> whole argument of violence and religion out the window. Jesus one act that could<br /> be considered violence is the temple/whip scene. There's no evidence the whip<br /> was used against anyone (which doesn't mean that it wasn't - just that we can't<br /> say whether it was or not), and he even sat down and made the whip before using it.<br /> Hardly a rash decision.</p> <p> Being that Jesus is God in the flesh, we can look at his violence free life and<br /> see that violence is not God's nature. (caution: This is me now, not Alister)<br /> I can tell the atheists are screaming<br /> "what about the Old Testament?" and, yes, He's quite violent in that, but rightly<br /> so. The (OT) dialogue was something like this:</p> <p> <strong>God</strong>: Keep these commands, or these things will happen to you<br /> <strong>People</strong>: Okie, sounds like a deal.<br /> <strong><i>People go off and disagree</i></strong><br /> <strong>God</strong>: RARGH, I'M GOING TO KEEP MY END OF THE BARGAIN!<br /> <i>Fast forward a few thousand year</i><br /> <strong>People</strong>: Look! God is EVIL!</p> <p> (ok, back to the actual lecture now :p)</p> <p> And just a quick thought provoker: If all traces of religion were to disappear<br /> forever tomorrow, would that be the end of violence?</p> <p> Moving on, Dawkins states that, in general, religion is a bad thing. It<br /> has nothing but negative effects on many parts of people's life, such as<br /> self esteem, career progression, family life, etc. I don't know how far he<br /> gets into it, but the impression I get is that it has a negative impact on<br /> ALL facets of life.</p> <p> Alister first pointed out (and this is obvious) that pathalogical religions aren't<br /> the norm. That is, religions that have a purpose to 'hurt' themselves or<br /> others.</p> <p> But what about the effect that non-pathalogical religions have? A study was conducted<br /> (again, sorry, no sources), which drew upon 100 other studies for its result.<br /> The results were intriguing:</p> <ul> <li>79 claimed that religion has a positive effect on people</li> <li>20 claimed that religion has a mixed, or an uncertain effect on people</li> <li>1 claimed that religion has a negative effect on people</li> </ul> <p> Now, I'm not sure what exactly was being tested, but very very interesting<br /> results. If Dawkins' statement was true, these numbers would be inverted.</p> <h2>Conclusion</h2> <p> It was concluded that Dawkins, in this book, cherry picked many examples to<br /> prove his point. I'm uncertain at the moment whether the same can be said<br /> about the lecturer's choice of examples - they were probably cherry picked<br /> too. Hard to remove bias, I guess.</p> <p> Also, there wasn't much in the book on positiveness of atheism. Only negatives<br /> of religion. It's not exactly fair to downtrod other belief systems if the<br /> same flaws exist in atheism, and even if they don't, there's plenty of merit<br /> in religion which was nicely avoided.</p> <p> Thirdly, it was stated that the majority of the book was criticised by atheists,<br /> more so than by theists. So The God Delusion probably shouldn't be taken<br /> seriously as the 'norm' thinking of atheists. Atheists are probably more reasonable<br /> thank Dawkins.</p> <p> Finally, Alister shed some insight into Atheism. Although it has always been around,<br /> it's only recently that its flared up. The reason for this is that Atheism<br /> gains popularity when it criticises unpopular beliefs. I suppose "The God Delusion"<br /> is proof of this?</p> Sat, 06 Oct 2007 00:00:00 +0000 http://stevesspace.com/2007/10/the-bankruptcy-of-atheism/ http://stevesspace.com/2007/10/the-bankruptcy-of-atheism/ Trinitarian talk with a JW <p>A prelude to this story: A few days ago I was thinking "y'know, I can't wait for a JW to talk to me, it'll be a good opportunity to spread the true gospel to someone (despite the brain washing)". Now, onto the story</p> <!--break--> <p>I was waiting for the train today, and a JW wanted to give me a copy of the "Awake" magazine for reading on the train. Conversation started off like: <p><strong>JW</strong>: Would you like something to read on the train?<br /><strong>Me</strong>: What's it about<br /><strong>JW</strong>: [reading the cover] How to live longer<br /><strong>Me</strong>: Oh, no thanks, I'll be living eternally - I've found Jesus<br /><strong>JW</strong>: It might be an interesting read anyway<br /><strong>Me</strong>: How about you talk to me about it instead of giving me a book to read?</p> <p>So he leans agains the rail thingy with me and we discussed Jesus. He asked me if I thought Jesus was God, and I replied yes, along with the Father and the Holy Spirit, so he pulled out his bible (and I think it caught him off guard a bit when I pulled mine out too, ah the simple joys of having a small bible). He quoted colossians:<br /> <blockquote><strong>Colossians 1:15: </strong> The Son is the image of the invisible God, the firstborn over all creation</p></blockquote> <p>Which he reasoned that since He was born, He must have had a creator who is God. I said that's flawed reasoning, then said what I realised myself was also flawed reasoning (that it said "over all creation" not "of all creation" - yeah, it was odd...), and then went on to the more doctrinely sound reasoning that Jesus is the image of God, which means He is also God. Much in the same way that my physical appearence is the image of myself, and my soul is also myself - two parts of the one me. I think he misunderstood that and said that we're different people (me and him...)</p> <p>Now I was having a major mind blank, which was unfortunate timing, and with a quick prayer, a verse jumped into my head.<br /> <blockquote><strong>John 1:1: </strong>In the beginning was the Word, and the Word was with God, and the Word was God</p></blockquote> <p>I asked him who or what the Word was, and he answered correctly, Jesus. I focused on the last part - the Word was God, he focused on the middle part - the Word was with God. I tried to point out that both can be happening at the same time, which I found obvious, but he couldn't get his head around easily, and went somewhere else in the bible.</p> <p>I can't remember exactly where he went, but it was a part where either Jesus said the Father sent him, or Paul said the Father sent Jesus, and went on to say that if He was sent, someone else must have done the sending. I tried to explain a bit of the trinity to him, and said that although Jesus is not the Father, the father is not the Spirit, and the Spirit is not Jesus, but they are all God. Much like the <a href="http://en.wikipedia.org/wiki/Image:Shield-Trinity-Scutum-Fidei-English.png" target="_blank">wikipedia picture</a>. I wasn't explaining well, unfortunately, and I had completely run dry on other Bible references to back myself up.</p> <p>But anyway, in a short amount of time my train came, I asked if he was catching it and wanted to get on, and he said he's not catching that train, and that was the end of that pretty much. I told him I'd be praying for him (which I have been)</p> <p>Once the train started going, I got a whole lot of ideas of places I could have went, and kicked myself for not remembering them. The obvious one was Jesus claimed it a few times, most notably when He was about to be crucified. Also, I could have angled it "Who can forgive sins? God...*flick to OT*. Who did forgive sins? Jesus. *flick to NT*".</p> <p>But I didn't think of any of these while I was talking to him, despite praying earnestly for some help there. So two questions:<br />1) How do you think of on-the-spot scriptural evidence for these kinds of debates? I usually remember the general theme, and google it<br />2) Why weren't my prayers answered straight away? It seems they would have been more useful then, than when I was on the train on my own.</p> Fri, 01 Jun 2007 00:00:00 +0000 http://stevesspace.com/2007/06/trinitarian-talk-with-a-jw/ http://stevesspace.com/2007/06/trinitarian-talk-with-a-jw/ The Law <p>People in the car on the way to SNL today need not worry about reading any more :) But for those who are still with me...</p> <!--break--> <p>I was reading the glossary in the back of my bible yesterday, and I came across "cornerstone": Either the bottom corner of a building (a foundation stone) or the keystone of an archway (Mark 12:10). It can cause a person to stuble (1 Peter 2:8) or it can fall on someone (Matt 21:44).</p> <p>For completeness:<br /> <b>1 Peter 2:8</b> - A stone that causes men to stumble and a rock that makes them fall."<u>[a]</u> They stumble because they disobey the message — which is also what they were destined for.<br /> <b><u>[a]</u> Isaiah 8:14</b> - and he will be a sanctuary;<br /> but for both houses of Israel he will be<br /> a stone that causes men to stumble<br /> and a rock that makes them fall.<br /> And for the people of Jerusalem he will be<br /> a trap and a snare.<br /> <b>Matt 21:44</b> - He who falls on this stone will be broken to pieces, but he on whom it falls will be crushed.</p> <p>So, that's the bible verses out of the way =D It's strange on how many people depict a cornerstone as a very <a href="http://www.christianlyricsonline.com/artists/day-of-fire/cornerstone.html">positive thing</a> - interpretting the cornerstone as the essential building block, something everyone can lean on - the solid rock that is Jesus.</p> <p>But I'm looking at these verses, and it depicts the cornerstone as being very...well...wrathful. It really jumps out at me how much Jesus is the Judge and a physical embodiment of the Law. And like <a href="http://www.biblegateway.com/passage/?search=Romans%207:7-10;&version=31;">Paul</a> <a href="http://www.biblegateway.com/passage/?search=Romans%202:17-24;&version=31;">points</a> <a href="http://www.biblegateway.com/passage/?search=Romans%203:19-20;&version=31;">out</a> several times in Romans (and there's more, trust me :p I just had a search), it's only because of the Law that we can be called sinners. If there was no definition of right, how can we be called wrong?</p> <p>So there...next time you hear people singing about Jesus being our cornerstone: remember that by being our cornerstone, Jesus is pointing out to all of us that we fall short of His glory, and don't hold to the law. But because of that, we will cling to him even harder, because we know how much we need Him.</p> <p>That being put aside, I have a rant: Christians - don't break the law when you drive! Moreso than any other, we need to be upright and faultless when being judged by the world. Read <a href="http://www.biblegateway.com/passage/?search=Romans%2013;&version=31;">Romans 13</a> if you don't believe me. Jim (from Student Life) said how a bunch of policeman would always wait at a particular road during Christian camps, and book them all for speeding. BE BLAMELESS! We're told we should be persecuted, but not for driving. Only for our faith. Please drive to the law!</p> Sun, 06 May 2007 00:00:00 +0000 http://stevesspace.com/2007/05/the-law/ http://stevesspace.com/2007/05/the-law/ Soul Acceptance of God <p>I came across this post on <strike>GodGab</strike> (Note: site no longer exists) when I should have been working, and thought I would share it.</p> <!--break--> <blockquote><p>The pedant is disposed to be skeptical by reason of a lurking belief that it is the look-out of the Divinity to make Himself known to him. But the jeeva (soul) is a dissociable particle of the divine essence with the onus of choosing for himself from among the alternatives of serving, a neutral and a disobedient career, his own relationship to the Divinity. He cannot escape the privilege of exercising this responsibility except by conscious self-deception or by hyporcisy.</p></blockquote> <p>Translated into a language we can understand (sorry, but that is not english), it basically means that a pedant is someone who thinks it is up to God to prove Himself to us. However, our soul can choose 1 of 3 ways to act towards God - servitude, neutrality, or rejection/disobedience. To think otherwise is either hypocritical or self deceiving.</p> <p>I think it also assumes that the soul exists...who knows. Stupid Existential English!</p> Tue, 17 Apr 2007 00:00:00 +0000 http://stevesspace.com/2007/04/soul-acceptance-of-god/ http://stevesspace.com/2007/04/soul-acceptance-of-god/ Christians: What I don't like <p>I just finished reading this book called "More than a Carpenter", which is a little book that basically confirms Jesus. I'm not sure if it was related, but as soon as I finished it, I realised there was a lot of things that I plain didn't like that a lot of people attribute to Christianity... I felt like writing a little book of my own about it - I figure a blog post is good enough.</p> <!--break--> <p><strong>"I'm not religious - I'm relational"</strong><br/>A lot of people say that when explaining their Christianity, I've heard it so often I could puke. They think that because Christianity is about a relationship with Jesus, it's not a religion, it's a relationship.</p> <p><a href="http://www.google.com.au/search?q=define%3Areligion">google define:religion</a>.</p> <p>I'm sorry, but a religion is a system of beliefs. If you don't have a religion, you don't have a belief - you don't believe in Jesus. Clearly, if you're Christian, you're religious. Deal with it.</p> <p><strong>Jesus called us to love, not like</strong><br/>I've also heard often that we can love people without necessarilly liking them... that line of thought is beyond comprehension. Let's check it out <a href="http://www.biblegateway.com/passage/?search=1%20cor%2013:4-7;&version=31;">here</a>.</p> <p>That link contains a basic essence of what love is about (I'm not sure if it's comprehensive or not, somehow I doubt it). Try being <b>all</b> of them to someone you don't like. Let's see protect and completely and utterly trust someone that you just plain don't like or get along with. Sorry, not gonna happen.</p> <p>If Jesus has called us to love Him, which He has, then we obviously have to like Him. Same with our neighbours, we need to love them, because God told us too, which means we have to aspire to like them. Accept their annoyances, and personalities that may clash with yours. Love is not easily angered or self serving, so be selfless and put up with it.</p> <p>It's important to realise you don't have to like what they do, though - for example, I abhor that I have friends and family that have sex before marriage, it's disgusting, wrong, and I really <i>really</i> don't like that. But I love and like them, which is why I try and correct that, in love.</p> <p>Yes, I realise the irony that this bit is about what I don't like... heheh</p> <p><strong>Buzzwords</strong><br />'nuff said =P</p> <p><strong>Quirky, cute sayings about God</strong><br />Sometimes I wonder if I'm following the same God as others.</p> <p><i>"Jesus is my best friend"</i> - really? What's His favourite food? Does He like to go bowling?. No, I'm sorry, the relationship we have with Jesus is not one of best friend. It's one of servitude on our part. It's a Father/Son (or daughter) thing. I mean, let's be serious, this is <i>God</i>. He created us, this earth, the entire universe - nothing we do cannot be done without His permission in doing it. Please, correct me if I'm wrong here.</p> <p>"God, you're so cute!" - I have actually heard people say this, or something similar, usually in response to some event attributed to God, like a nice divine appointment. I just think, "Really? God (again, creator of everything), who destroyed entire cities in justful wrath, is cute? God, who came in human form, to guide us all, and ultimately be tortured and killed in order to take our sins, is <i>cute</i>?" I know I'd be insulted. I have no idea whether God is or not - it's just not something I would attribute to His character.</p> <p>And so on...</p> <p><strong>Mission</strong><br />Don't get me wrong - I understand the importance of mission, but when I hear that a group of people spend $3000 each to go on mission for a couple of weeks, and they talk about shopping, culture and etc. more than they talk about their spiritual conversations, salvations and personal growth type testimonies, I begin to wonder.</p> <p>It's especially annoying when these people would be more effective at home, if they were focused on doing the same thing, commited to doing the same thing. A Japan trip recently saw 3 people turn to Christ, which is awesome. However, when I see something amazing like Schoolies (where there can never be enough helpers and support), having a dozen salvations in one night - at less than 10% of the cost, I begin to wonder.</p> <p>This is an area of my own personal growth as well, but I honestly thing we need to treat our whole lives as mission, not just the weeks we go on mission. Look at the apostles in acts...wow...imagine if we were like that in our own communities.</p> <p>Not to mention that God has placed us right where we are...what better place to sow?</p> <p><strong>Statistics</strong><br />Now, to be hypocritical, since I quoted salvation numbers in the previous post, I hate that :p. It's not a big thing that happens, but I know it does (and I think I worry about it too much as well). The fact is, David got in big trouble for doing a Census, for counting things, and relying on that strength instead of God's. Numbers don't matter - faith in God does.</p> <p><strong>Work</strong><br />It's often emphasised by missionaries that work as a missionary is God's calling for most people - only a few have need for secular work, and we'd all serve God best through missionary type work.</p> <p>A few things I see wrong with that: <ul> <li>Who will financially support missionaies in the event that everyone is a missionary?</li> <li>What's more effective: established relationships with non christians daily (secular work), or random talking to strangers and hoping that it becomes spiritual</li> <li>The 5 parts of a church defined in...ephesians I think? Preacher, Teacher, Evangelist, Apostle, Prophet. Only 1/5 of those is missionary work - apostle. Though I suppose that's open for debate, they could all be considered missionary stuff...</li> </ul> <p>Don't get me wrong here - I think missionaries do an excellent, and necessary job, and it's great that they've found their calling in it. Seriously, keep up the awesome work. But it's not for everyone, and don't try to say it is.</p> <p><strong>It's Christian - it must be good!</strong><br />Rap will never be good, even if it is Christian. Nor will pop music. Or country music. I don't like the book 'Wild at heart' very much at all.</p> <p>Just because it's something Christian, doesn't mean I have to enjoy it. Or be particularly interested in it. It's a big field, I can't like it all.</p> <p>Also, closely related, is Christian obligations - like "outreach (like, planned 1hr/week type stuff) is something Christians should do" - nooo, it's not, and just because it's Christian, doesn't mean I'm obligated to partake. Think of the repurcusions involved if everyone did everything that Christians can do? We'd be working 6000 hr days.</p> <p><strong>Blind Faith</strong><br />I'm not sure if this is just me and something I need to work on, other people and something they need to work on, or difference of opinion that's fairly trivial and doesn't matter either way (Faith isn't a big spiritual gift for me), but if I'm hoping that a traffic light will stay green so I don't have to do a hill start, and that traffic light stays green, I don't attribute that to God's divine intervention.</p> <p>I think this is just a faith thing - but I can't attribute something that was caused by the timing of computers. The would have behaved as they did, regardless of your prayer. I don't doubt that God <i>could</i> make those lights change. I just doubt that they're in His will to change, and would wonder his motives for doing so.</p> <p>So, that's about it that I can think of now :p I hope you enjoyed the read and it helped in some way. Considering the extensiveness of Christianity, and the relative shortness of this list, be comforted that I love Christianity, and Jesus, very much! Not to mention all you guys and gals ;-)</p> <p>May God bless you all.</p> Mon, 12 Mar 2007 00:00:00 +0000 http://stevesspace.com/2007/03/christians-what-i-dont-like/ http://stevesspace.com/2007/03/christians-what-i-dont-like/ Divine Appointment <p>"Divine Appointment" was a term introduced to me by the people down at schoolies. It's basically a word to explain those 'chance' encounters that you get which are so obviously set up by God, hence, a divine appointment (God being divine, the meeting being an appointment...yeah, I'm sure you geddit.)</p> <!--break--> <p>So anyway, I post about this because the past week or so has been divine appointmenterific for me. Y'see, I lost my old web job at the end of last year, and was looking for a job at the start of this week. I asked Paula to pray for my job findingness, and I fired up seek.com and started clicking on tonnes of jobs and applying for them.</p> <p>None of those seek jobs returned anything fruitful *sniff* BUT randomly, while Paula was checking her gmail, Kevin (a guy who we've only met twice... and not really talked to a whole heap) ended up talking to Paula on the gmail-chat thing. They talked about work for a while, Paula mentioned that I was jobless...</p> <p>Luckily, I'd sent Paula a copy of my resume the day before, because Kevin asked her to send him one, and he'll see if I can get a job where he works. Long story short, I'm going in at 2pm today to suss it out, while they'll suss me out.</p> <p>There was too many coincidences here to consider it as anything else BUT a divine appointment. The prayer of Paula's was happening not long before the gmail encounter. I'd just sent her my resume the day before. Kevin and Paula just happened to be on gmail at the same time, and Kevin initiated conversation that Paula wasn't even looking for. There just happened to be a job going there, and it happened to be in a language that I'm quite familiar with, having done this very website in it.</p> <p>Interesting side note - the office is located in the same place as the old Uptime Southbank store. Dunno if that's a divine appointment or not, but it's very cool either way.</p> <p>I've also made a new year's resolution to read the bible daily, and going well with it (though have spent a couple of late nights already due to forgetfulness). Maybe this was God's way of rewarding me, making things super easy? I can't begin to guess His reasons, though I'd imagine they have something to do with Love, and I'm grateful for it either way</p> <p>Also, we went to SNL yesterday. I'd decided I was going to see if they wanted their website done up. It apparently got hacked a while back and hasn't been the same since. want proof? See <strike>their website</strike> (Note: site no longer exists. see <a href="http://oslc.org.au">Our Saviour Lutheran Church</a>). Anyhoo, we got to SNL about 15mins early, and the pastor was out the front talking to people.</p> <p>I thought again almost immediately "wow, another divine appointment", it was a perfect opportunity to ask him about the website stuff, and when I do, he was saying they were just talking about looking for someone to do up their website.</p> <p>Wow, God, you're awesome.</p> Sun, 07 Jan 2007 00:00:00 +0000 http://stevesspace.com/2007/01/divine-appointment/ http://stevesspace.com/2007/01/divine-appointment/ Schoolies 06 Part 2 <p>It's now later!</p> <p>I forgot to mention that I asked Lauren about praying in tongues on this night. Previously, I'd read that linguists had done study on a recording of it, and it seemed human made, because it's simply not diverse enough to be a real language. It's full of repetitiveness and it's basically not random enough. I didn't tell this to Lauren when I talked to her, though</p> <!--break--> <p>So anyway, she explained that speaking in tongues is just something that... happens, essentially. While she's doing it, she has no idea what she's saying, and it requires no brain work to keep it going (because it's coming from the soul, not the mind). This means that while you're praying in tongues, you can think your own prayers, or read a book (as Anna said she did once), or whatever – it's completely disjoint from thinking (though I'd imagine it can still get distracting).</p> <p>Also, I believe that the reason these linguists found it repetitive was because the main 'topic' of praying in tongues is praise to God. That's my own opinion though. Oh, the other thing about praying in tongues was because it comes from the soul/spirit, it is completely in line with God's Will. And we all know what that means (or for those who don't; if we pray for something, and it's in line with God's will, we can be certain it will be answered).</p> <p>Some more spiritual stuff that's been happening over the past few days was, like, the team was 'seeing', or noticing things that I simply wasn't. Hard to explain this one... Had to be there I guess. I don't know why I mentioned it. But it made me wonder if I needed to add more spiritualism to my faith. I'd always accepted spiritual things to be real, but never on a very personal level...</p> <p><b>Tuesday, 28th November</b></p> <p>The sun up, we went to bed (tee hee), woke up late as always. Today was a pretty good resting day for us – well earned too, methinks. We decided not to take any pancake orders or anything today to avoid being run into the ground, so essentially we stayed at home and rested for a while. We had a BBQ to go to after dinner, but nothing before that.</p> <p>Before we went to dinner, we went and did something (wish I could remember what), and Lauren wanted to go shopping. In typical Red Frog nature, with the whole 'staying together' thing, it was a good idea to have someone with her, and so I got that ... hate to say it ... task. Little did I know Lauren was so big on shopping, we spent quite a bit of time in a shop. I did get a dress shirt for $10 out of it though, so it wasn't completely in vain.</p> <p>We went to dinner, then got our meat and headed to join the BBQ down below. There was a lot more people at this one, but it wasn't all that interesting =P. At one stage Lauren was talking to a girl at one of the tables on their own, while everyone else was on their own. I'm not sure exactly what they were talking about, but Lauren got a good vibe from it, so it can't have been all bad ;-)</p> <p>We headed to the hotel, it was a fairly crazy night. Room 16B was playing this drinking game 100 shots in 100 mins. They were shots of mixed drinks, so it wasn't as bad as it sounds, but there were tonnes and tonnes of drunk people there. The guy whose name the room was out under didn't want us there (presumably because of drugs and stuff that were in there), though everyone else did. We didn't go in, and everyone in the team felt a big 'spiritual attack' (mostly Lauren), that I did not.</p> <p>So we went back to the apartment. I'm sitting there, fully fine and ok, wondering why on earth we're back here. I have no idea what Lauren was feeling, but she felt attacked. We stayed there for like an hour. I was still getting annoyed and frustrated at our reason for still being back there. I was of the opinion that even if Satan was attacking us, he can't do squat anyway, while we've got Jesus and the Holy Spirit on our side. But... y'know, I'm not pentecostal so I don't understand these things. We prayed for Lauren, sat around for a little while longer, before heading out again. There was a <i>lot</i> of “going back to the apartment to recharge” that night.</p> <p>We went up to room 15B after that, had a little talk to them...was pretty cool but my notes have not much to say about it, and I can't remember much. doh!</p> <p>We cooked some pancakes for our guards and brought it out to them, they appreciated it. It's amazing to think that all the stuff that we get into is just a very small part – there's tonnes of workers out on the beach, Cavill ave, security for each hotel, ambulance, police... We were small fry in all the turmoil, so we thought we'd better look out for our guards and keep them refreshed as much as possible. We gave red frogs to any police or anything we ran into, if they wanted it, as well.</p> <p>Anyway, we went up to 31A, whom we were beginning to come quite close to by now. I brought my camera up to get some night shots of surfers, hopefully I'll get them uploaded soon. We were mainly building relationships for this one, and we did that quite well. 31D were the kind of people that would stay at home and 'chill', rather than going out and partying like mad.</p> <p>We went back to our apartment after that (we'd done several other times but I didn't mention that), for a bit of a recoup before leaving just after 3 to catch the people coming back from the clubs and stuff. It was fairly dead then though, we spent a lot of the time talking to the guards of the hotel across the road, handed out the red frog cards to them and a few people that were coming back. We slept at about 4 that night.</p> <p>Oh, we were planning on having a 'pancake party' at 1 o'clock at the BBQ on Wednesday, so we let people know about that tonight too. Most people were fairly interested. There was a few musicians about the place, so we'd organised 2 guitars and my harmonica to be down there too. w00t!</p> <p><b>Wednesday, 29th November</b></p> <p> Well, we slept until about midday today, and we had our pancake party at 1, giving us time to spend some time with God. We headed down with a few bowls full of pancake mix and whatever else was needed to make pancakes. A lot of the people form the hotel said they'd be coming, so I was looking forward to a good turnout.</p> <p>Ahh, expectations, we were the only ones at the BBQ when we arrived... And we were 15mins late! So it didn't look like we were going to get a very big turnout after all. Started cooking the pancakes anyway (in case the smell attracted people I guess), and started eating the pancakes too. The girls from our floor (2A and 2D) eventually made their way down, which was good. They didn't know each other or anything before our little pancake party, but after the party, they were more 'united', which was good.</p> <p>The musical inclined people came along later on as well – and there was a bit of jamming going on with a guitar and Brendon on my harmonica. I wasn't brave enough to jam with them, as my harmonica skills were dodgy at best. They had fun though!</p> <p>After the pancake party subsided, we went back to our room for the little time we had before the daily red frog meeting and dinner. Went back to our apartment, prayed and all that jazz before heading out for another rocketing night.</p> <p>Our first stop was up to the top floor. We liked going up there, because the guys in that room were 'chill out' sort of people instead of crazy drunks like the 6th floor boys. That, and the view was awesome. We were there for a few hours actually – and there was some AFL player guy who was going around with the red frogs for the night as well...but more on that later.</p> <p>The Holy Spirit was at work quite awesome for this night (particularly this room). Anna had gotten into a conversation with one guy, near the TV, about scripture and Jesus (I wasn't part of the conversation so I can't be more specific than that). Brendo and I talked to a guy (who, might I add, is an AWESOME guitar player) about Jesus and some religion stuff as well, fairly spiritual if I recall correctly (that's a big if). Lauren was also talking to another guy, though I have no idea what specifically about. We had these 'surfer bibles' which was basically a new testament, with a bunch of testimonies from Christian Pro Surfers. So we pulled one of those out, and it was really awesome to see a stack of people crowding around a bible wanting to read it (they were mainly reading the testimonies). We handed the 2nd one out so more could read it.</p> <p>These guys were big AFL nutters, too, and we requested that the AFL guy make an appearance here for these guys. So in not long, Anna and Brendo went down to fetch them and came back. When they walked in the room, there was basically an amazing air of power among them all. First there was Andy, the founder and organiser of the Red Frog organisation. Following him was the retired pro AFL player and (I think) his father (or someone's father). Then there was Mark, the organiser of the 2nd week, who was there to take photos basically. Then there was a guy who had been leading a lot o the prayers and doing organising and stuff too (not sure on his name). The air around them when they walked in was one of command, almost as if they were claiming the apartment in Jesus' name.</p> <p>Essentially, everyone broke up into groups and talked. I was kinda nowhere in all of this, so I talked to some of the red frogs that were also nowhere, and also prayed. I can't really say what the effectiveness of the guys that came in were or weren't, because I simply wasn't part of the conversation. But there was good vibes, lotsa pictures taken, so I suppose it was a good thing.</p> <p>We headed off from there and went across the road to the next hotel. We only ended up going to one room, and the room could probably described in 3 words: Sex, Drugs, Alcohol. The room was fairly dark (as in, no lights were on, and there was a lot of confusion as to who we actually were at first, but we sorted that out, gave our cards with the red frog support number on it, and got to talking. Some of the had heard of us, and that we were Christian, so we talked about that for a little while, what they'd done at school and everything. It wasn't too bad, but the other people in the team got a bad vibe of the place, and the conversations weren't going anywhere, so we left. I thought it went well, the fact that Jesus was brought up and talked about was very positive for me, but the general feel of the place wasn't.</p> <p>I'm not sure where this bit slots in, but tonight I decided to make a midnight dinner for the team (we'd often go back to eat something at around midnight, but never actually have dinner). Lauren was leaving tonight, so it was a good time for a last complete-team get together. I made oyakudon, it turned out pretty bad, but meh. We headed to 6th floor.</p> <p>Yeah, we went to 6th floor. Whoa, they were quite drunk indeed =P. We were hanging around in 6C for a while, one of the guys (the drunkest one) looked like he was gonna pass out sometime soon, so we thought we'd escort him up to his room (7th floor) to make sure he got there ok. That was an adventure, because we had to end up taking him up on a mattress... it was weird. Anyway, we got him there, he was lying down so we went back to the room and talked. Anna was talking to one of the guys there, I'm not totally sure what about, but it was something good hehe. Not long after, the guy we escorted upstairs had decided to come back down and got tonnes of yays and wahoos. It was fairly funny :p</p> <p>Another guy (who had been drunk since like Monday) went to the other apartment for a reason which I can't remember now. It came back to us that he suffered a fairly severe nose bleed. From what we've been able to gather, one of two things happen. One: His nose just started bleeding randomly. Two: His friends said he does this thing fairly frequently where he pulls a door towards himself, makes it hit his foot or knee really hard, and pretend it hit his face. So it could have been this, but missing his foot or knee in his drunken state and smashing it into his face. We went up to check him out, and got him to do things properly (as in, don't run water over it, pinch the top bit, etc.). He was ok, but man the bathroom looked like a werewolf had been set loose in it. One of the guys had cut his hair in there the day before, and hadn't cleaned up the hair. The blood had gone everywhere too, so there was hair and blood and everything everywhere, it was insane. The boys said they'd clean it up so we left it at that. (Yes, this story continues in 2 days time)</p> <p>We went to sleep after probably our most intense night thus far, nice farewell for Lauren.</p> <p><b>Thursday, 29th November</b></p> <p>We woke up and had a call to help clean up a room at a hotel about a block to the south. Note I said help clean a room, not clean it for em =P we're careful about that sort of thing. We got it cleaned up fairly quickly in the end, considering... They had made 'vodka jelly', which had gone everywhere over the floor. There was also some meatball sauce or something like that on the window (which we didn't do). The funny thing about this place is it was right next to the Q1. So from the balcony you could look into the enormous pool, and see the size of the most prestige hotel there. It's a bit of a slap in the face really. There was a bit of Jesus talk happening here, which was good. They also had a huge beer wall there... Hopefully I'll get pictures up for you to see.</p> <p>We'd been told during the Red Frog meeting that there was going to be a Youth service at the local church on Friday Night, and that we should let it be known to our friends in the hotel. Out first stop that night was the girls next door, and we basically went there for pancakes and played cards with them. It was a pretty good time. We invited them to the youth service, they said they were going to dreamworld the next day, but they'd come to the Youth service if they got back in time. Played some more cards, and we moved on.</p> <p>Up to 14B, another few girls (we only went to see guys yesterday, so it was about time we visited some of the female variety. I didn't mention Josie earlier (whoops). Anna's been talking to Josie for a little while now, about some fairly deep stuff, and this was where she was staying. She said she'd definitely be there, which was awesome =D. Some guys showed up in the room too, wanted their cd player (lol), we invited them too, and they were enthusiastic. It looked as though we were making an impression among these kids, which is awesome.</p> <p>We ran into 14A on the way down to ground, they booked us in for some pancakes tomorrow night</p> <p>Went down to the ground and talked to Andre and Bill (hotel security guards). Andre's a champ, I'll get a picture of him up. His hair is awesome. We gave him the gospel of Mark the day before, and he's been reading it – up to book 4 (where Jesus gets tempted for quite some time). We talked for a while about that. His impression of God was that yes, He created this world, but He doesn't influence it anymore – prefers to leave us to our own matters, sort of thing. We talked to him for a little while about that. Nothing much more eventful came out of that night, so we went to bed. Tomorrow would be our last day.</p> <p><b>Friday, 1st December</b></p> <p>Woke up, realised it was the first of December, so I gave Brendo a pinch and a punch. I don't think he was expecting it. Said it was a good punch too. Woo. By now, Brendon, Anna and I were the only ones left.</p> <p>During the day, we went up to our 31st floor boys to say g'day. We just chilled with them for a little while, but not a whole lot interesting happened. We left after an hour or so, before going back to 14th for their pancakes.</p> <p>We started with the pancakes, Anna cooking while Brendo and I talked to them. Talked and ate for a while, I think we went through 2 or 3 thingies of pancakes. I was quite full, as was everyone else there. So much for dinner later. Brendo left to go to the leader's meeting (he was acting leader since Lauren left), Anna and I stuck around to talk to them. Anna talked about her experiences with drugs, and how they leave you with the constant search for the 'high', but always have a down afterwards. She basically shared her testimony with them, which was awesome. She said God gave her a constant high without the need for the drugs or anything. They seemed to be taking in every word, but weren't really interested in the Youth service tonight.</p> <p>We went to church, as it turns out, only Josie wanted to come, but that was ok. The band was rocking, thought hey only played 3 songs (and repeated 'One Way' twice). It was awesome worship though, I was buzzing afterwards. The sermon was fairly straight forward, and kinda expected really. The pastor talked about his past, which included Sex, drugs, and all that stuff too, and related to what the schoolies would be going through at the time (a week of drinking, the low at the end of it and not feeling that great). He told them the Gospel, and got everyone to close their eyes and raise their hand if they wanted to respond. I don't know how many people raised their hands :p</p> <p>Anna and I walked Josie back, and talked to her and see what she thought of it all. Anna and Josie had gotten really close, and were definitely going to keep in touch. I think Josie was going to look more into going to church, which was an awesome thing. Her mother was Christian, but I don't think her mother's church really suited her. So that was really awesome ! </p> <p>Going back to the hotel, we ran into a girl (who was 20) who had her ID confiscated because they thought it was a fake. It was damaged, bot not a fake. We went to the club with her, and sorted the misunderstanding out with the bouncer. He was a bit of a jerk really, but I'd imagine these were stressful times for him so it's understandable I guess.</p> <p>Back at the hotel, we ran into Eva in the elevator. She was a resident of this Hotel (not just on holiday), and about 30 years old I'd imagine. She had a child, and was currently going through divorce settlements with her ex-husband. The child's father had not paid any child support for a <i>long</i> time, which is what the court proceedings was about. Also, she had been done for drink driving earlier in the week, and was in danger of losing custody of her child. We comforted for a while on this, she was obviously distraught (and a little drunk...). I think we made her feel a lot better just by being there and talking to her.</p> <p>She asked us a lot about why we were doing what we do, and obviously Jesus' name was dropped :p. She was raised up Catholic, and still sort-of believed it. She also believed that God reveals Himself in different ways to different religions. We had the longest discussion about that sort of thing, and each of us shared our testimonies with her. I'd been carrying a tract in my wallet for almost a year now (called “Knowing God Personally”), which I felt strangely compel to give to her, so I did. We encouraged her to pray along with all the organising she had to do with the court cases, and to do it with a clearer mind (ie. Don't drink while going through court settlements...which is sensible). We prayed with her there, and she prayed too. She was polish, and Brendon said to her “You're polish, you have your own language, I'd like to pray for you in mine.” And so, started praying in tongues for her. It sounded slightly different to usual, but like always, he had no idea what he was praying about. Eva felt compelled to give each of us something. Brendo got a picture of the Virgin Mary... I can't remember what Anna got, and I got an old Bracelet of Eva's that has her name on it. I've got it on my doorknob now, and pray for her whenever I see it, which is fairly frequently. I'll keep it up for a year.</p> <p>Our adventures didn't stop there (we were there for more than an hour, i'd imagine), we went back to our apartment to recharge for a little while, as we were quite drained. Andre called us after a little while, said some people called him for needing help with a guy who had drunk way too much. We went to the ground, got details, and headed up to the 6th floor.</p> <p>So we get there and see Rhys sitting in the bath, deathly white. With a tinge of green. He actually looked dead, but he was conscious. After some debate (with his friends), we called the ambulance, as he quite seriously looked like he needed a stomach pump due to alcohol poisoning. Anna pretty much took charge here, she'd seem to have dealt with this sort of stuff before, and was putting the cold shower on him and all that. We kept talking to him to keep him from passing out, he wasn't looking much better, but still talking. Drunk people aren't that interesting to talk to, but he had a very loving heart. Kept talking about how he had the best brother in the world, and how his best friend couldn't stay at schoolies for long because he had epilepsy.</p> <p>Andre came up to check it out after a little while, and said the last time he'd seen someone who looked like Rhys did (except Rhys looked worse), he died that night, which redoubled our prayer for him. This was also the werewolf bathroom, with the blood and hair... It was crazy. Lucky we bought some cleaning stuff with us, and did that while we kept Rhys conscious talking. Anna did an awesome job cleaning the bathroom while I talked to Rhys.</p> <p>After time passed, much prayer had been done, and there was still no sign of the ambulance. But Rhys finally started to look a bit better, and managed to move himself over to the other side of the bath, where there was a ledge to sit on. We gave him a towel and he started to warm up some, colour returning to him. He was still drunk as anything, but looked a little better, not quite so white/green.</p> <p>It was fairly sudden... he seemed to just 'get better' really quickly. We cancelled the ambulance, as Rhys was now standing up, still drunk as hell, but ok. He just noticed the bathroom was clean and thanked us heaps. He stood up, even, which was surprising. We kept him in there for a while, till we were sure he wouldn't just fall over after starting to walk. Basically, he was drunk as all buggery, but he was ok.</p> <p>Anna and Brendo went to the loungeroom, Brendo jamming on the guitar, and I stayed in the bathroom with Rhys. After not long, he decided he wanted to go out there, and he seemed ok, so I let him go (but stayed close just in case). Then he wanted to go to the beach. One thing about drunk people (well, nice drunk people, not violent drunk people), you can basically make them do what you want, despite their objections. He went out to the elevator, and pushed the down button, but we're like “no, you're not going there”. Eventually we pretty much just pushed him back into his room (not violently, more guiding type pushes) and he obliged. Things were ok then, so we let them go.</p> <p>I honestly believe we saved this guy's life – I won't ever know for sure if we did or not, but it's a fairly big possibility. His sudden sobering up, I attributed to God answering our prayers, and things turned out fairly well. I hope the ambulance that didn't come was able to help others, with that long of a wait, there must have been a lot going on everywhere else as well.</p> <p>And that was the end of schoolies. We went to bed, woke up the next day, had breakfast (I had 2 breakfasts, which was 4 eggs, 4 bits of toast, and tonnes of bacon. Man I was full. Caught the bus our own seperate ways, as Brendo and Anna were meeting a friend down south. I caught up with them on Monday for some Belgium Beer (quite ironic, a week around lots of alcohol, then when we meet up we have a beer). It was great.</p> <p>I'll post more later, more introspective group stuff, self growth, what I learnt, etc. Should be more interesting than the recollection of events. Thankyou, Lord (if You are reading, which I'm sure You are) for such an opportunity to not only help others and witness for You, but also for the great spiritual growth that was evident in me during this time. I pray that this is a testimony to You, and the people reading can be affirmed in their faith more after seeing the work You've done in a place that is portrayed as the Devil's playground, pretty much.</p> Fri, 15 Dec 2006 00:00:00 +0000 http://stevesspace.com/2006/12/schoolies-06-part-2/ http://stevesspace.com/2006/12/schoolies-06-part-2/ Schoolies 06 Part 1 <p>So, as all of you would know (surely), I've just spent a week down at Surfers Paradise, volunteering with the Red Frogs, basically showing the schoolies there Christ's love by serving and looking out for them. While I was there, I was insightful enough to write out a few notes of what happened on each days, so I could post here as a testimony to how God works in places where it's most needed.</p> <!--break--> <p>So I left on the 25th, after Paula's musical audition, and band practice with y'all (yes, I had my harmonica with me!!). A big thanks to Paula for driving me down! I'd have found my way, but it was good of her to do so :-). I wandered around looking for where to sign up, found it eventually, signed up, got my photo taken, they didn't ask for my blue card... and helped them move stuff into a ute. Found out my hotel, walked to it and went to meet my team. I'd heard that one of the members had red dreadlocks. He meets me in the foyer of the hotel, I tell him his reputation preceeds him (with the dreads), and we go up and say g'day to the rest.</p> <p>Ahh, my team. Y'know, I'm certain that God was lookin' out for me when He picked the team I would be assigned to, I was so blessed to be a part of it. They were all from Woolongong, near Sydney, and very very devoted to God, quite an inspiration. We'd usually spend nigh on half an hour praying before heading out. More on that later though...</p> <p>So the team consisted of Lauren (the leader), Brendan, Anna, Louise, Sarah and myself. They were members of a church called “Lighthouse”, and most of them were biol students. Lighthouse was a pentecostal church, from what I gathered. Interesting that they don't consider themselves protestant, which I found confusing at first. This would be my first prolonged experience with a group of pentecostal people (man, writing it like that makes it sound so negative :p), as I said before, more on that later.</p> <p>Thus begins...<br/><br /> <b>Saturday, 25th Nov</b></p> <p>First night, should be good. We spent half an hour or so praying before we hit the hotel. Lauren was doing the praying in tongues thing, I spent some time analysing it, because I'd read that some linguists had analysed it, and said it is quite repetitive, and basically made up by people. It basically sounded liked “shoo tetetete” over and over (with slight variation in number of “te”s). So that confirmed that. Another thing I noticed was that the prayer was very... long winded, I guess you'd say. That's another thing I've sort of had a problem with too, people that go in depth, quote scripture, explain things to God, and so on. And a lot of symbolism and things were involved there too, like “cover us in Your blood from the tip of our heads to the bottom of our feet”, along with the armor of salvation fully explained every time... yeah, you get the picture. I feel I'm complaining about the team – I'm not, they were great :).</p> <p>From there, we basically walked around up to the 10th floor of our hotel (it had 31), and introduced ourselves to people. Not many people knew about us – NSW high schoolers aren't told of our presence, it seems, so we got real good at explaining what we do, real fast. We handed out a bunch of cards with our support number on it, and found out quite a bit about each person. Also caught wind that there'd be a party in 6D on Monday (though we heard this from the 7th floor people, not the 6th floor people. We made a mental and paper note of that one, we'll crash it then!</p> <p>We also met Andre – one of the security guards. He's a machine, he did 149 hours over 8 days. We took care of him always bringing him free starbucks coffee and dinner and whatever else we could.</p> <p>Having had enough of the hotel, we thought we'd check out Cavill Ave, and the beach. It was less busy than expected (but still loads of people), though the reason for that was twofold – the schoolies come 'staggered' over 2 days, and there's not many NSW/Vic schoolies compared to the Qld ones. Which reminds me... week 1 had 500ish red froggers, we had about 80. Anyway, we hit the streets and talked to people who looked like they wanted talking to.</p> <p>A few interesting things happened before we got to the beach. It was pretty crowded and full on, I made it a duty of mine to make sure that there's no team member left talking to someone on their own, which is important. I spotted Lauren talking to this guy [I won't name any names]. I came in halfway through the conversation, but found out that his mum was Christian, and has been praying for him, but Steve was still doing some searching, and having run with a bad crowd recently (just got out of jail). He was fairly open, Lauren was doing most of the talking, and ended up asking if we could pray for him, for him to resolve some issues with confidence and etc. He let us pray for him, and we did, and he thanked us, and we kept on going. He's in God's hands now, really.</p> <p>While that was going on, Anna and some others had been talking to this girl, and she had a messed up ankle (sprained or otherwise sore). Anna is interesting in that the Holy Spirit has decided to bestow the gift of healing. Quite out there, yeah? I thought so. They prayed for her, and the ankle felt better. I only heard the girl say it afterwards, and that's all we really got there. Still, quite amazing.</p> <p>Went down to the beach afterwards, talked to some people, checked out the stage (the Red Frog stage had the most people!!), and talked to some more people. Anna ran into a girl who had become seperated from her friends, and was quite distraught, as her hotel was in labrador (about 5 or 10min drive north). We comforted her and started praying and scouring the beach with her to find the friends. After a couple of circuits, she found a group she knew (though not the people she was staying with), and joined in there. And all but ignored us after that. Ahh, thankless serving.</p> <p>Went to see the street chaps from student life after that – Damo is sick, so I've been praying for him. Couldn't chat for long though, had stuff to do. Well, not that much to do. We went back to the hotel, had a conversation with a supremely drunk guy about evolution. For the record, drunk people talk and don't listen. Wish I'd picked that up earlier. We went back to the hotel room at about 2am that night, fairly early really, and slept. Not a very good sleep, as I was on the couch, but a sleep nonetheless</p> <p><b>Sunday, 26th Nov</b></p> <p>What's wrong with this Sunday? No church! But it was ok I guess, we were serving God well. And serve we did – there was another team (with 1 member) who had a call for pancakes at 9am in the morning. Anna and I joined with Rach to cook them pancakes. Wasn't an extremely spiritual conversation, but I think we made an impression that Christian people go alright. Most conversations go as follows: “So, do you guys get paid?”, “Nah, actually, we have to pay for food and that”, “Eh? Why would you do that?”, and then a spiel about Jesus and churches and how we found out about it and all that. There wasn't anything else to follow on from that though, but the pancakes we made were awesome, I must say.</p> <p>Went back to the hotel for some quiet time – I read Romans 2, which basically said not to judge others. I thought initially it would be very relevant – it's very easy to pass judgement on drunk, drugged, naked schoolies, so I made sure to watch that. The interpretation of the Word was different later on in the day, though. Also read something in 2nd Samuel, where David gets punished for taking a Census of the population. That taught me from the very start of the week that I should not count the number of people I bring to Christ, or help, or anything like that. It's not important. I took note of that and had a nap, I was tired.</p> <p>Evening came, we ate, and Rachel's team needed me for that night *sniff* I have a night away from my awesome hotel and team. It really made me realise how great my team was. That sounds bad, but it's true unfortunately.</p> <p>We started off by going to Long Beach Hotel which, truthfully, was a total pain in the butt. The manager of that hotel was really strict, wouldn't let us take the red frogs up, and they didn't have a pre-prepared list of the rooms with the schoolies in it...we spent about 1/2 hr in reception before heading to the top and beginning to work our way down. Because we couldn't bring frogs up, it was like knock knock, hi, how are you? Good to hear. So... We're red frogs, but we don't have any. But here's a card with our number if you need it. Bye! It was really hard to get in without the frogs. After 4 or 5 floors we got a call out for a pending eviction about 8 or 9 blocks north (20-30 min walk). That's where things started to really drag for tonight.</p> <p>I took the pending eviction to be fairly high priority, the others did not, and proceeded quite slowly to go down to the reception and ask for the location of the hotel. We sort of discovered how far away it was, rang home base and asked if there was any closer teams that were free... apparently not... The team didn't wanna walk it, they thought we'd catch a taxi. We were told there'd be a 45 min wait on taxis, being schoolies, and ugh. The leader of the team wasn't doing anything for initiative, so I kind of tried to there – I'm like “Look, there's an eviction, we've already spent a tonne of time here ringing home base, finding the location of the hotel, let's start walking”. They wanted to take the long way (via a main road), and hail a taxi if it was free. Of course, there were none free, and the time it took to get them moving even that far...ugh, I was praying hard for the boys and the manager.</p> <p>The directions we were given was it was near the GCI hotel. About half an hour walk later (no rush, right?), we get to the GCI and start asking for hotel names. The girl who answered the phone didn't write down the hotel name, and we all had different ideas on what the hotel name was. They were fairly certain it was something, I suggested we call home base and double check, but of course, they knew better. We bugged the GCI reception on more precise locations, the hotel name was wrong, they couldn't find it AT ALL, and still wouldn't call back home. 20mins later, they eventually call home base, the name was completely wrong, and within half a minute we had the location and were on our way.</p> <p>“Don't judge others.” Yes, God was telling me not to pass judgement on my team. I prayed hard about that, and kept praying that the manager was keeping his cool about the possible evictees. We made our way to the apartment. Turns out the prayers were answered, the boys were still in there. Apparently they'd thrown a bottle off the balcony, which is grounds for eviction (you'd kill someone easily doing that). The manager just wanted someone to fess up, but none of them knew who did it. We talked it through, didn't really reach a conclusion, but things were made more clear to the manager and the schoolies in the room. They didn't get evicted. Victory!</p> <p>We went back to HQ to restock on frogs and stuff, and to pick up another team member who had just gotten in then. Richard, his name was. Great guy. We got a call out to a hotel nearby the other one, so we went that way. A big walk later, we get there and talk to the guards, they know nothing of such a call out. So, people were tired, and we sat around there for a half hour or so before heading off to the beach</p> <p>Nothing really of note on the beach. I ambushed a couple that were making out, and offered em red frogs. They found it quite funny and had some. Not sure if they continued making out afterwards. We then got a call that someone needed an escort home from the beach, so we did that, and I left the team as we were going past my hotel on the way back.</p> <p>This was at about 2am, and my team had a pancake run at 2am, so it worked very very well. We made 3D some pancakes, stayed and chatted to em for like 2 hrs, went back to the apartment and slept. 2C wants to make us breakfast at 11am tomorrow morning :)</p> <p><b>Monday 27th Nov</b></p> <p>As we're sleeping, we get a knock on the door. I groggily get up to answer it, and it's the 2C girls asking if we're ready for breakfast yet. I'm like “Isn't that at 11am?”, after which they tell me it is now 11:15am...heheh. So I get everyone up and out and we head next door. They're makin' us pancakes!! Woo! I was half expecting bacon and eggs (because they had some of that the morning before) but pancakes is great too! :D:D, they're quite awesome.</p> <p>We had a call for pancakes to be cooked at the hotel across the road, Surf Regency, it was a group of boys. Nothing of note here, we went back to our apartment, and got another call that room 31A needed cleaning. Top floor! Woo! The view was great! The room was actually cleaner than ours, but we did a quick clean up anyway, it was a great way to meet everyone there, which would prove valuable in future.</p> <p>Went to dinner, and there was a BBQ happening tonight. Remember the 6th floor party that's happening tonight? Well, apparently the 6th floor people didn't want a party, so they left their rooms and stayed down here. We talked to heaps of them. Louise talk to some guy who had been “Christian” his whole life, I didn't catch much wind of that, but as we were leaving, she said she'd like to talk to him more. We ran into him a few days later and got his email and stuff. Also talked to another guy called bambi (strange name, no?), who'd recently had golden staf, and none of the doctors knew who it was. See, it's hardly ever passed on outside of hospital, so ... yeah. Anyway, one other guy went to a Catholic primary and high school, and was getting into some debates with Brendan about the legitimacy of the Bible.</p> <p> We went to a few different rooms, nothing of note, so we decided to go out to Cavill, hit the streets. Ran into a guy who I like to call “Mr Religion”. He was a genius, pure genius. His most common line was “This is what you believe, you gotta know your stuff”. But yeah, he was quite drunk, and just reciting stuff he learnt in religion class, with no real belief. We offered to meet up with him later on for coffee and a more sober conversation, gave him our card and went on our way. Never heard from him after that, unfortunately.</p> <p>We met a guy who was the God Son of one of the guys who started Hillsong, I didn't talk to him so I can't say much more there. We also met a really nice guy who was hoping to get into uni next semester, we've been praying for him to get into his degree. Yet another thing that we just won't know the outcome of. That was about it for tonight :-)</p> <p>Anyway, I'm sick of writing for now... But expect more later</p> Sat, 02 Dec 2006 00:00:00 +0000 http://stevesspace.com/2006/12/schoolies-06-part-1/ http://stevesspace.com/2006/12/schoolies-06-part-1/ You Are Loved... <p>I think everyone needs to focus much more on loving others, and much less on being loved. And yes, the reason this post is going up on my blog is because I realised it's something that needs to come true in my life, too. <small>Let's see if anyone can guess what specifically...</small></p> <!--break--> <p>I think Rebecca St James said it best<br /> <blockquote>You are loved, more than you can imagine.</p></blockquote> <p>So, let's stop relying on the love of others, because we have God's love, which is more than we can imagine. The only thing left is to love others.</p> Sun, 19 Nov 2006 00:00:00 +0000 http://stevesspace.com/2006/11/you-are-loved/ http://stevesspace.com/2006/11/you-are-loved/ 0, 18, 'Strength - Phil 4:13 <p>So, here I am, worried about this exam on Monday - many things make it seem impossible for me. Was a great opportunity to pull open my 33.33% Holy Spirit source of Goodness and see what He has to say. </p> <!--break--> <p>I'm going to share each and every time I do it with y'all who bother reading this, aren't ya lucky? Even though you've probably read them already, it'll be nice to know the context in which they all came out</p> <blockquote><p>I know what it is to be in need, and I know what it is to have plenty. I have learned the secret of being content in any and every situation, whether well fed or hungry, whether living in plenty or in want. I can do all things through Him who strengthens me.</p></blockquote> <p>It becomes immediately obvious that Paula didn't type it, because it is all spelt right ^_^ Haha! But I guess what this has to show me is that, even though I probably feel like I'm going to fail I (a) should be content, even if I do and (b) shouldn't be worried, because I <i>can</i> do this exam, through Christ. <p>Thanks, whoever did this one!</p> Fri, 10 Nov 2006 00:00:00 +0000 http://stevesspace.com/2006/11/0-18-strength-phil-413/ http://stevesspace.com/2006/11/0-18-strength-phil-413/ The God for me <p>I'm reading <i>Case for Faith</i>, came across a quote from a pastor, John R W Stott. Excuse the typos, I barely looked at the keyboard or screen, and I'm not about to proof read it ;-)</p> <!--break--> <blockquote><p>I could never myself believe in God, if it were not for the cross.... In the real world of pain, how could one worship a God who was immune to it? I have entered many Buddhist temples in different Asian countries and stood respectfully before the statue of Buddha, his legs crossed, arms folded, eyes closed, the ghost of a smile playing round his mouth, a remote look on his face, detached from the agonies of the world. But each time after a while I have had to turn away.</p> <p>And in imagination I have turned instead to that lonely, twisted, tortured figure on the cross, nails through hands and feet, back lacerated, limbs wrenched, brow bleeding from thorn-pricks, mouth dry and intolerably thirsdy, plunged in God-forsaken darkness. </p> <p>That is the God for me! He laid aside his immunity to pain. He entered our world of flesh and blood, tears and death. He suffered for us. Our sufferings become more manageable in light of His. There is still a question mark against human suffering, but over it we boldly stamp another mark, the cross which symbolizes divine suffereing. 'The cross of Christ ... is God's only self-justification in such a world' as ours.</p> </blockquote> <p>This is at the end of a chapter entitled "Suffering disproves God". There's much more written, but we should do well to remember that Christ suffered everything that we suffer, and everything that everyone else has suffered. Not to mention everything He Himself suffered. Which, dare I say, is much much more than I ever have, and likely ever will. Betrayal, rejection, abuse, torture... Wow.</p> <p>Jesus doesn't give us an answer to suffering, He <b>is</b> the answer!</p> Sun, 08 Oct 2006 00:00:00 +0000 http://stevesspace.com/2006/10/the-god-for-me/ http://stevesspace.com/2006/10/the-god-for-me/ The Future <p>From <i>The Message</i>:<br /> <blockquote>For if the future is dominated by the coming again of Jesus, there is little room left on the screen for projecting our anxieties and fantasies. It takes the clutter out of our lives. We're far more free to respond spontaneously to the freedom of God</p></blockquote> Tue, 12 Sep 2006 00:00:00 +0000 http://stevesspace.com/2006/09/the-future/ http://stevesspace.com/2006/09/the-future/ Forsaken <p>Skillet - Forsaken (Lyrics)</p> <!--break--> <p>I recall going madly in love with you<br/>And I remember this<br/>How could I forget?<br/>Regret is a needle<br/>In my neck<br/>It's slowly filling me<br/>With poison<br/>Spreading to my chest<br/><br/>Take my pain and numb me from this<br/><br/>Why do I have to beg<br/>When all that's left<br/>Is a memory<br/>Forsaken [2x]<br/><br/>I recall pledging my sole devotion to you<br/>It reminds me how<br/>Now I'm on my knees<br/>My guilt consumes<br/>Lost the will in me<br/>Wasting away before you<br/>Hold me closer please<br/><br/></p> <p>No, it's not the whole song, it's most of it though (it repeats a lot :p). I guess parts of this song go on a lot about my last post (about forgiveness)</p> <p>Seems the write of the song did something wrong, that they're struggling with, and they're so torn by what they've done the feel they really need to beg for forgiveness. "Regret is a needle inside my neck" - shows how bad things can really get with regret.</p> <p>But the big part of it: "Why do we have to beg when all that's left is a memory?". I'm supposing that's why sins are so easily forgiven - because that's all they are; a memory.</p> <p>Just a nice little though exercise :o)</p> Sun, 14 May 2006 00:00:00 +0000 http://stevesspace.com/2006/05/forsaken/ http://stevesspace.com/2006/05/forsaken/ Forgiveness <blockquote>"Can sins be forgiven"</blockquote> <p>- Cloud, Final Fantasy 7: Advent children.</p> <!--break--> <p>In the Final Fantasy movie, this guy called Cloud feels like absolute crap for not being there to save Aeris when he dies. He asks Vincent the above quote, Vincent doesn't know the answer. He said he hasn't tried yet.</p> <p>The thing is, with Cloud, he had been forgiven. Aeris had forgiven him (she makes a lot of appearences for a dead chick), all his friends don't blame him, or have forgiven him if they do, but he has trouble forgiving <i>himself</i>.</p> <p>I'm sure we all know exactly how Cloud feels, like he's let the world down (or in this case, the girl of his dreams, which is the same thing :p), and he's just so caught up in it, he doesn't realise that he's just dragging around this issue, for no reason - everyone has forgiven him!!</p> <blockquote><p>zuru-zuru zuru-zuru</p></blockquote> <p>This is something Cloud's childhood friend, Tifa, says. It's japanese onomatopeia for dragging (apparently - that's what some subtitles said). It was relating to Cloud &amp; the Aeris' death issue - he was just dragging it around, and it wasn't helping nothing.</p> <p>How does all this relate to us? I know how it relates to me :p definetely. Things we do wrong to other people - even after saying we're sorry, repenting, and being told that it's ok, we're forgiven, that's not enough for us</p> <p>Even though we've prayed for forgiveness, and we know we've received it - God has told us this many many times through the bible, it's still not enough for us to feel ok about it.</p> <p><b><i>WHY</i></b> can we forgive others <u>so</u> easily, why can we be forgiven by God so easily, forgiven BY others so easily, but have such a hard time forgiving outselves?</p> <p>How do we even forgive ourselves? When we do things wrong, it feels like the core of us is rotten, and takes a long time to feel good again.</p> <p>And even when we've gotten over whatever it is, we're never <i>really</i> over it. I know I still feel bad about something that happened back in primary school, even though my 'friend' back then has forgiven me for it.</p> <p>Lord, I'm sorry for all the things I've done, and I know you're loving and forgiving, please give me the strength to accept the forgiveness of You, and of others, and please Lord, give me the courage to forgive myself.</p> Sun, 07 May 2006 00:00:00 +0000 http://stevesspace.com/2006/05/forgiveness/ http://stevesspace.com/2006/05/forgiveness/ Bible <p>I was cleaning my room the other day, and I put my Bible in the bookshelf (ok ok, top of the cupboard) while I tidied up.</p> <p>Later, I took the Bible out, and all the other books there fell over.</p> Mon, 24 Apr 2006 00:00:00 +0000 http://stevesspace.com/2006/04/bible/ http://stevesspace.com/2006/04/bible/ Camp <p>Sorry for the late updates, I've been <i>really</i> lazy about it. As most of you know, I went on a camp with student life a few weeks back, it was awesome. Actually, it was quite a bit eye-opening. Y'see, the week before I went there, I was struggling a bit with God using me, or not using me as it were. I felt I was too quiet and introverted to do anything for Him.</p> <!--break--> <p>But while I was on camp, I was the most talkative and out there person I've ever been (ok...that sounds weird :p). At the end of camp, one of the girls there said to me that I encouraged her heaps, by being a new Christian, and still willing to voice my opinions and leading some things (like, group discussion)</p> <p>At first I'm like "wow, that's awesome God <i>is</i> using me after all". After a while, though, I began to think that God was just showing that He <i>can</i> give me the power to be "out there", if He wishes. Indeed, a quote I read and liked while I was there was "Don't be proud if God uses you, he could use a talking pig if He wished", and those words ring true.</p> <p>Last SNL service, I went and prayed at the altar as I sometimes do, and Pastor Robin asked me if I'd like to be blessed, and what specifically. I mentioned my quietness in this regard, and he gave me the most encouraging words I could possibly hope for. He told me that quiet people are generally more thoughtful, kind, caring and deep people than the louder ones, and can show God through those qualities far better than through something that I'm not. Wow.</p> <p>Anyway, I wrote some things while I was on camp, I've copied and pasted some of it here. I missed some out, bwa ha ha, that's personal :p</p> <p><b>Camp – Day 1</b></p> <p>Well, not really a day as such – I’d probably call it an evening to be perfectly honest. Seems the Spirit is doing wonders for me atm – I’m quite extroverted and talkative, meeting tones of new people and (usually) even holding somewhat decent conversation with some of them :o) Praise God.</p> <p>Got to Beenleigh station at 5:45 with no drama, waited there for about 5 or 10 minutes, then a tarago pulls up. Forgot that Scott drove a Tarago, so I just sat there still – didn’t even take a second glance at the Tarago. Phone rings, it’s Scott, calling from inside the Tarago. So I get in the car :o). Had some asian take away because KFC was way too full. And we made our merry way up to Tamborine. Windy road, reminded me of Cairns and made me think of stars. Looks like Paula brought me to see stars far far before I get the chance to take her to some. Sigh. Oh well :o)</p> <p>When we got here the reception was awesome – really cool people (there’s an awful lot of really cool Christians, I’ve noticed). Paula was singing away at the mic, as expected, and I went and got my name badge and had a good ol’ yak to Stuart.</p> <p>Bit of socializing, and the activities for the night started. The “Blues siblings” (Tim and [insert her name here]) did quite a few cool skits, Baa-aa, Moo, Neigh game to get everyone fired up. Then they switched to some aussie-type clothes, and did a skit to show everyone how socializing doesn’t always work (two distinctly different people trying to talk in a bar – funny, because Paula and I are quite completely different and never have trouble…). We then did heel-and-toe as a big group. Part of my extrovertedness came out here – I got Paula up to help show people how to do it. Both of us, not just her. :D was fun, but squishy, but fun. Paula suggested that we do a modern jive lesson for the popples. She wasn’t being totally serious, but I thought it was a cool idea, we ran it by Tim. Hopefully we will run it during the free time tomorrow, it’ll be a ball :D<br /> After a bit of worship, the preacher guy started his sermon joke. Funny joke here:</p> <blockquote><p>A trucky stops at a truck stop, and order a fries, burger and coke. Just as he sits down to eat it, 3 big rough lookin’ guys (Hell’s Angels type thing) ride in on their Harleys, the whole tattoo type kit, nose rings, earrings, you name it, they’re crazy. One walks up and takes a big handful of his chips and eats it. The next takes a huge swig of the drink, and puts it back. The last guy eats half the burger, returning the rest to the guys plate.</p> <p>The guy, being Christian (hence, cool :p) goes and pays for meal, and wanders back to his truck without saying much at all. The Hell’s Angels guys head to the cashier and say “He’s a bit of a wimp aint he?”, the cashier goes “I dunno, but I know he’s a pretty horrible driver – he just ran over 3 Harley’s”.</p></blockquote> <p>The preacher was mainly focused on encouragement. I think that everyone really wants encouragement. If we do something, we like to be noticed, even if we don’t ask for it (being humble and all). I know that I get encouraged and discouraged at times, I prefer the encouraged :) heheh.</p> <p>Something that stuck at me during the sermon was he mentioned if one of these Hell’s Angels type people came to church while we were at the door, greeting people, if we’d embrace him and say “Welcome to the Family!” or if we’d reject him saying “You can’t smoke in here.” And “You can’t wear those clothes in God’s house!” and etc. etc. etc. – could go on forever. I wonder what I’d choose – encouragement or discouragement. I think I’d go encouragement – words of a from complete stranger guy “What’s a Christian look like?”. Encouragement! Wow.</p> <p>Barnabas was a prime example of encouragement - check him out!</p> <p><b>Day 2 (well, half way through day 2) </b></p> <p>Well, after a refreshing 4-5ish hours sleep, bright and ready and rearing to go!! Oh yeah.</p> <p>Not really, seems my extroverted self has run away, I’m all introverted today. I blame my tiredness, or rather, lack of awakeness ^_^. I did get up early enough for morning prayer though – even had a shower beforehand and all. Forgot my towel, used my work shirt, it was great. :D. Forgot my deodorant, used someone else’s deodorant. Great fun!</p> <p>I missed half of morning prayer, they were praying for others when we started. Then we thanks the Lord for things, and … damn, can’t remember. It was pretty good though :-)</p> <p>As was breakfast – cereal, then scrambled eggs and bacon, or bacon and eggs, depending on which way you like to say it. MMM.</p> <p>Had an awesome preaching after that – another cool joke: Here ‘tis:</p> <blockquote><p>An Englishman, Scotsman and [insert stereotypically less bright race]man – Let’s call it islbrman – are stuck in a Nazi concentration camp. They manage to escape, but are caught at the edge of a forest, and the alarm bells go off. So, thinking quickly, they all dash up different trees. The sniffer dogs smell around the bottom of one tree, the one with the Englishman in it, and thinking quickly, he goes “tweet tweet”. The guard shouts “YOU STUPID DOG! IT’S JUST A BIRD!!”. The dog moves to the next tree, starts barking, and the Scotsman, remembering the Englishman’s idea, tries it out: “Hoot Hoot!”. Same response from the guard, just with owl instead of bird… The dog then barks at the tree of the islbrman’s tree, and following the other people’s lead, goes “mooo”.</p></blockquote> <p>Somehow, I think that joke works better when said…heehee</p> <p>I think I forgot to mention that the sermon yesterday was about “Encourage each other daily”. Well, that was the big part of it anyway. Today, it was focusing on encouraging one another. Indeed “Accept one another” was the whole saying thing we had going on then. A lot of it was from Romans, and how people coming from, say, a Pagan would feel uncomfortable as a new Christian, eating meet that was offered to the idol and then sold at the market. For Christians who are strong in faith in that department, it is ok for them to eat the meet, as they know it’s just meat, and is a blessing from God. But Pagans who are of lesser faith may harm their faith by eating said meet, because they’re unsure of it.</p> <p>They said that things that we are uncertain about whether they are sinful or not, is generally regarding as sinful. I wonder how this applies in my life, what I’m certain and uncertain about. Dot dot dot…<br /> Indifference came into play a lot here – I’ve found, in my experience, that most people are rather indifferent about denominations and things, and are generally accepting of people regardless. That’s just who I’ve met though, others may not share the people I’ve met’s mindset there (argh, bad, bad sentence). It was an interesting thought process anyway.</p> <p>We then split off into groups and talked about things that could ‘split’ people, especially in regards to campus Christianity, and personal experiences of weakness and strengths in people, and ourselves. See notes for more.</p> <p>Had some quiet time… I must say, I work much better in group environments than on my own, but having time to pray and think things already covered through helped me a lot. Yay!</p> <p>Then we went to training, not a whole lot of new material there, but I think I learnt a few things still, put some things in perspective, and saw other people’s views on some things, so I’m not complaining. It was good :)</p> <p>We had some free time after that, went and had a game of soccer. After it was 2-2, we decide to go ‘next goal wins’. 30 seconds later, the other team scores &#61516; SHAME! Heaps of fun though</p> <p>Now we’re still in free time, Paula’s pretty much asleep next to me here as I type this, I really feel like sleeping, I think quite a few other people here share that mindset. Let’s see how that goes.</p> <p></p> <p>Sorry about the length, hopefully entertained you while you were procrastinating :o). I didn't write any more than that, unfrotunately, I kinda wish I kept up with it heh. Stay tuned...until next time...maybe a few months away now</p> Mon, 10 Apr 2006 00:00:00 +0000 http://stevesspace.com/2006/04/camp/ http://stevesspace.com/2006/04/camp/ Footprints in the Sand <p>As some or all of you know, I went on a camp thing with the UQ Catholic Society last weekend - up at the sunny coast. Something headlands was the place, it was cool. I was a little reprehensive at first...not being Catholic...and not exactly trying to be either... but it was cool. No denominational arguments or doctrinal speeches, it was all just focus on Jesus. Woo!</p> <!--break--> <p>We went down to the beach on the Saturday morning - as we were walking I looked back, and noticed the footprints in the sand. It reminded me of something dad had (not sure if he still does) aaages ago, back when church was a regular thing...</p> <p>It was a little picture frame of footprints in the sand. The footprints represented this person's walk through life, and there's some dialogue in it between this person, and Jesus. My wording will suck in comparison...but the person says something like, "For a large part of my life, there are two sets of footprints, Yours and mine, but one of them stops, and I'm left to carry on alone"</p> <p>Jesus replied to this, "I did not abandon you. Where it changes to one set of footprints is where I carried you."</p> <p>(Sorry, my wording sucks, and I can't make it better)</p> <p><b>Edit:</b> Seems there's some online versions of this poem. Sigh, and my memory was doing so well. Click <a href="http://www.gospelgreeting.com/cards/inspirational/footprints/footprints.jpg">here</a> for one such thing. And thank mysterious M ;-)</p> Mon, 13 Mar 2006 00:00:00 +0000 http://stevesspace.com/2006/03/footprints-in-the-sand/ http://stevesspace.com/2006/03/footprints-in-the-sand/ Miracles <p>They happen so often that we often look over them and just see it as something that happens.</p> <!--break--> <p>Take a look out your window now - all the trees, animals, the clouds or stars, the sun and moon - how amazing is it all? Everything you see there is a miracle. I find it hard to remember that.</p> <p>Every now and then, though, one comes along that blows you away and just makes you thank God so much for it, because it's amazing, breathtaking, stunning.</p> <p>If you guessed that something amazing happened recently to me that I was blown away by, you'd be right :p Most of you know I've just come back from Japan, and it was awesome. That's not the miracle I'm talking about though.</p> <p>A big reason for me going to Japan was the prospect of seeing snow. When I'd heard that Tokyo had had the highest rainfall in like...years...I got all excited. We get there, and it's super cold, but nowhere near cold enough for snow. Plenty of rain, no snow.</p> <p>We were going to go to Fuji-san on the first Sunday (was it Sunday? I can't remember), but didn't because the weather wouldn't have been clear enough to see it. The weather was getting warmer and warmer, so the chance of snow was getting lower and lower to accomodate that</p> <p>Then one fine, overcast, on the verge of raining, Friday afternoon, we were heading from Paula's place to mine by train, we get near Higashi-Abiko station (the one before mine). I'm looking out the window and the rain isn't really looking like rain at all...it looked like SNOW!</p> <p>I saw it as a miracle, the first thing I did was thanked the Lord, because I really wasn't expecting to see any, except on the top of Mt Fuji from a distance. With the temperature increasing, chances seemed low. He gave me (us) some snow though!! See what I mean about Miracle?</p> <p>The second thing I did was go to the door of the train and play with some of the falling snow. Yaay! Snow! I was excited. I even started singing... "There's snow on my jacket" is about the limit of what I remember. Those who know me well enough, me singing is a pretty major thing.</p> <p>Anyway, we get off at Abiko (my station), the snowing has died down a bit, it was freezing (literally) cold, and there was snow around the place. I only got about 15mins of actual snowing, but it was awesome. Thankyou, Lord!!</p> <p>Mind you, the next day we went to Fuji-san, and one of the lakes around there had a significant amount of snow on it - we were allowed to play there for a while. Yay!</p> <p>That was my biggest part of Japan, something I definetely see as a miracle, because it was TOTALLY unexpected. Nothing on forecasts or anything, and it didn't even seem cold enough. Mind you, all of Japan was a miracle. I couldn't ask for anything more of Him.</p> Tue, 07 Mar 2006 00:00:00 +0000 http://stevesspace.com/2006/03/miracles/ http://stevesspace.com/2006/03/miracles/ The Way of Love <p>The 'verse of the day' over the past couple of days have been taking a leaf from 1 Cor 13... I figured I might take a <a href="http://www.biblegateway.com/passage/?search=1%20Cor%2013&version=31">read</a> of it (<a href="http://www.biblegateway.com/passage/?search=1%20Cor%2013;&version=65;">Message</a> version) although I've already read it before.</p> <!--break--> <p>Some gems from it - &quot;If I speak God's Word with power, revealing all his mysteries and making everything plain as day, and if I have faith that says to a mountain, 'Jump,' and it jumps, but I don't love,I'm nothing.&quot;. There's a few like this - If I do XXXX but don't love, I'm nothing, I've gotten nowhere, I'm bankrupt.</p> <p>I was wondering...why? If (for some weird reason) I help an old lady across the road, but without love, does she not still get helped across the road? Is it not the same either way, with or without love, to her? Then I thought... no, that's not right. If I help someone across the road, or pack groceries in the car, or I dunno, do some sort of charitable event with the purpose of furthering oneself - for example, expecting payment or increase in status in other people's eyes - then there really was no point doing it in the first place. The ends do not justify the means, the means justifies the ends. It's a good act if we do it for love, it's a bad act if we do it for ourself.</p> <p>I guess the point of it is, with love, <a href="http://www.biblegateway.com/passage/?search=1%20Cor%2013:4-7;&version=65;">all these things</a> will happen as a result, which is what makes love the most <a href="http://www.biblegateway.com/passage/?search=1%20Corinthians%2013:13;&version=65;">important</a> of all. Of the three it mentions there - Trust, Hope, Love - Love is the most important. Why? The list before said Love "Trusts God always" and "Always looks for the best" - LOVE is a prerequisite for those 2, how can you trust without love? How can you even <i>hope</i> without love? Hope is fairly synonymous to faith (oblig quote: "fear expects the worst, faith expects the best"), so how can we be faithful if we do not love? I believe THAT is why love is the most important.</p> <p>Before I became Christian, some things didn't make sense to me (indeed, lots of things still don't :p heh). Hmm having trouble articulating it here though... maybe a question... "If there was no heaven, would you still love God? And live your life by /for Him?". It's a tough question, the thing that gets most people into Christianity (or really, any religion) is the reward. Personally, I think that if the only reason for worshipping and loving God is to "get into heaven", then we're not gonna get there. We have to live our lives as Christians because it's a better way to live, to love, because it's the way Jesus lived His life, for others, out of love, not because of the rewards we get out of the end. If God dropped us a message tomorrow saying "I've given up on this Heaven thing," I don't think he'd want us to stop loving. Jesus wouldn't have, and He's a guy that knows what He's doing! :D</p> <p>(sidenote: I quite like analysing these things, I should do it more often :D)</p> Sat, 11 Feb 2006 00:00:00 +0000 http://stevesspace.com/2006/02/the-way-of-love/ http://stevesspace.com/2006/02/the-way-of-love/ The more we drink, the thirstier we get <p>I gave blood today. Those of you who have given blood before would know that you have to increase drinking habits before giving - more blood, or thins/cleans the blood I suppose. Anyway, after drinking many many glasses, I was thirstier not long after</p> <!--break--> <p>This got me thinking how it applies to everything. I think the same is true for food, for leisure - as in, the more we play, the more we want to keep playing - and etc. Most importantly, I think it's also true with worship.</p></p> <p>See, I went to SNL on Sunday night. Yeah I know, crazy huh? SNL on SUNDAY (yes, that's a joke). And I'm quite shocked at how much I missed it. I missed the week before because I just got back from Bundaberg, and so I hadn't been for 5 weeks or so. I was quite pumped about it afterwards - wanting more, as it were (a side note on that: If anyone knows any churches closer to me that can match (or indeed, compare to) the SNL services, let me know please).</p> <p>The reverse is also true though, the less we drink, the less we want to drink. I'm guessing that's one of the main reason people stop going to church is because they miss a few (for whatever reason) and then aren't really inspired to go again, because perhaps they forgot how good it really is</p> <p>So drink up the Lord! And thirst for Him more! That's my advice!</p> <p>Other advice includes: Don't watch Brokeback mountain. It's the worst movie I've ever seen.</p> <p>The forums are done, I've not yet put them up (and I suspect I will have major problems putting them up too, so don't get TOO excited now :p They'll be up soon</p> Tue, 07 Feb 2006 00:00:00 +0000 http://stevesspace.com/2006/02/the-more-we-drink-the-thirstier-we-get/ http://stevesspace.com/2006/02/the-more-we-drink-the-thirstier-we-get/ Looking back... <p>Oooh an update. Are you content, oh-so mysterious Miss 'M'?</p> <!--break--> <p>Anyway, I came across <a href="http://images.ucomics.com/images/pdfs/sadams/godsdebris.pdf">God's Debris</a>, an e-book, written (I think) by the creator of a few fairly famous comics, not the least of which is dilbert, but probably a few others too. Make sure you finish reading this before delving into it though :p. I believe I was warned not to read it if I have any belief of God, but when I read a snippet from it I couldn't resist... but anyway, I'm passing that warning to you.</p> <p>Mind you, now I've read about 2/3 of it (I'm on page 101/144), I think it's interesting, and thought-invoking, but not exactly radical or belief-shaking either. Some of you might find it disturbing or whatever, depends I guess. Someone (perhaps accurately) said it seems impressive and insightful to people who don't really have much experience with thinking.</p> <p>Well, I can't believe the similarities this "thought exercise" had with my thinking before I became Christian. Probability, free will, etc. Obviously not everything, but certain parts there i'm like *gasp*.</p> <p>Have a read if you like, it's long, but interesting. And make sure you remember it's one guy's opinion (when he dethrones Einstein, you'll agree with me heheh).</p> <p>We'll talk about it once I get the forums in. I've been lazy^H^H^H^Hbusy (geek joke: ^H is what a backspace is translated to as it's passed over some telnet connections, depending on the config). All the posting functionality, users and things have been done, but little things like how many posts have been done for each topic, and sorting by most recent posts still need doing. Oh, and I need to add the ability for user accounts to be made heh. (I added the test one manually).</p> Thu, 02 Feb 2006 00:00:00 +0000 http://stevesspace.com/2006/02/looking-back/ http://stevesspace.com/2006/02/looking-back/ Evil/Good <p>I'm not sure where I pulled this from, but it's been on my mind a little lately, even though I'm sure I got it a while ago. Unfortunately I don't remember the exact words, but it goes something along the lines of; There's no evil in the world, only lack of good.</p> <!--break--> <p>It makes sense, there's a lot of things that follow the same sort of rule. Look at darkness - there really isn't such a thing as darkness itself. Darkness doesn't spread, darkness disappears as soon as there is light nearby. You can't make something darker, unless you remove light. Coldness is just a lack of heat, again the only way to make cold is to remove heat.</p> <p>Also there's a limit on each of these - temperature can only go as low as -273.1 degrees celsius, and I'm pretty sure there's nothing darker than the complete absence of light.</p> <p>I believe this can be applied to evil as well. Evil, as we know it, is created by lack of good. There's probably a limit on the amount of evil someone has as well (see: Satan), but remember it's not the amount of evil, it's the complete absence of any Goodness</p> <p>I suppose that's why we can be forgiven, and when we go to heaven, our hearts are filled with 100% good, just like Jesus's heart. Down on earth though, there's probably always a dark, cold corner in our heart that's not filled with good. *sigh*</p> <p>---<br/> <p>Update on the website; I had to create my own forum because none of the premade ones (phpbb and minibb) would work on this crappy webserver (sorry webserver, if you're listening). I'm about 70% complete on the forums, and it's ugly. I need an interior decorator or something. Stay tuned.</p> Mon, 02 Jan 2006 00:00:00 +0000 http://stevesspace.com/2006/01/evilgood/ http://stevesspace.com/2006/01/evilgood/ Ooohh <p>Found this. It's cool. Was too long for an MSN Name. </p> <!--break--> <blockquote><p>Do I love myself to the contempt of God's will, or do I love God to the contempt of my own will?</p></blockquote> <p>That is all. How awesome huh? Nice question to ask ourselves when we want to make up our own rules about things.</p> Fri, 09 Dec 2005 00:00:00 +0000 http://stevesspace.com/2005/12/ooohh/ http://stevesspace.com/2005/12/ooohh/ First Post! <p> Well, I figure it's about time I wrote something in here to fill the void - I'm sure most you are quite bored with seeing a huge list of "lying down" dudes, though truth be told, I never tire of them.</p> <!--break--> <p> As you can probably tell, the site has been updated a little. Layout wise. hopefully by the time you're reading this, I've done the gallery section decently enough and well, other stuff too.</p> <p> Uhh, I dislike Internet Explorer. It messes everything up... simple as this site looks (and indeed, is), it took *ages* to line everything up properly. I can only pray it looks half decent on safari and opera, the only other 2 browsers that matter. Drop me a comment if it doesn't show right. Oh, and if you can think of a better colour scheme, which shouldn't be hard - a colourblind penguin could come up with a better colour scheme methinks - also drop a comment in, or contact me. Y'all know how to. Feedback is appreciated.</p> <p> Anyway, this'll do for now. Next major update will include *gasp* a FORUM! (and better layout for gallery...)</p> Fri, 09 Dec 2005 00:00:00 +0000 http://stevesspace.com/2005/12/first-post/ http://stevesspace.com/2005/12/first-post/