Knife: a collection of micro-modules

Showcase your libraries, tools and other projects that help your fellow love users.
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: Knife: a collection of micro-modules

Post by airstruck »

scissors61 wrote:Hi, thanks for your library.
I'm experimenting with ecs using your library, which worked pretty well, but I'm having a problem when trying to use knife.system with love.mousepressed because that function requires the arguments "x, y, button, istouch" which I can't find a way to pass them to the entities/system. Is it possible?
Hello, I'm glad it's working for you.

A system's "process" function can take any number of parameters after the component-related parameters. You can do something like this:

Code: Select all

local mouseInputSystem = System(
    { 'isPlayer', 'isAlive' },
    function (isPlayer, isAlive, dt, mouseInfo)
        -- do stuff here
    end)
When applying the system, extra arguments after the initial "entity" argument are passed along to the "process" function.

Code: Select all

local mouseInfo = {}

function love.mousepressed (x, y, button, istouch)
    mouseInfo.x = x
    mouseInfo.y = y
end

function love.update (dt)
    for _, entity in ipairs(entities) do
        mouseInputSystem(entity, dt, mouseInfo)
    end
end
This "mouseInfo" argument is no different from "dt" in most of the examples; it's just an optional pass-through argument. Note that you won't need to do this if your system is defined where "mouseInfo" is in scope (although it's probably better to pass it in, so that systems can be easily reused across projects).
User avatar
scissors61
Citizen
Posts: 76
Joined: Fri Jan 08, 2016 10:16 am

Re: Knife: a collection of micro-modules

Post by scissors61 »

Made it work!
thanks for your explanation. It helped me to find the solution. The problem was that I was using a component in the entities to filter in which gamestate the entity will work, like this: mainMenu = true, but because it was only a filter I didn't add it to the function, which messed up the positions of the parameters.

Code: Select all

buttons.mainMenu.changeStateSystem = system(
{"name", "x", "y", "w", "h", "mainMenu"},
function(name, bx, by, w, h, x, y, button, istouch)
So the solution was:

Code: Select all

function(name, bx, by, w, h, x, y, mainMenu, button, istouch)
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: Knife: a collection of micro-modules

Post by airstruck »

scissors61 wrote:The problem was that I was using a component in the entities to filter in which gamestate the entity will work, like this: mainMenu = true, but because it was only a filter I didn't add it to the function, which messed up the positions of the parameters.
You should be able to prefix it with "-" and omit it from the parameter list, like this:

Code: Select all

buttons.mainMenu.changeStateSystem = system(
{"name", "x", "y", "w", "h", "-mainMenu"},
function(name, bx, by, w, h, x, y, button, istouch)
You can also move it to any position you want; I like to put that kind of thing at the beginning.

See the "sigils" section of the readme (there are some other useful ones listed there).
If an aspect begins with -, the component is suppressed from the arguments list passed to the process function. This is useful for components that only help determine whether to process an entity, but the value of the component is not needed within the process function.
User avatar
scissors61
Citizen
Posts: 76
Joined: Fri Jan 08, 2016 10:16 am

Re: Knife: a collection of micro-modules

Post by scissors61 »

Much better. That's exactly for my case. Thanks.
I have another question. Can I update components values from systems basing only on the parameters from functions?

I want to do this

Code: Select all

{"name", "selected", "x", "y", "w", "h", "-study"},
function(name, selected, bx, by, w, h, mx, my, button)
	if selected == 0 and button == 1 and mx > bx and mx < bx+w and my > by and my < by+h then
		selected = 1
	end
	if selected == 1 and button == 1 and mx > bx and mx < bx+w and my > by and my < by+h then
		for i = 1, #checkbox do
			selected = 0
		end
	end
end)
But because it doesn't work, I have to do this:

Code: Select all

{"name", "selected", "x", "y", "w", "h", "-study"},
function(name, selected, bx, by, w, h, mx, my, button)
	if selected == 0 and button == 1 and mx > bx and mx < bx+w and my > by and my < by+h then
		for i = 1, #checkbox do
			if checkbox[i]["name"] == name then
				checkbox[i]["selected"] = 1
			end
		end
	end
	if selected == 1 and button == 1 and mx > bx and mx < bx+w and my > by and my < by+h then
		for i = 1, #checkbox do
			if checkbox[i]["name"] == name then
				checkbox[i]["selected"] = 0
			end
		end
	end
end)
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: Knife: a collection of micro-modules

Post by airstruck »

scissors61 wrote:I have another question. Can I update components values from systems basing only on the parameters from functions?
You can prefix "selected" with an equals sign, and return the new value from your system. Everything you prefix with "=" must be returned in that same order, even if the values haven't changed.

From the readme:
An aspect may be prefixed with = to indicate that it demands a return value. The process function should return one value for each aspect prefixed with = (even if the value has not changed). The returned values are used to update the corresponding components, allowing systems to mutate components with primitive values. This sigil cannot be combined with pipe-delimited choices.
If you don't like doing it that way, you could use the special "_entity" aspect instead, and then write _entity.selected = 0. I'd recommend using the "=" prefix and return value, though (because ideally I think systems should be able to operate entirely on components and have no knowledge of entities at all).
User avatar
scissors61
Citizen
Posts: 76
Joined: Fri Jan 08, 2016 10:16 am

Re: Knife: a collection of micro-modules

Post by scissors61 »

Sorry, how would I identify the component that was just processed by a system to return it a value? A loop and also return "name" to find the right entity?

Btw, I'm learning programming and lua as I experiment with love2d. Recently I managed to understand what return does (I thought that It returns a table to the global environment).
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: Knife: a collection of micro-modules

Post by airstruck »

scissors61 wrote:Sorry, how would I identify the component that was just processed by a system to return it a value? A loop and also return "name" to find the right entity?
I should have have been more clear, I meant something like this:

Code: Select all

-- prefix some things with "=" (say we also want to change x and y)
{"name", "=selected", "=x", "=y", "w", "h", "-study"},
function(name, selected, bx, by, w, h, mx, my, button)
   -- do your stuff here...
   return selected, bx, by -- always return everything you prefixed with "="
end)
The other thing you could do is this:

