I think we’ll do a bit more on our ‘Making App’, the part of our code that helps us make the ‘Shipping App’, the product we ship. (We decide to bless this effort.)
The current Making App, that we’ve been refactoring until it squeals, lists all the class definitions and function definitions from each tab of a Codea project. It’s a good detailed report and it can be quite enlightening.
GameRunner has so many methods that they won’t even fit on the page. How many? Well, we’d have to count them, or make the app count them for us. Or we could do something else, like this:
parameter.action("count GR", function() local ct = -2 for k,v in pairs(GameRunner) do if type(v) == "function" then ct = ct + 1 end end print(ct) end)
This creates a button that will count all the functions defined in the table
GameRunner. Why does it init the count to -2? Because there are two built-in functions that I chose not to count,
__index. When I press the button, Codea prints “60” to the console. 60 methods on GameRunner.
Tile, Monster, and Player are close to that same size. Intuitively, I feel that 60 methods on a class is probably too many, and there are probably objects hiding in there, wanting to be born.
As I scanned the Making app’s display, I noticed this odd blank line:
What could be causing that?
I’ll spare you a page of text where I divided up the input to search for the problem, where I dumped the output to a tab and saw that it looked good, surprising me. Then this happened:
This just in: I just took a short walk “down the hall”, and I think I know what the problem is: those output lines are so long that the text goes past the right side of the screen. I think, but am not certain, that when Codea gets a text item that goes past the right side, nothing displays at all. We can check this quickly with a smaller font or a smaller indent.
Yes. That’s it. Program works, display can overflow. Fine. Revert changes. False alarm, as you were, back to your desks, panic is over.
Was That Time Worth It?
I think so, but not so much I want you to read about it. I learned a little something, and in the end gained confidence in our little Making tool.
Now that we know what it is, we can prioritize a fix, with a story like “Make extra long lines in the class-method output display either partial lines, or fold”.
Note that there are no “as a”, “i want”, nor “so that” lines in this story. We don’t cotton to that stuff around here.
The actual story, on a sticky note, is “long lines in Making output”.
OK, now we were talking about long classes and such. We were in the middle of counting methods in classes. Let’s take a page from Dave’s book and at least generalize the button we just put in.
parameter.text("class_name") parameter.action("Count", function() local ct = -2 for k,v in pairs(_G[class_name]) do if type(v) == "function" then ct = ct + 1 end end print(class_name.." has "..ct.." methods") end)
Now we can put in any class name and get a count of its methods. Easy and possibly useful.
I am surprised to see that Player has 80 methods. We may have a winner here, ladies and gentlemen!
Let’s zoom out a bit and set some priorities.
We need to think about our priorities. We do have a Dungeon “product” to think about, and there are at least a few bits of learning to drag out of that.
In aid of that, but also in aid of all our Codea development, we have GeePaw Hill’s notion of the “Making App”, the collection of code that helps us program and that isn’t part of the actual product, the Shipping App.
From a Dungeon product viewpoint, it’s probably time to turn our attention back to adding features to the dungeon. That said, I suspect that most of us are a bit bored with it, mostly because I think up some possibly difficult feature, and then it goes in just fine.
Occasionally I do confuse myself, and I know that some of you find that amusing, but it’s really not worth the low low price of admission.
So … I’m going to declare a time out, and work on the Making App side of things, using the Dungeon program as our test client. To that end, I’m going to create a new article Category and retroactively use it in these last few articles.
And I’ll retitle articles from here on, while we focus on our Making app.
What Do We Want
We’re resetting our minds for a while, from “Build a Dungeon Program”, to “Build a Codea Making Program (using the Dungeon program as a subject).”
Why are we doing this? Well, because I want to, because it will help with the Dungeon program, and because the things we’ll do in our Making App will be more generally useful in building Codea programs. At least we hope that it will.
When I start something new, I like to think a lot about what I want. And I am mindful of the song, expecting that we can’t always get what we want, but we might find that we get what we need.
Today I went back and forth between the Making program and the Dungeon program a half dozen times, getting information, testing changes, and so on. It was a pain. You have to exit one project in Codea and then enter another. It would be easier if you could run more than one Codea instance, but you can’t, at least not today. It would be easier if the Making App was a real App, compiled to XCode, but it isn’t–and it certainly won’t be, at least not any time soon.
A very tempting yak to shave, but no.
We could do as we did with CodeaUnit, which is actually bound into the Dungeon program, rather than linked in as a dependency. I did that per a request from “UberGoober”.
But as a rule, binding more code directly into our app is not a good thing. If nothing else, it will fill up memory. And in a real app, we usually don’t want to ship our toolset with the app.
But I think we want the Making App to be run as part of the Dungeon App. So we’ll compile bits in, and we’ll be working to make it able to be brought in via dependency rather than compiled in.
Even that will be awkward, however. Suppose we are coding along in the Dungeon program and suddenly we think we want to see whether we’ve added globals, and suppose we don’t, as yet, have a tool in Making that can do that. We’d still have to exit Dungeon, go into Making, and write the tool. Or we could write the tool in Dungeon, making it extractable, and when it works, pull it out and integrate into Making.
So, it’ll be awkward sometimes. We’ll try to devise ways to make it as easy as we can. We will call those attempts “learning” and award ourselves gold stars for them.
Getting What We Need
For a while, I could just bind the Making code into Dungeon, tests and extra tabs and all. Until memory explodes or something equally bad, that should work fine.
(It occurs to me that there will be some components of the Making App that will be quite unique to Tiled dungeons. Maybe we don’t care, maybe we do. Today, we can’t select the tabs we load from a dependency.)
The Making App has its own tests and sample tabs. When we’re working on it, we will sometimes want to refer to those samples, and may then want to quickly plug our changes back into D2 to use them. Normally, I’d put up with a lot of that. I think most teams will put up with tools that are awkward to use, because they can’t find the time (due to pressure) to improve the tools.
Chain Saw MassacreXXX Metaphor
It’s kind of like your chain saw runs out of gas and the foreman won’t let you take time to fill its tank. Productivity goes way down, but at least you’re keeping busy.
I think the main thing will be to make this thing useful in the context of D2, so I’m going to move its tabs wholesale into the D2 project. We’re going to have an issue with drawing the output, but with luck it won’t be too awful.
Let’s begin by renaming the tabs to make them easier to spot.
I determine that the “Uncommented” tab is no longer needed, it was the golden result for the old comment removers and we have new ones and new tests now.
Now, you know what I’d like to have? I’d like to have the ability to move these tabs automatically into another project.
Let’s make sure that D2 is at a clean commit and then do this with code instead of the old copy-paste.
This copying feature is clearly a feature of the Making app, so we’ll put it into it.
I don’t quite see how one TDD’s this, but I do think I’ll make a fake project to use as a target.
Automatic Tab Copier
I’m going to spare you again, this time a lot of experimentation, most of which was perhaps educational but mostly frustrating.
Codea has a number of facilities for accessing what it calls “assets”, which include the tab of your project. Each project is in a file folder, or a near approximation to a file folder, and there are various arcane ways of working out what’s in there.
There are, however, some very simple and obvious methods one could use:
saveProjectTab, which, well, read and save the text from project tabs. We’ve seen the read method in the code for the Making App, reading tabs and analyzing their text.
It should be, and is, pretty easy to copy tabs from one project into another. In fact, here’s a prototype that does it, which for reasons I’ll explain, took well over an hour to write.
It’s set up to run as a button and a text parameter in the Making App:
parameter.text("target_project") parameter.action("copy to target", function() if #target_project < 1 then print("No project") return end local targetProjectName = target_project local exists = listProjectTabs(targetProjectName) if #exists < 1 then print(targetProjectName.." doesn't exist") return end local tabs = listProjectTabs() for i,tabName in ipairs(tabs) do print(tabName) if tabName:find("^M_") == 1 then print("reading "..tabName) local contents = readProjectTab(tabName) local targetName = targetProjectName..":"..tabName print("writing "..tabName.." to "..targetName) saveProjectTab(targetName,contents) end end end)
Basically what this function does is find all the tabs starting with “M” in the current project, read, and save each one to a tab of the same name in another project. It copies M tabs into the target project.
Let me show you how it looks.
Above is a project named MTarget, containing two tabs, Main and AlreadyHere.
Here’s the Making App, with its tabs:
I’ve decided, for now, that all the tabs we have will be moved over, so they are all named M_something. In the future, I expect to have some tabs in the Making App that can b copied over for use inside your project, and others that are for use only outside.
Here’s Making, running. Note that I’ve filled in the text field with MTarget. This picture is taken after I’ve pressed the button to move the tabs.
We can see that the console traces what is being done. Now let’s open MTarget again:
The tabs have been moved.
Now, mostly to get my own head together, let me speak to why this took so long, well over an hour.
Short form: I went down a rathole. Maybe even two.
There are the read and save project tab functions, but there are also more generic readText and saveText, that can access any (text) file by its name. And something in the Codea forum led me to think that these functions were less likely to change or be deprecated, and that I should use them.
Nothing good came of that. The internal asset names are arcane. The tab Foo in a project will be some very long folder path starting on Arturus, and will come down to “Foo.lua”. The only way I could find to tell a tab from a random file was the “.lua”. Constructing new legal path names was a nightmare, and I never got close to anything that I felt was going to be trustworthy.
So I went back to the obvious way, and will deal with any changes if and when they come along. After all, it isn’t even 100% clear that we’ll want this tab-copying capability at all, and we’ll surely be changing our needs as we learn more about what we want the Making App to do.
So that, long story more or less not quite so long, is how my Saturday went.
Was it worth it? I learned a lot of things not to do. That’s valuable. But frustrating? Oh yes.