Friday, December 24, 2010

Entity Systems Part 2: Components

Entities are essentially disconnected collections of components. Or as disconnected as possible. Some components depend on others to mean anything in game, but can be processed individually. We should start looking at how components are stored and associated with entities.

It's useful to think of each component type having it's own database table. Each row then is an instance of that component type. The primary key is the entity key.

Components are just data containers so there's no standard functionality across all of them. The only functionality they should contain are things like operator overloads, type conversions, or internal data manipulation (getters, setters). A few will have events to indicate that a value has crossed a threshold for example an OnDied event when int Health == 0

I've found it useful for the component to have on small bit of common functionality - the owner. My original design was an empty interface, but this didn't provide a link back to the owning entity unless the component type specified it. I added an abstract base that implemented this.

IEntityComponent

public interface IEntityComponent
{
    ulong Owner { get; set; }
}

EntityComponent

public abstract class EntityComponent : IEntityComponent
{
    private ulong _owner;

    public virtual ulong Owner
    {
        get
        {
            return _owner;
        }
        set
        {
            if (_owner == 0)
                _owner = value;
            else
                throw new FieldAccessException("Cannot reset component to different entity.");
        }
    }
}

And that's it. Just an interface so our component managers can enforce some semblance of type control.

IComponentManager

This is really the meat of components - holding and manipulating typed collections. I use a base interface to make it easier to swap out different implementations.

interface IComponentManager : IEnumerable<Type>
{
    void Add<T>(ulong entityId, T o) where T : EntityComponent;
    void Add(ulong id, IEnumerable components);

    void Remove(ulong id);
    bool Remove<T>(ulong id) where T : EntityComponent;

    T Get<T>(ulong id) where T : EntityComponent;

    bool Contains(ulong id, Type t);
}

The interface specifies the functionality to add components singly or as a list. It can remove a whole entity or a single type from an entity. Finally, it can retrieve components.

This is not meant to be a forward facing class. It's designed to be internal to the EntityManager. It provides the mechanisms for the entities to report about themselves.

Default ComponentManager

The default ComponentManager is mostly just a wrapper for a dictionary that conforms to the interface.

internal class ComponentManager : IComponentManager
{
    ...

    Dictionary<Type, Dictionary<ulong, EntityComponent>> _components;

    ...
}

Conclusion

Everything up till now has been fairly straightforward. Things get more complicated when we start creating systems that require multiple component types. To put it in SQL terms again, how do we write a JOIN query? Well, that's for part 3.

Entity Systems Part 1: Entity and EntityManager

The current state can be found in changeset e73319ab32fd.

Entity

First things first, an entity is essentially an id number. There are a few useful utility functions that can be encapsulated inside of it, but embedding too many will cause problems down the line. Most of this is duplicated in C# from Adam Martin's post here.

Entities know where to find the component store to add and retrieve associated components. Arguments can be made about storing basic things like position and orientation in the entity, but as will be shown later, systems don't hold references to entities, just lists of components to operate on.

/// <summary>
/// Basic entity class.  Consists of an id and a list of components.  Ideally it
/// should only be an id, but it's useful to be able to know what components it
/// is built of and to find them quickly.
/// </summary>
public class Entity
{
    readonly ulong _id;

    public ulong Id
    {
        get
        {
            return _id;
        }
    }

    /// <summary>
    /// Static instance of the entity manager.  Entities should be able to add 
    /// themselves to the manager on creation - saves having to remember to do this.
    /// </summary>
    public static EntityManager EntityManager { get; set; }
        
    public Entity(ulong id)
    {
        _id = id;

        if (EntityManager == null)
            throw new ArgumentNullException("Entity manager cannot be null.");

        EntityManager.Add(this);
    }

    /// <summary>
    /// Add a new component to the entity
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="component"></param>
    /// <returns></returns>
    public Entity Add<T>(T component) where T : IComponent
    {
        EntityManager.Components.Add(Id, component);
        return this;
    }

    /// <summary>
    /// Return this as a component
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public T As<T>() where T : IComponent
    {
        return EntityManager.Components.Get<T>(Id);
    }
}

EntityManager

The manager is responsible for creating and tracking loaded entities and assigning them ids. Entity ids are transient - rebuilt every load. Template ids persist between game runs. Currently I have two Get<T> functions that aren't implemented - still thinking if those are actually needed or not. Leaning towards no. Not show here are the details of the ComponentManager - that's another long post.

public class EntityManager
{
    static ulong _nextAvailableId;

    Dictionary<ulong, Entity> _entities;

    /// <summary>
    /// Store of all loaded components, indexed by type and entity id
    /// </summary>
    public ComponentManager Components { get; set; }

    public Entity this[ulong index]
    {
        get
        {
            // TODO: Should we be doing this?
            // If we're looking for a non-existant entity, go ahead and add it
            if (!_entities.ContainsKey(index))
            {
                Add(new Entity(index));
            }

            return _entities[index]; 
        }
    }

    public EntityManager()
    {
        _entities = new Dictionary<ulong, Entity>();
        _nextAvailableId = 0;
        Components = new ComponentManager();

        // Check that entities know this is the manager
        if (Entity.EntityManager == null)
        {
            Entity.EntityManager = this;
        }
    }

    /// <summary>
    /// Return all entities containing a particular type of component
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public IEnumerable<Entity> Get<T>() where T : IComponent
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// Return all entities containing all of the parameter types
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public IEnumerable<Entity> Get(params Type[] componentTypes)
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// Add an existing entity to this
    /// </summary>
    /// <param name="entity"></param>
    public void Add(Entity entity)
    {
        if (!_entities.ContainsKey(entity.Id))
        {
            _entities.Add(entity.Id, entity);
        }
    }

    /// <summary>
    /// Create and return a new entity
    /// </summary>
    /// <returns></returns>
    public Entity Create()
    {
        _nextAvailableId++;

        // Ensure that the next available id is, in fact, available
        while (_entities.ContainsKey(_nextAvailableId))
        {
            _nextAvailableId++;
        }

        return new Entity(_nextAvailableId);
    }

    /// <summary>
    /// Remove an entity
    /// </summary>
    /// <param name="entity"></param>
    public void Remove(Entity entity)
    {
        Components.Remove(entity.Id);
        _entities.Remove(entity.Id);
    }

    /// <summary>
    /// Remove an entity
    /// </summary>
    /// <param name="entity"></param>
    public void Remove(ulong id)
    {
        Components.Remove(id);
        _entities.Remove(id);
    }
}