Code: Select all

-- _entity gives us the entity
{"name", "_entity", "selected", "x", "y", "w", "h", "-study"},
function(name, entity, selected, bx, by, w, h, mx, my, button)
   -- do your stuff here...
   entity.selected = selected
end)
Btw, I'm learning programming and lua as I experiment with love2d. Recently I managed to understand what return does (I thought that It returns a table to the global environment).
Got you. In some languages you can pass variables into functions in such a way that they can be altered. For example, imagine you could do this in Lua and have it print "2"

Code: Select all

function increase (x)
    x = x + 1
end

local foo = 1
increase(foo)
print(foo) -- prints 1 :(
You can't; it'll print 1, because "increase" just works on its own copy of the value 1. In other words, in Lua, you never really pass variables into functions, you pass values into them. The "increase" function never actually had access to "foo" even though you wrote "increase(foo)." It can't change the value of "foo."

So instead, you could write it like this:

Code: Select all

function increase (x)
   return x + 1
end

local foo = 1
foo = increase(foo)
print(foo) -- prints 2 :)
Anyway, because of that, in System if you want to update a component with a primitive value (numbers, strings, true and false) you have to either prefix them with "=" in the list of components, and return them from the function in the same order, or use the special "_entity" pseudo-component-thing and update its fields.

Also, there's no problem modifying fields of a table that was passed into a function.

Code: Select all

function increase (x)
   x.value = x.value + 1
end

local foo = { value = 1 }
increase(foo)
print(foo.value) -- prints 2 :)
So for example instead of individual x, y, w, h components, you could use a "position" component with x and y, and a "size" component with w and h, and you'd avoid the problem there. For example:

Code: Select all

local updateMotion = System(
    { 'position', 'velocity' },
    function (p, v, dt)
        p.x = p.x + v.x * dt
        p.y = p.y + v.y * dt
    end)
I think the return syntax is really not so bad, though:

Code: Select all

local updateMotion = System(
    { '=positionX', '=positionY', 'velocityX', 'velocityY' },
    function (px, py, vx, vy, dt)
        return px + vx * dt, py + vy * dt
    end)
User avatar
scissors61
Citizen
Posts: 76
Joined: Fri Jan 08, 2016 10:16 am

Re: Knife: a collection of micro-modules

Post by scissors61 »

Hmm, your comments have been helping me to rethink about parameters, returns, tables, values and variables, so great!

It was the _entity option that allowed to modify components through systems, not that I'm unwilling to try the "=selected" sigil, but the behavior is weird and I couldn't make it to work properly.

doing this changes selected to 20

Code: Select all

checkbox.study.checkboxSystem = system(
{"name","=selected", "x", "y", "w", "h", "-study"},
function(name, entity, selected, bx, by, w, h, mx, my, button)
	if selected == 0 and button == 1 and mx > bx and mx < bx+w and my > by and my < by+h then
		selected = 1
	end
	if selected == 1 and button == 1 and mx > bx and mx < bx+w and my > by and my < by+h then
		selected = 0
	end
	return selected
end)
doing this makes it nil

Code: Select all

checkbox.study.checkboxSystem = system(
{"name","=selected", "x", "y", "w", "h", "-study"},
function(name, entity, selected, bx, by, w, h, mx, my, button)
	if selected == 0 and button == 1 and mx > bx and mx < bx+w and my > by and my < by+h then
		selected = 1
		return selected
	end
	if selected == 1 and button == 1 and mx > bx and mx < bx+w and my > by and my < by+h then
		selected = 0
		return selected
	end
end)
I was thinking that if I return something I need a recipient (variable) to store it, but it seems that knife.system doesn't need that, and although I'm not doing it in the right way, it's returning values to the components.

