Ok, the "lazy evaluation" method worked superbly. I was surprised that that it worked almost flawlessly on the first compile. Xml is parsed only once and the playlist datastructure is handed off to an array of anonymous functions which do all the work outside of the xml parser. Playlist creation time through the pml parser is negligable compared to what it was. Large playlists which took 45+ seconds to generate using the old parser now complete in 1-2 seconds which is obviously a major improvement.
I've lost you a bit - how is this any better than hardcoding the common case with ifs?
Or are you talking about memoisation of the processed data?
(Pardon, I'm not much into C#, but I do know enough about dynamic languages due to using Python a lot)
I'd try a hash table.
You'd have to create a good and fast hash from the name of the file. There are some viable examples available in the literature. Bonus points for keeping file name locality while not colliding too often.
Maybe even a C# hash table would be small and fast enough.
What's getting memorized is the collection of anonymous functions which actually generate the html using whatever playlist data it is handed. Providing the user doesn't switch playlist templates (which would result in the old function list getting discarded and a new one generated), the steps required to render a playlist will be identical regardless of the contents of the playlist. The method I used to do this was actually pretty interesting as it takes advantage of most of the new optimization features in the latest C# spec.
My anonymous functions are prototyped as such:
private delegate string PMLTask<T>(T param);
This basically states that each step of the playlist rendering process can possibly take a value of an arbitrary datatype (ie tracklists, tracklist groupings, the playlist, etc), and will return a string (our html chunk).
When I go to parse the xml, a generic list is initialized to hold these functions:
List<PMLTask<object>> local_tasks = new List<PMLTask<object>>();
For every loop structure I encounter in the pml, I create a list of these and add another PMLTask to process one of these lists creating the ability to perform recursion:
local_tasks.Add(delegate{
string html = "";
foreach (SkipList<Track> tracklist in this.groups){
foreach(PMLTask<object> task in loop_tasks){
html += task(tracklist);
}
}
return html;
});
At the very end I process the list:
foreach (PMLTask<object> task in tasklist){
html += task(null);
}
Which works on whatever playlist is currently stored in memory. When the playlist is altered, only the very last step needs to be done again. Hence our nifty cached instruction set. . When the user decides to load a different pml file, this cache is cleared out and a new instruction set is created.
Now that that is out of the way, I am running in to another issue which may end up being a show stopper. The problem is that using the gecko# wrapper which is what allows me to embed the gecko engine, I do not have access to the DOM once the html is rendered. This is a limitation of the bindings unfortunately and most work being done to overcome this is pretty immature atm. I was hoping to be able to dynamically interact with the DOM direct from c# to handle any number of tasks but as it stands this is impossible. I am currently experimenting with a home brewed xpcom wrapper that someone else wrote in order to bridge c# <-> javascript but I don't know how that's going to turn out yet. These kind of hacks make me really uncomfortable so I may end up looking for alternatives. Anyone have any suggestions on how to go about this?
In the process of developing this, I've gotten a buddy of mine interested in the project, and I may actually push this into a prototype for a real media player at some point if I can maintain this pace. More to come on that.