Currently entity id's are just ulong's incremented for every entity. Theoretically it's possible to reach the max, but even creating one new entity every millisecond, it would take over 500,000,000 years to reach the max. ulong's are really big. Even just a plain int would give us 2,147,483,647 possibilities (positive ints only).

Sample Usage

_manager = new EntityManager();

_manager.Create()
    .Add<Transform>(new Transform() { })
    .Add<Renderable>(new Renderable() { });

var transform = _manager[1L].As<Transform>();

Nothing really special here - most of the work is done in the ComponentManager. That's coming up next.

Saturday, December 18, 2010

Entity System Test Project

Entity Systems are a great way to build up game objects, but a significant thought shift from traditional OOP inheritance trees. Simply getting started is one of the bigger challenges as well as being aware of, and avoiding, functionality creep into entity and component objects.

Much has been written on entity systems, although most of it is at a very high level overview of what they are. There are some examples in a few books, but I'm cheap. Browsing the net though, here's a quick list of some of the best examples and most helpful collections:

Step 1

I plan on stepping away from OTD for a little while here. It has a ton of code behind it, a lot which has been sitting there for over a year without refactoring. I'm starting a much smaller project to focus on entity systems for a while as well as clean up some of my engine code.

Amoebal

Source code is up on BitBucket. It's still very, very early, but a decent foundation to build on. Further posts will detail the process of creating an entity system in XNA and C# as well as try to explain my reasoning behind a few things.

Questions to deal with

  • How will lists of components be sent to systems? Big dictionaries?
  • How will component types be cached and then pruned? Entities can register components on add

Saturday, December 11, 2010

Mesh Levels

Got some various debugging info in place and finally decided to get some stats on the various levels of the map build.

Build Data
Level Cells Avg. Distance Time (ms)
0 12 1.051462 56
2 92 0.396275 243
4 252 0.2403322 1264
6 492 0.1721796 3594
8 812 0.1340831 11187
10 1212 0.1097718 19649
12 1692 0.09291888 34545

The cells increase at a slight exponential rate while the time at a much steeper rate. The generating code could be a lot more optimal, and the vast majority of this time is spent in the mesh generate. Each cell is locally subdivided down three levels. There are probably a lot of optimizations that could be run, and a couple of the loops could be consolidated. Over time I've managed to optimize most loops to just n * n.neighbors. I've opted more for understandable code (as much as possible) vs highly optimized loops. Considering that all of this is done once at compile time, it's not a big deal really.

Graphs

Cells

Time

Distance

Friday, December 3, 2010

IEnumerable.ToString()

One useful thing is to be able to concatenate IEnumerable's into a string for printing. A couple extension methods take care of this.

public static string ToString<T>(this IEnumerable<T> collection)
{
    return collection.ToString(",");
}

public static string ToString<T>(this IEnumerable<T> collection, string separater)
{
    return String.Join(",", collection);
}

public static string ToString<T>(this IEnumerable<T> collection, Func<T, object> field) where T : class
{
    return collection.ToString(",", field);
}

public static string ToString<T>(this IEnumerable<T> collection, string separater, Func<T, object> field) where T : class
{
    return String.Join(separater, collection.Select(field));
}

But couldn't we just use String.Join as the extension methods are? Sure, but this is more fun and provides a more logical response to ToString() as well as more control over objects with anonymous functions.

var testList = new List<SimpleObject>()
{
    new SimpleObject() { Id = 1, Name = "asdf" },
    new SimpleObject() { Id = 2, Name = "qwer" },
    new SimpleObject() { Id = 3, Name = "zxcv" },
};
var s = testList.ToString<SimpleObject>(",", c => c.Id);

One caveat, the base IEnumerable.ToString() is still there, and simple prints out something like System.Collections.Generic.... Not what we expect. To call the basic extension method, we have to specify it with the type parameter:

var fail = testList.ToString();  // Ruh-roh!
var works = testList.ToString<SimpleObject>();  // Woot!

Tuesday, October 26, 2010

Rabbit Hole

Well, almost 4 months later and a ton of work and I think it's time to scrap cell blending.

Heightmaps still work and I will probably keep those, but getting cell blending to look good will take just too much work. Leaving the art style simpler will help preserve the board game feel. Cell borders may get some rounding if I can figure out an easy way.

Edit:

Nope, not scrapping texture blending. Looks much better if the influence radius is set smaller. Screenshots later as I'm working on loading heightmaps from the saved map file. Oceans will have a sandy texture on the grid and a separate inner sphere for the actual water.

Friday, October 8, 2010

Entity Framework and Max Field Length

Being able to check the maximum length of an entity field can be incredibly useful. Later, I'll be exploring how to automatically validate entity lengths, but for the moment we just need to find them. Worry about using them for later post.

Entity Framework 4 Data Models, edmx files, provide all the database metadata, but it takes a bit of finding. First, you need a valid ObjectContext. I'm using the Repository/UnitOfWork pattern and my UnitOfWork acts as a wrapper around the ObjectContext that gets passed into the repositories. At the moment the functionality to check length is in the UnitOfWork, but can reasonably be moved anywhere that has access to the database context, such as the repository base. Might make more sense there even.

Anyways, to the code. There are several examples around the web and they provided starting points on where to find the correct metadata. In the end, the code is actually quite short.

/// <summary>
/// Find the maximum length of an entity field in this context
/// </summary>
/// <param name="field"></param>
/// <returns></returns>
public int Length<TEntity>(string field)
{
    var item = Context.MetadataWorkspace.GetItem<EntityType>(typeof(TEntity).FullName, DataSpace.CSpace);
    return (int)item.Properties[field].TypeUsage.Facets["MaxLength"].Value;
}

I leave error checking to be handled by the caller. The only errors that should be thrown are if the Context is invalid, ArgumentExceptions if you send an invalid entity, field name, or perhaps most importantly, you try to run this against a non-string type like int or bool.

To explain a bit of the code, you need access to the MetadataWorkspace. I believe that ADO.Net and Linq to SQL all create this object and provide access to it one way or another. From there, you can get EntityType metadata. Other examples online pulled all the entities, but that isn't really necessary. The GetItem function works fine. If the TEntity typ is invalid, this function will throw an error. Once we have the EntityType item, we have the full info about that particular tracked entity, and within the Properties, we can get field data. There are other Facets available depending on the data type. Strings contain these:

  • Nullable
  • DefaultValue
  • MaxLength
  • Unicode
  • FixedLength