I'm satisfied with the _entity solution, but I guess you don't recommended it that much because it looks a little like oop code, so is better for a ecs to deal only with components. The other solution, the one with a position component, looks cool and it doesn't deal with the existence of entities, but I don't see the logic behind, I mean if "callback = { selected = 1}" is passed to callback, why the much simpler "selected = 1" isn't passed to selected.
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: Knife: a collection of micro-modules

Post by airstruck »

scissors61 wrote:doing this changes selected to 20
Looks like you forgot to remove "entity" from the parameter list, so the "entity" param actually held the value of the "selected" component, and "selected" held the value of "x" (which was 20, I assume).

If you remove "entity" from the parameter list, what you had in that top code snippet should work.

In the second code snippet, you have a possible execution path that doesn't return anything, which would result in "selected" being nil (when neither of those conditions are true, nothing is returned).
I was thinking that if I return something I need a recipient (variable) to store it, but it seems that knife.system doesn't need that, and although I'm not doing it in the right way, it's returning values to the components.
Yeah, ordinarily you would need to do something like that. In this case the library does that step for you.

If you're curious what's actually going on there, you can edit knife/system.lua and add a line with print(source) just before the last line in the file beginning with "return." That will dump the generated code to the console whenever you create a System.
I don't see the logic behind, I mean if this is passed "callback["selected"] = 1" to callback, why the much simpler "selected = 1" isn't passed to selected.
This is what I was getting at earlier. Say you write something like this:

Code: Select all

local function toggleSelection (selected)
    if selected == 1 then
        selected = 0
    elseif selected == 0 then
        selected = 1
    end
end
And then you write something like this:

Code: Select all

local selected = 1
toggleSelection(selected)
print(selected) -- it's still 1
You didn't actually pass your variable selected into toggleSelection, you just passed its value, 1. Inside the function, you have something called selected again, and its value is 1 (until it changes to 0), but it's just internal to that function. It's not the same selected that you passed in. So you can change the value of toggleSelection's internal selected, but it makes no difference to the world outside of toggleSelection.

The reason you can get around this with tables is because tables have fields, and you can change the values those fields hold. So even though the function can't alter the value that an external variable holds, it doesn't need to, because it can alter the value of a field.

In other words, this works...

Code: Select all

local function toggleSelection (selected)
    if selected.value == 1 then
        selected.value = 0
    elseif selected.value == 0 then
        selected.value = 1
    end
end

local selected = { value = 1 }
toggleSelection(selected)
print(selected.value) -- it's 0 now
And conversely, this doesn't work...

Code: Select all

local function toggleSelection (selected)
    if selected.value == 1 then
        selected = { value = 0 }
    elseif selected.value == 0 then
        selected = { value = 1 }
    end
end

local selected = { value = 1 }
toggleSelection(selected)
print(selected.value) -- it's still 1
...because here, we're assigning an entirely new value to selected, which is local to toggleSelection, and that new value ends up in the garbage once toggleSelection ends, just like the first example.

Don't worry, it's difficult to explain properly and probably not easy to understand if you're just getting started, but it should make sense after a while.
User avatar
scissors61
Citizen
Posts: 76
Joined: Fri Jan 08, 2016 10:16 am

Re: Knife: a collection of micro-modules

Post by scissors61 »

Don't worry, it's difficult to explain properly and probably not easy to understand if you're just getting started, but it should make sense after a while.
I'm finding it very interesting, but yeah, I'm struggling a little bit, mostly because, based on your examples, explanations and what I have experimented, the behavior of variables as parameters in functions is not consistent. I'll repeat some of the things you already pointed out just to show the particularity of how these things work:

If you add a variable that holds a value the function will work with a local copy of the value. It won't affect the original variable at all.
If you add a variable that holds a table, it will deal and modify the values of the variable but in a limited way:
  • You can change the values of the fields of the tables, but you cannot delete or modify the fields or the tables or the tables already set up in the variable outside the function.
  • You can add more tables to the fields, and add more fields to the tables.
So, I cannot describe how parameters works with a general statement or logic such as it just makes a copy of the variable, or it just pass what's inside a variable, or the variable itself. To explain it properly it's necessary to point out all the rules and exceptions.
Looks like you forgot to remove "entity" from the parameter list, so the "entity" param actually held the value of the "selected" component, and "selected" held the value of "x" (which was 20, I assume).
Ups, you are right, my mistake, but still it's not working and I think I followed everything you told me, let me show you a love file.
Attachments
return.love
(1.56 KiB) Downloaded 336 times
Post Reply

Who is online

Users browsing this forum: No registered users and 6 guests