There’s another kind of “iterator” possible in SLua. You pass it a function, which it applies to each of the elements one after another. Let’s see what that might be like.
Since I haven’t done this before, or at least not in SLua, or at least not lately, or if I have, I’ve forgotten now, I’ll start with something simple, in a test.
Since we have the Polyline class as a sample from yesterday, let’s see if wee can devise a test for this proper iterator.
function Tests:test_proper_iterator()
local poly = Polyline({10, 20, 30, 40, 50})
local total = 0
local function sum_up(n)
total += n
end
poly:do_function(sum_up)
Tests:assert_equals(total, 150)
end
I think we can make this work quite easily:
function Polyline:do_function(f)
for _, v in self._contents do
f(v)
end
end
The test runs correctly, first try. I can hardly believe that, so I change the test to check for 151.
Actual: 150, Expected: 151.
Tests:test_proper_iterator
So that was easy enough. Let’s try building up a table of distances by using our pairwise
method.
function Tests:test_polyline_distances()
local poly = Polyline({10, 20, 50, 55, 70})
local expected = { 10, 30, 5, 15}
local actual = {}
local function add_dist(p1, p2)
actual[#actual+1] = p2-p1
end
poly:do_pairwise(add_dist)
self:assert_equals(actual, expected)
end
The function add_dist
, if applied to (10,20), (20,50), and so on, should produce the differences in expected
. ANd it does, with this code:
function Polyline:do_pairwise(f)
for i, p1, p2 in self:pairwise() do
f(p1, p2)
end
end
So, this is interesting, ,but what use is it? Well, we could use the idea in PolylinePath, which is an object we will probably actually use. We could even use the Polyline class as part of the PolylinePath class and use it that way.
The PolylinePath has a sort of odd property: it contains a series of points which we plan to travel between, and it will someday contain some kind of rotation information. And, it contains information derived from the points series, the distances spanned by each pair. We can imagine a PolylinePath that didn’t have that information saved, but just calculated the distances as needed. It would work the same, save some storage, and be notably slower. Since our movers execute many times per second and often fetch more than once, speed is probably more important than memory.
I am developing this code on my Mac, with a Luau compiler, and the other day I created a table with ten million items in it, which the Mac didn’t even sneeze at. SLua is not likely to have quite that generous a memory space, and we may desire code that is both fast and compact. The conflict that I feel is that, for me, small objects are easier to understand and build with, but we need to at least be conscious of the impact.
For example: suppose that we have a path of some kind that includes a lot of points and associated rotation information. The “natural” way to represent something like that would probably be with a tiny class containing the point and rotation, and in the most convenient form for the processing code, which might well be vector
and rotation
. But a vector
is a table of three numbers, a rotation
is a table of four numbers, and putting them together into an object allocates another table of at least two items (only if you’re careful). And we’d have those in a table, hundreds of them.
At base, seven numbers will take up 56 bytes, the overhead of three tables per each is probably at least 24 more. We’re perilously close to 100 bytes per item. Because we know reasonable limits on the values, we have been known to store them in strings, especially since LSL lists have 12 bytes of overhead per element. In SLua we could mack things into byte strings or such.
That way, in my view, lies madness, especially in these early days. We’ll work toward good, nearly idealized design, and then, when we really understand the situation and the shape of the code, we’ll see where to scrunch it.
I think it likely that we will not store more than a few points and rotations at a time. We will keep defined paths in Link Set Data, in some form that we can quickly convert to what we want internally. We can spend lots of effort producing that form, making it just what we need for unpacking, since we write it once and read it many times.
The SLRR looks ahead and creates its path as it goes, and it ditches previously used path elements quickly. How does it back up? Very nicely, thanks for asking.
How much of all the work I’m doing will we need? Well, I think we’ll benefit from my experience more than for any specific code. However, I do think that we’ll find it desirable to convert from Bezier to PolylinePath as part of SLRR, and that will require at least a rudimentary Pathfinder notion to select from the path. The Pathfinder will want to be improved to serve as the pipeline for Beziers coming in, being traveled, and being forgotten.
Constructs in code pay off in a number of ways, including providing a convenient way to do something that is done often, providing flexibility in changing things in a few places rather than many, and in hiding complexity so that the bulk of our code can be more clear (and thus easier to change).
Programmers often seem to formulate rules of nature based on their own limited experience. They love this and hate that, often because they know this and do not know that. And they quickly decide this is good and that is bad. THat’s not quite reality.
However, reality does suggest that we should provide code and ideas in a form that our users, our audience, are best able to deal with.
I think that the little objects I’m working on now will not be used with iterators by outside users. We will iterate the polyline in pairs, internally, in creating the distance table. We will iterate the distance table, internally, to find the points and rotations we need. We’ll iterate groups of Bezier curves to create PolylinePaths, again internally.
Externally, I think those objects will be used in approximately one way: given a distance d
provide the point and rotation to use to move to that distance.
In short, I think all of this is likely under the covers. My heirs and assigns may have to do maintenance after my inevitable and tragic demise, but with any luck that’s a long way off. And well-organized not-very-tricky code will be what they need. And, frankly, it is what I need as well.
So will we keep the iterators? Maybe. Quite probably not. Will I be glad to know how to do them? I already am.
Today we produced trivially simple iterators that apply a function to each of the elements of a collection, singly, or pairwise. They were really quite simple, once you get the idea of passing a function in and calling it.
We could extend our iterators to work with methods rather than naked functions, to understand self
and so on. Perhaps one day we’ll do that. Perhaps not. For now, I think the iterator topic can rest.
Safe paths!