So it would be pretty easy to rework this function to provide any one of those meta values.

The TEntity type should be a valid model type tracked by the context, and the field is just a property name. There's probably some fancy-dancy reflection way to eliminate the string here, but I haven't found it yet.

Calling the function looks something like this:

int length = _unitOfWork.Length<User>("UserName");

Again, placing this in the UnitOfWork might not make the best sense. Perhaps in a Repository which contains an actual ObjectSet would be a bit safer.

It should be noted that for other data types, int, bool, floats, etc., different Facets are available, and for some, trying to reference MaxLength will result in an exception being thrown. More information about the actual field type can be found in EntityType.Properties([field]).TypeUsage.EdmType. Greater error checking can, and probably shoud, be put in place.

Friday, September 17, 2010

LINQ and IEquality

Linq provides several handy set functions - Union, Except, and Intersect. They even provide a way to override how equality comparisons are done by declaring a concrete IEqualityComparer.

When working with POCO objects though, it is quite cumbersome to have to provide comparers for every object. True, they could all implement an interface and go through a generic comparer, but a much easier option would be to allow anonymous functions to be defined. We need two things - a comparer class that takes delegates and some Linq extensions to make use of this.

Thankfully, Brendan Enrick already implemented a comparer that takes anonymous functions and runs them in IEqualityComparer.Equals()

/// <summary>
/// Generic equality comparer allowing lambda expressions
/// </summary>
/// <typeparam name="T"></typeparam>
/// <see cref="http://csharpfeeds.com/post/10941/LINQ_Your_Collections_with_IEqualityComparer_and_Lambda_Expressions.aspx"/>
public class LambdaComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, T, bool> _comparer;
    private readonly Func<T, int> _hash;

    public LambdaComparer(Func<T, T, bool> comparer) : this(comparer, o => o.GetHashCode())
    { }

    public LambdaComparer(Func<T, T, bool> comparer, Func<T, int> hash)
    {
        if (comparer == null)
            throw new ArgumentNullException("comparer");
        if (hash == null)
            throw new ArgumentNullException("hash");

        _comparer = comparer;
        _hash = hash;
    }

    public bool Equals(T a, T b)
    {
        return _comparer(a, b);
    }

    public int GetHashCode(T o)
    {
        return _hash(o);
    }
}

Now we can call this as so:

myCollection.Intersect(otherCollection, new LambdaComparer<PocoType>((a, b) => a.Id == b.Id));

Having to declare a new LambdaComparer every time can be further simplified by adding some new Linq extensions that handle creating a new LambdaComparer for us.

public static class LinqExtensions
{
    public static IEnumerable<T> Except<T>(this IEnumerable<T> first,  IEnumerable<T> second, Func<T, T, bool> comparer)
    {
        return first.Except(second, new LambdaComparer<T>(comparer));
    }

    public static IEnumerable<T> Union<T>(this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer)
    {
        return first.Union(second, new LambdaComparer<T>(comparer));
    }

    public static IEnumerable<T> Intersect<T>(this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer)
    {
        return first.Intersect(second, new LambdaComparer<T>(comparer));
    }

    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> first, Func<T, T, bool> comparer)
    {
        return first.Distinct(new LambdaComparer<T>(comparer));
    }
}

This further simplifies the call and makes it a bit more elegant. We can do sets based on aribitary fields without having to declare a whole new comparison class.

myCollection.Intersect(otherCollection, (a, b) => a.Id == b.Id);
myCollection.Union(otherCollection, (a, b) => a.Name == b.Name);

Wednesday, August 25, 2010

Epiphanies

So after four months of ever-so-slowly moving forward on cell blending, I had finally see the light at the end of the tunnel. Just in time to have a sudden inspiration on how to do it all easier (sort of).

The goal: enable heightmap and texture blending between cells.
It sounds innocent enough - just fine affected mesh vertices and blend away! Four months of slow work (broken by countless TF2 sessions and some monumental Minecraft bases), and I've finally got step 1 working. There's still some clean-up work on step 1, but that'll be easier and the motivation is now returning.

The problems: The previous major version did a couple things wrong. I got heightmapping and texturing working over the entire sphere using a Miller projection. This had potential, but lacked in several areas: the 'hexness' of the map was lost and it required a very, very high resolution texture to look decent.

I wanted to maintain the hex feel of the map, yet be able to blend heightmaps to create continuous mountain ranges, sea beds, and other land features. Each cell has the potential to align differently in UV space meaning that seams wouldn't match up easily like a flat 2d grid. The first idea was to add more texture channels to each mesh vertex:
  1. Base cell UV's
  2. Neighbor channel 1 (neighbors 1 & 3)
  3. Neighbor channel 2 (neighbors 2 & 4)
  4. Neighbor channel 3 (neighbors 3 & 6)
  5. Optional detail channel 1 (micro detail - bumpmap, etc...)
  6. Optional detail channel 2 (macro, miller projection - color shifts, shading...)
Then to write the HLSL shader to handle the custom vertex format...

Then to write all the testing tools to ensure that the mesh is being build correctly...

The testing tools allowed me to achieve a nice unwrap of the icosahedron mesh and from there to write a 2d-map importer... This step turned out very useful. I can open up Photoshop, set an indexed color palette, and draw the map over the top of an exported unwrap of the cell points, then read it back into the game.
Finally, the blending step. The problem lies in that each cell is independently textured. This will ensure better resolution when zoomed in without massive textures and help to maintain the hex board game feel. The first plan went something like this:
  1. Create base cells
  2. Build base UV coordinates as a single large hexagon or pentagon
  3. Find and sort neighbor cells
  4. Start building render mesh
    • Subdivide each cell for the render mesh.
    • Lookup neighbor mesh vertices 'affected' by each cell vertex.
    • Take affected vertices appropriate texture channel (each vertex has 4+ texture channels - base, neighbor 1, 2, and 3) and align to the cell edge. This is the part that proved to take the longest and I've still got the texture blending ahead.
The epiphany here is to incorporate polar coordinates for better affected vertex lookup and UV texturing. Hexes bordered by a pentagon have some alignment issues with the pentagons along neighbor seams due to the pentagon's having different base angles. Switching to building mesh UV's off polar coordinates will solve this issue as well as some of the slight texture warping that will occur.

Edit:

Scrap that, polar's didn't work for helping with UV mapping. Things get too funky around the poles.

Random Picture

Wednesday, August 4, 2010

ASP.Net MVC ImageActionLink

The default Html.ActionLink() only takes text, and I like doing icons often to save space for for aesthetics. Here's a simple helper that takes the image url and action as the only required parameters and C# 4.0's fancy new optional parameters for the rest. Doing all the various method overloads for ActionLink sounded like overkill.

/// <summary>
/// Returns an anchor element to the virtual path of the specified action with an image for the content
/// </summary>
/// <param name="html"></param>
/// <param name="imageUrl"></param>
/// <param name="action"></param>
/// <param name="altText"></param>
/// <param name="controllerName"></param>
/// <param name="routeValues"></param>
/// <param name="htmlAttributes"></param>
/// <returns>MvcHtmlString</returns>
public static MvcHtmlString ImageActionLink(
    this HtmlHelper html, 
    string imageUrl, 
    string action, 
    string altText = null, 
    string controllerName = null, 
    object routeValues = null,
    IDictionary<string, object> imageHtmlAttributes = null,
    IDictionary<string, object> htmlAttributes = null)
{
    MvcHtmlString link = html.ActionLink(
        "[replace_me]", 
        action, 
        controllerName, 
        routeValues, 
        htmlAttributes);

    TagBuilder builder = new TagBuilder("img");
    builder.MergeAttributes(imageHtmlAttributes);
    builder.MergeAttribute("src", imageUrl);
    if (altText != null)
        builder.MergeAttribute("alt", altText);
   
    return MvcHtmlString.Create(
        link.ToString().Replace(
            "[replace_me]",
            builder.ToString(TagRenderMode.SelfClosing))
    );
}

Monday, August 2, 2010

XNA 4 Beta

Microsoft's been pushing Windows Phone 7 development quite hard for the past few months. The XNA and Silverlight teams have been pushing out news, examples and generally evangelizing it. I don't have much interest in phone development yet, but the latest version of the Windows Phone 7 SDK included the XNA beta (and is the only place to download it). The team's been hard at work updating XNA for greater cross-platform development and cleaning up some of the oddities in the API. In switching OTD over to the beta, I found a fair number of issues that either haven't been implemented or just moved somewhere new. I actually gave up after a while and started up a new dummy project to walk through the basics and start from a cleaner code base.

Changes

This is by no means a comprehensive list, but a short list of things that I've encountered and my solutions (if any) so far.

  • Content folder is now a separate project by default. Cool, this works, no problem here.
  • BasicEffect no longer uses SetParameter, but rather all parameters are properties.

BasicEffect

Shawn Hargreaves talks about two changes to Basic Effect:

Rendering Sample

Below is a simple object generated from the pipeline and consists of just a VertexBuffer and IndexBuffer. We reference the BasicEffect and Graphics Device statically. View, World, and Projection are much simpler as properties now. Within the effect pass loop, we no longer begin() and end() the effect and passes, but only Apply() each pass at the start. Less code for less mistakes.

The way vertex and index buffers are set on the device has changed as well to a simpler interface. Vertex buffers contain the information of their contained format and a single stream has only on vertex type. We set the length now by number of vertices and indices rather than byte sizes. The call to set the vertex buffer is much simpler.

class Sphere
{
    public VertexBuffer Vertices;
    public IndexBuffer Indices;

     public void Draw(Matrix view, Matrix projection)
     {
         // Set shader projections
         Core.BasicEffect.View = view;
         Core.BasicEffect.World = Matrix.Identity;
         Core.BasicEffect.Projection = projection;

         // Set vertex and index buffers on the device
         Core.GraphicsDevice.SetVertexBuffer(Vertices);
         Core.GraphicsDevice.Indices = Indices;

         foreach (EffectPass pass in Core.BasicEffect.CurrentTechnique.Passes)
         {
             pass.Apply();

             Core.BasicEffect.GraphicsDevice.DrawIndexedPrimitives(
                 PrimitiveType.TriangleList,
                 0,
                 0,
                 Vertices.VertexCount,
                 0,
                 Indices.IndexCount / 3);
         }
     }
 }

Content Pipeline

When building vertex buffers in the pipeline, a few things have changed here as well. Since vertex formats are now embedded in the buffer object, it takes an extra bit of initialization. A new class, VertexBufferContent is now needed.

VertexBufferContent

Building the VertexBufferContent is pretty simple since the information is available in the vertex format - we just need to loop through the VertexElements.

// Generate the VertexDeclarationContent from the VertexElements in our format
VertexDeclarationContent vdc = new VertexDeclarationContent();
foreach (VertexElement ve in VertexPositionColor.VertexDeclaration.GetVertexElements())
{
    vdc.VertexElements.Add(ve);
}

// Set the declaration fors the buffer
Vertices.VertexDeclaration = vdc;

Code

Here's the whole pipeline object that gets processed.

public class SphereContent
{
    public VertexBufferContent Vertices;
    public IndexCollection Indices;

    public SphereContent(int subdivisionLevel)
    {
        Vertices = new VertexBufferContent();
        Indices = new IndexCollection();

        // Generate the vertices
        List<VertexPositionColor> vertices;
        List<int> indices;
        Generate(out vertices, out indices);

        // Generate the VertexDeclarationContent from the VertexElements in our format
        VertexDeclarationContent vdc = new VertexDeclarationContent();
        foreach (VertexElement ve in VertexPositionColor.VertexDeclaration.GetVertexElements())
        {
            vdc.VertexElements.Add(ve);
        }

        // Set the declaration fors the buffer
        Vertices.VertexDeclaration = vdc;

        // Write to the vertex buffer
        Vertices.Write<VertexPositionColor>(
            0, 
            VertexPositionColor.VertexDeclaration.VertexStride, 
            vertices
        );

        // Add indices
        Indices.AddRange(indices);
    }

    private void Generate(out List<VertexPositionColor> vertices, out List<int> indices)
    {
        // Magic!
    }
}

More coming as I explore further.

Saturday, July 31, 2010

Dictionary Stuff

Serialization

Most of the C# Generics are XML serializable. Dictionary, however, is not. It can be extended with IXmlSerializable.

Paul Welter's Example

Tuesday, July 27, 2010

ASP.Net Helper Extensions

Been a while since I've put anything up here, so just tossing a few simple things here.
Helper Extensions! Holy cow things things are nice! Some of these aren't terribly well tested yet, and there should be more coming.

Capitalize

Found this somewhere where I can't remember. Acts like php's ucwords() function in that it takes a string "foo bar" and returns "Foo Bar"
public static string Capitalize(this String s)
{
    return s.ToCharArray().Aggregate(
        string.Empty,
        (working, next) =>
        {
            if (working.Length == 0 && next != ' ')
            {
                return next.ToString().ToUpper();
            }
            else if (working.EndsWith(" "))
            {
                return working + next.ToString().ToUpper();
            }
            else
            {
                return working + next.ToString();
            }
        });
}

Even/Odd

Simple enough and useful for zebra striping tables.
public static bool Even(this int i)
{
    return (i & 1) == 0;
}

public static bool Odd(this int i)
{
    return (i & 1) == 1;
}

Newlines

Useful for displaying input from textareas
/// 
/// Replace line breaks with <br /> tags and encode the output.
/// 
/// 
public static string Nl2br(this HtmlHelper html, string text)
{
    return html.Encode(text).Replace(Environment.NewLine, "<br />");
}

Wednesday, April 28, 2010

KeyedCollection

System.Collections.ObjectModel.KeyedCollection is an interesting class, but with one drawback: it serializes in XNA's IntermediateSerializer, but doesn't deserialize. There're ways to write a custom serializer for this class, but I chose a little bit different route - making my own that doesn't need a custom serializer.

Advantages

  • Different methods can be exposed between the pipeline and runtime versions. This can help make the collection immutable if desired.
  • Hides methods that may not be needed at runtime like adding and removing elements.
  • Provides regular for loop access for speed.

Disadvantages

  • Extra complexity of a new class
  • Have to re-implement any methods

Code

This is an abstract base class that serves solely as a container, provides an iterator, and a method of accessing by key.

abstract class KeyedCollection<T> : IEnumerable<T>
{
    #region Internal Data

    [ContentSerializer]
    private List<T> _collection;

    /// <summary>
    /// Keyed lookup: <Key, Collection Index>
    /// </summary>
    [ContentSerializer]
    private Dictionary<int, int> _lookup;

    #endregion;

    #region Properties

    /// <summary>
    /// Indexer
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    [ContentSerializerIgnore]
    public T this[int index]
    {
        get
        {
            return _collection[_lookup[index]];
        }
    }

    /// <summary>
    /// Number of elements in the collection
    /// </summary>
    [ContentSerializerIgnore]
    public int Count
    {
        get
        {
            return _collection.Count;
        }
    }

    #endregion;

    /// <summary>
    /// Constructor
    /// </summary>
    public KeyedCollection()
    {
        _collection = new List<T>();
        _lookup = new Dictionary<int, int>();
    }

    /// <summary>
    /// Find the neighbor at the given keyed index.
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    public T Key(int index)
    {
        return _collection[_lookup[index]];
    }

    #region IEnumerable

    /// <summary>
    /// Enumerator
    /// </summary>
    /// <returns></returns>
    public IEnumerator<T> GetEnumerator()
    {
        foreach (T element in _collection)
        {
            yield return element;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion;
}

Pipeline compiler directives already included too! It's a bit sparse for now, but a start.

Icons!

Social Networking Icons

p.yusukekamiyamane Fugue Icons

A huuuuuge set of general purpose icons. Not as ubiquitous as the famfamfam icons.

FamFamFam Silk

Entirely ubiquitous now - you see these everywhere.
Silk Companion 1

Friday, April 16, 2010

Shaders!

Learning some HLSL as the plan requires sometime more than what BasicEffect can handle. It doesn't do multiple diffuse textures per vertex. The plan is still very theoretical, and I'm not sure if I'm even smart enough to make it happen.

The screenshots before are simply a Miller projection wrapped around the grid. This means however that the texture border's don't line up to the grid borders. What I want is a more 'cellular' look and feel to it, along with a height map.

Cell Influence

foreach (neighbor in neighbors)
{
    foreach (vertex in neighbor.Vertices)
    {
        Calculate distance and then cell's influence on this vertex
        Cell.NeighborVertices[ neighbor.Index ].Add( Vertex, Influence );
    }
}

Now, we have a list of neighbor vertices that the cell has influence over. The distance and amount can be adjusted like a drawing brush - soft or hard brush. In this step, the neighbor vertices should be given a UV channel that lines up with the cell.

Blended Textures and UV Channels

Each vertex will have a minimum of three UV coordinates as it will have at most, three cell textures to blend (cell brushes shouldn't extend beyond one neighbor). Add in another texture channel or two for normal maps or detail overlays (just a tiled Miller wrap) and soon, every vertex has 5 UV channels. Currently I'm focusing on the first 3.

Paged textures will allow multiple textures to be used and not have to create channels in every cell for each texture. Each neighbor lookup will have assigned to a channels 1 or 2. Then just alternate channels for each neighbor. Except the 12 pentagons... Yeah, going to have to figure out something different there...

Height Maps

The current screenshots show a simple height-map retrieved from the diffuse texture. When moving to a more cellular approach, this kind of height-mapping won't be satisfactory. Instead, a series of small height-maps can be loaded and applied to cells individually. Using the neighbor influence lookup, the map can spill over and scale down into neighboring cell.

Sunday, April 4, 2010

Some Screen Shots

Borders still working. Have it set to wrap a Miller projection to the grid. There's a nasty seem on the back side to figure out. This isn't quite the ultimate goal though - I want a blended grid, not a single texture over the mesh. Height-mapping is working though.

Options:

  • Texture Splatting: more limited textures due to the blending mask. Might need a bit different type of unwrap?
  • Just leave the cell edges: easy, but doesn't look as nice

Sunday, March 7, 2010

Spherical Coordinates in XNA

OTD uses a globe based board. As such, spherical coordinates can be useful. The aim is to eventually be able to load a terrain map from a 2D image file. While I have a ways to go before that, I do have a utility class for managing spherical coordinates. Taking from DirectX's Vector2 and Vector3, I settled on Polar3 as a short yet descriptive stuct name.

Spherical coordinates need three basic elements - the azimuth phi, Φ; elevation theta θ; and a radius, R. Φ runs between 0 and 2π. θ however presents some choices. We can set one of the poles to be 0 and the other π, or set the equator to 0 and the poles to be +/- π/2. I haven't gotten far enough to determine which is the best way, leaving the north pole at 0 appears to be the easiest option when dealing with rotations. We'll see in practice though. Two properties for latitude and longitude can be set up that handle as expected.

Another thing to remember is which direction is up. The equations over at wikipedia and elsewhere place up as the Z axis. However, the default in XNA is Y axis, so Cartesian conversions need to account for this.

/// <summary>
/// Spherical Coordinate
/// </summary>
/// <see cref="http://en.wikipedia.org/wiki/Spherical_coordinates"/>
/// <see cref="http://www.radicalcartography.net/?projectionref"/>
public struct Polar3 : IEquatable<Polar3>
{
    /// <summary>
    /// Angle in the Right, Forward plane
    /// </summary>
    public float Phi;

    /// <summary>
    /// Angle around the Up axis
    /// </summary>
    public float Theta;

    /// <summary>
    /// Radius
    /// </summary>
    public float R;

    #region Latitude and Longitude Properties

    /// <summary>
    /// Latitude in radians
    /// -pi/2 <= theta <= pi/2
    /// </summary>
    public float Latitude
    {
        get
        {
            return MathHelper.PiOver2 - Theta;
        }
    }

    /// <summary>
    /// Longitude in radians
    /// 0 <= phi < 2pi
    /// </summary>
    public float Longitude
    {
        get
        {
            return Phi + (float)Math.PI;
        }
    }

    #endregion;


    #region Constructors

    public Polar3(float theta, float phi, float r)
    {
        Theta = theta;
        Phi = phi;
        R = r;
    }

    #endregion;

    #region Operators

    /// <summary>
    /// Equals
    /// </summary>
    /// <param name="value1"></param>
    /// <param name="value2"></param>
    /// <returns></returns>
    public bool Equals(Polar3 other)
    {
        return ((this.Theta == other.Theta) &&
            (this.Phi == other.Phi) &&
            (this.R == other.R));
    }

    /// <summary>
    /// Equality Operator
    /// </summary>
    /// <param name="value1"></param>
    /// <param name="value2"></param>
    /// <returns></returns>
    public static bool operator ==(Polar3 value1, Polar3 value2)
    {
        return value1.Equals(value2);
    }

    /// <summary>
    /// Inequality Operator
    /// </summary>
    /// <param name="value1"></param>
    /// <param name="value2"></param>
    /// <returns></returns>
    public static bool operator !=(Polar3 value1, Polar3 value2)
    {
        return !value1.Equals(value2);
    }

    /// <summary>
    /// Get the object hashcode
    /// </summary>
    /// <returns></returns>
    public override int GetHashCode()
    {
        return Theta.GetHashCode() + Phi.GetHashCode() + R.GetHashCode();
    }

    #endregion;

    #region 2D Map Projections

    /// <summary>
    /// Returns the xy Miller projection
    /// </summary>
    /// <see cref="http://en.wikipedia.org/wiki/Miller_cylindrical_projection"/>
    /// <returns></returns>
    public Vector2 ProjectMiller()
    {
        Vector2 xy = new Vector2();

        xy.X = Longitude;
        
        /*
         * (5/4) * ln( tan( pi/4 + 2theta/5 ) )
         */
        xy.Y = Latitude * 2 / 5;
        xy.Y = xy.Y + MathHelper.PiOver4;
        xy.Y = (float)Math.Log( Math.Tan(xy.Y) );
        xy.Y = xy.Y * 1.25f;

        return xy;
    }

    #endregion;

    /// <summary>
    /// Create a polar coordinate from a cartesian vector.
    /// </summary>
    /// <param name="vector"></param>
    /// <returns></returns>
    public static Polar3 CreateFromVector(Vector3 vector)
    {
        /*
         * DirectX defines up as (0,1,0), so we need to move some coords around.
         * Theta becomes the vertical angle against the Y axis
         * Phi becomes the horizontal angle in the XZ plane.
         */
        Polar3 coordinate = new Polar3(
            (float)Math.Acos(vector.Y / vector.Length()),
            (float)Math.Atan2(vector.X, vector.Z),
            vector.Length()
            );
        return coordinate;
    }

    /// <summary>
    /// To String
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
        return (String.Format("(Theta:{0}, Phi:{1}, R:{2}, Lat:{3}, Long:{4})", Theta, Phi, R, Latitude, Longitude));
    }
}

Map Projections

ProjectMiller() returns 2D coordinate when using a Miller projection. It needs a little bit of work still, but the coordinates appear to be sound (just needs some offsetting) to map to a 2D image.

Other projections such as Mercator and Mollweide could be added as well.

Notes

Currently all the available values (Phi, Theta) and properties (Latitude, Longitude) are in radians. Given that all other C# functions use radians, this seemed the simplest.

Saturday, February 27, 2010

Simple Settings Manager for XNA

Being able to load settings into a game is a must. There are a number of ways to accomplish this. My current method works well enough for now, although it needs a bit of work in a few areas. I accomplish this by creating a series of settings classes that can be serialized into XML and back again at run time using my simple serializer.

Settings Manager

public class SettingsManager
{
    const string Extension = ".xml";
    const string Directory = "Content\\Settings\\";

    /// <summary>
    /// Loaded settings - Not currently implemented
    /// </summary>
    //SerializableDictionary<string, SettingsBase> _settings;

    /// <summary>
    /// Constructor
    /// </summary>
    public SettingsManager()
    {
    }

    /// <summary>
    /// Load a settings file
    /// </summary>
    public T Load<T>()
    {
        Type t = typeof(T);
        return Load<T>(t.Name);
    }

    /// <summary>
    /// Attempt to load a settings file.  If not found, it will
    /// load a new, default instance of the settings class.
    /// </summary>
    public T Load<T>(string name)
    {
        try
        {
            return Serializer.Read<T>(
                SettingsManager.Directory + 
                name +
                SettingsManager.Extension);
        }
        catch (Exception e)
        {
            e = null;
            Type t = typeof(T);
            return (T)Activator.CreateInstance(t);
        }
    }

    /// <summary>
    /// Save a settings object
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public void Save<T>(T o)
    {
        Type t = typeof(T);
        Save<T>(o, t.Name);
    }

    /// <summary>
    /// Save a settings object
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="file_name"></param>
    public void Save<T>(T o, string name)
    {
        Serializer.Write<T>(
            o,
            SettingsManager.Directory + name + SettingsManager.Extension);
    }
}

The basic idea is that we can load and save settings files either based on an arbitrary name or, for simplicity, the settings class Type.Name. This is still a rough class - it assumes a lot about the settings files being loaded such as the files being serializable. I currently have a simple class that settings files inherit from:

Settings Base

[Serializable]
public abstract class SettingsBase
{
}

More functionality could be added here such as a Save() function that allowed settings objects to save themselves via the SettingsManager. The only special thing in this class is adding the [Serializable] tag at the top to ensure that all inheriting objects get this.

Simple Settings Example

public class PlayerSettings : SettingsBase
{
    public int Id;
    public string Name;

    public PlayerSettings()
    {
        Id = 1;
        Name = "Spartacus";
    }
}

Now, the settings manager can be used to load/save this.

// Load default settings
PlayerSettings ps = settingsManager.Load<PlayerSettings>();

// Change something
ps.Name = "Hercules";

// Save with default name
settingsManager.Save<PlayerSettings>(ps);

Future Improvements

  • SettingsManager does not ensure that loaded files inherit from SettingsBase at all.
  • SettingsManager does not track previously loaded settings files. Tracking and storing loaded files would prevent costly file IO, deserialization and reflection.
  • Settings classes assume that the constructor will set the default settings.

Friday, February 19, 2010

Reading XML Feeds in PHP

PHP has the super simple, and indeed aptly named SimpleXMLElement class.  Give it a string or a url and voila, parsed XML. Except, wait, the arch-nemesis of native PHP objects looms ahead - serialization!

Now, lets add in some extra stuff to help with Google data like like etags and some default versioning headers.


class Feed
{
    /**
     * Etag
     * @var string
     * @see http://code.google.com/apis/gdata/docs/2.0/reference.html#ResourceVersioning
     */
    public $etag;
    
    /**
     * A list of namespaces used in this feed
     * @var array
     */
    public $namespaces = array();
    
    /**
     * Internal xml object
     * @var SimpleXMLElement
     */
    protected $_xml;
    
    /**
     * Default cUrl headers
     * @var array
     */
    private static $default_headers = array(
        'GData-Version: 2',
    );
            
    /**
     * Constructor
     */
    protected function __construct($feed)
    {
        $this->_xml = $feed;
        
        if (is_string($this->_xml))
        {
            $this->_xml = simplexml_load_string($feed);
        }
        
        if ($this->_xml instanceof SimpleXMLElement)
        {
            $this->namespaces = (array)$this->_xml->getDocNamespaces();
            $this->etag = (string) $this->_xml->attributes($this->namespaces['gd'])->etag;
        }
    }
    
    /**
     * Get variables from the xml feed
     * @param string $var
     * @return mixed
     */
    public function __get($var)
    {
        // Allow namespaces to be accessed as $this->namespace->var...
        if (array_key_exists($var, (array)$this->namespaces))
        {
            return $this->_xml->children($this->namespaces[$var]);
        }
        else if ($var == 'namespaces')
        {
            return $this->namespaces;
        }
        else
            return $this->_xml->$var;
    }

    /**
     * Load a feed either from the web or from the cache
     * @param string $url
     * @param string $class
     * @return Feed
     */
    public static function load($url, $class = 'Feed')
    {
        if (!IN_PRODUCTION)
            cache::delete($url);    // Always clear cache in dev mode.
        
        // Check if there is a cached version.
        $feed = cache::read($url);

        // Try and get the feed using the etag.
        try
        {
            $headers = self::$default_headers;
            
            if ($feed !== null)
            {
                $headers[] = 'If-None-Match: '.$feed->etag;
            }
        
            /**
             * @see http://www.php.net/manual/en/function.curl-setopt.php
             */
            $raw_feed = remote::get(
                $url,
                array(
                    CURLOPT_HTTPHEADER => $headers,
                    // CURLOPT_HEADER => true,    // For debugging
            ));
            
            $feed = new $class($raw_feed);
            
            cache::write($url, $feed);
        }
        catch (Exception $e)
        {
            // Catch 304 errors - Content Not Modified
            if ($e->getCode() == 304)
            {
                // Return cached version
                return $feed;
            }
            else
                throw $e;
        }
        
        return $feed;
    }
    
    /**
     * __wakeup - unserialize
     */
    public function __wakeup()
    {
        /*
         * Reload the SimpleXMLElement from the raw,
         * serialized string.
         */
        $this->_xml = simplexml_load_string($this->_xmlRaw);
        unset($this->_xmlRaw);
    }
    
    /**
     * __sleep - serialize
     */
    public function __sleep()
    {
        /*
         * The SimpleXMLElement $this->_xml can't be serialized,
         * so we have to to get the raw xml feed.  However, this
         * instance may still be used after serialization, so
         * we can't just reassign $this->_xml.  We use a temp
         * variable, _xmlRaw, to store the raw feed for
         * serialization.  
         */
        if ($this->_xml instanceof SimpleXMLElement)
            $this->_xmlRaw = $this->_xml->asXML();
        
        $members = array_keys(get_object_vars($this));
        unset($members[ array_search('_xml', $members) ]);
        return $members;
    }
}

One caveat - this is meant to work with Kohana 3, but could be easily adapted to standalone code. With Kohana though, the remote::get() call needs one fix to work correctly. The vanilla code throws an exception on anything but HTTP 200 responses, okay, however the exception does not include the returned status. Google's etag will return 304 if the feed has not been modified. See this issue. The fix is really simple:

Kohana: remote.php

// Close the connection
curl_close($remote);
 
if (isset($error))
{
    throw new Kohana_Exception('Error fetching remote :url [ status :code ] :error',
        array(':url' => $url, ':code' => $code, ':error' => $error),
        $code);
}
 
return $response;

Usage

So now what?

We have an object that can be instantiated, but there's also a static method - load(), that handles caching the feed and checking the etag. We can even have it return an instance that inherits from Feed and does some extra processing to the feed.

load($url, $class = 'Feed')

Static method that loads the feed at the given url. Handles caching the feed and checking against the etag to see if the feed has been updated. This saves you response time

__get($var)

Ah magic methods. This lets us access the SimpleXMLElement as $feed->title, $feed->entry, etc... It also adds a bit to auto-children namespaces. $feed->gdata->id, etc...

$feed = feed::load($url);

echo $feed->title;
echo $feed->gdata->id;
echo $feed->link->attributes()->href;

foreach ($feed->entry as $entry)
{
    // Loop all the entries from this feed
}

Geodesic Map - Borders

Just posting an older picture showing borders working.

The borders are a separate mesh layer drawn over the cells (just turn z-indexing off). While the cell mesh never changes from build time (except changing out texture paging), the border mesh is built entirely at run-time. Given that it has a low frequency of changes, this is okay. The mesh can be built quickly whenever the borders change.

Tuesday, February 16, 2010

Simple Serializer for C#

Nothing too special here, just a utility class to help serialize objects quickly and easily.


public static class Serializer
{
    /// <summary>
    /// Serialize the given object into UTF-8 encoded XML
    /// </summary>
    /// <param name="item"></param>
    /// <returns></returns>
    public static string Serialize(object item)
    {
        if (item == null)
        {
            throw new ArgumentNullException("item");
        }

        using (var stream = new MemoryStream())
        {
            // This constructor is automatically cached, so no need for the
            // local cache.
            var serializer = new XmlSerializer(item.GetType());

            serializer.Serialize(stream, item);
            return Encoding.UTF8.GetString(stream.ToArray());
        }
    }

    /// <summary>
    /// Deserialize the given UTF-8 encoded XML string
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="xml"></param>
    /// <returns></returns>
    public static T Deserialize<T>(string xml)
    {
        using (var stream = new MemoryStream(xml.ToByteArray()))
        {
            // This constructor is automatically cached, so no need for the
            // local cache.
            var serializer = new XmlSerializer(typeof(T));

            return (T)serializer.Deserialize(stream);
        }
    }
}

Usage is pretty simple:

MyClass instance = new MyClass();
Serializer.Write<MyClass>(instance, "instance.xml");

MyClass instance_2 = Serializer.Read<MyClass>("instance.xml");

Edit, 1/30/13


When targeting heirarchies and inherited types, the XML serializer can leak memory. Basically, when you create a new serializer, it will create an assembly at runtime. The basic constructors of XmlSerializer will cache this assembly automatically on their own, but when working with an inherited type (this constructor), it won't be. So, here's an expanded helper that works with inherited types and caches XmlSerializers.


Updated Xml Helper

Wednesday, February 3, 2010

PHP Cache Class

While Kohana has an internal caching function that works quite well, I wanted a bit more robust handling. Namely, I wanted to be able to set a cache file that never expired - something Kohana 3's cache function doesn't handle. Also, I found it awkward to have to pass the lifespan of the cache into read function. I found some sample code where you can play with the touch() function to set an m time in the future. When the m and c times are equal, we have an indefinite cache file

There are only a few situations where you'd want an indefinite cache, and it leaves handling expiration up to the function doing the caching. In my current case, this is reading ATOM feeds from Picasa. The feed headers allow the feed to be queried to see if the feed has been updated.

  • Attempt to get cached feed to get the etag field.
  • Retrieve the feed using the If-None-Match: header
  • Evaluate response:
    • If response is HTTP 304 - content is not modified, use cached feed. In this case the entire feed is not sent back in the response.
    • If response is HTTP 200 - content is modified, take feed and update cache

Code

Much of this is based on Kohana's caching function with some file locking tossed in.
This is an early version - not tested much, beware!
/**
 * Cache helper
 *
 * @package    Cache
 * @author     Levi Baker
 */
class Cache
{
    /**
     * @var directory permissions for the cache directories.
     */
    private static $cache_dir_permissions = 0700;

    /**
     * Cache a resource
     * @param string $name - resource name
     * @param mixed $data - data to be serialized and cached.
     * @param int $lifetime - ttl of the data in seconds.
     */
    public static function write($name, $data, $lifetime = null)
    {
        $file = self::name($name);
        $dir = self::dir($name);
    
        try
        {
            // Ensure that the directory has been created.
            if ( ! is_dir($dir))
            {
                // Create the cache directory
                mkdir($dir, self::$cache_dir_permissions, TRUE);
    
                // Set permissions (must be manually set to fix umask issues)
                chmod($dir, self::$cache_dir_permissions);
            }
    
            // Open the file.
            $f = fopen($dir.$file, 'w');
            
            // Get an exlusive lock on the file while writing
            flock($f, LOCK_EX);
            
            // Write to cach
            if (fwrite($f, serialize($data)) === false)
                throw new Exception('Could not write to cache file.');
            
            fclose($f);    // close and release the lock.
            
            
            // Set lifetime
            if ( ! touch($dir.$file, time() + (int) $lifetime))
                throw new Exception('Could not touch cache file');
                
            return true;
        }
        catch (Exception $e)
        {
            throw $e;
        }
    }
    
    /**
     * Read from a cache resource
     * @param string $name - resource name
     * @return mixed - unserialized resource, or null if not found.
     */
    public static function read($name)
    {
        try
        {
            $path = self::dir($name).self::name($name);

            if (is_file($path))
            {
                // Check if a lifetime was set.
                if (filemtime($path) > filectime($path))
                {
                    // cache file is expired.
                    if (time() > filemtime($path))
                    {
                        unlink ($path);
                        return null;
                    }
                }
                
                // We need a file pointer to do a lock
                $f = fopen($path,'r');

                if (!$f)
                    return null;
                
                // Get a shared lock
                flock($f, LOCK_SH);
                
                $data = file_get_contents($path);
                fclose($f);
                
                return unserialize($data);
            }
            
            // Cache not found.
            return null;
        }
        catch (Exception $e)
        {
            throw $e;
        }
    }
    
    /**
     * Delete a cached resource
     * @param string $name - resource name
     */
    public static function delete($name)
    {
        $path = self::dir($name).self::name($name);

        if (is_file($path))
        {
            return unlink ($path);
        }
        return false;
    }
    
    /**
     * Name - returns the name of the cache file
     * @param string $name
     */
    private static function name($name)
    {
        return sha1($name).'.txt';
    }
    
    /**
     * Directory - returns the full path of the cache file
     * @param string $name
     */
    private static function dir($name)
    {
        $file = self::name($name);
        return Kohana::$cache_dir.DIRECTORY_SEPARATOR.$file[0].$file[1].DIRECTORY_SEPARATOR;
    }
}

While this is setup to run with Kohana 3, it could quite easily be made standalone by modifying the function dir($name) to set the cache directory elsewhere.

Using this class is pretty simple:

// Write to a cache with no expiration
cache::write('resource.name', $my_cool_data);

// Write to a cache with a lifespan of 5 minutes
cache::write('resource.expires', $temporary_data, 300);

// Read data
$data = cache::read('resource.name');

// Check that the resource didn't expire though...
if ($data !== null)
{
    // Do things!
}

Notes

This uses serialize on the data coming in, so be sure that the data being saved is serializable. Some php classes have issues with serialization. Look into the __sleep() and __wakeup() magic functions to extend and enable serialization on objects.

css @import troubles

Interesting read on why the @import rule should be avoided.  Should use <link> instead to help speed up loading and preserve load order.

High Performance Web Sites blog