[Home] [Downloads] [Search] [Help/forum]

Gammon Software Solutions forum

See www.mushclient.com/spam for dealing with forum spam. Please read the MUSHclient FAQ!

[Folder]  Entire forum
-> [Folder]  MUSHclient
. -> [Folder]  Lua
. . -> [Subject]  Comparing a function to another
Home  |  Users  |  Search  |  FAQ
Username:
Register forum user name
Password:
Forgotten password?

Comparing a function to another

[Reply to this subject]  Reply to this subject   [New subject]  Start a new subject   [Refresh] Refresh page


Pages: 1 2  

Posted by Ksajal   (17 posts)  [Biography] bio
Date Sun 25 Apr 2010 08:56 PM (UTC)  quote  ]
Message
I am trying to find a way to queue function to be executed at the prompt. So far, it looks like this:

function prompt:queue (f)

self ["queued"] [#self ["queued"]+1] = f

end --function

I use it like this:

prompt:queue (function () affs:add ("affliction")end)

I execute the function stored like this:

for k, func in ipairs (prompt ["queued"])do
func ()
end --for

I need to find a way to check if I have already queued a function. The only way that I can think of is supplying an extra argument to the queue method, an unique id for the function queued so I can compare it with other queued functions.

Any other ideas? This seems rather difficult, as I need to remember all the function ids that I have supplied each time I use the queue for the same function.
[Go to top] top

Posted by Twisol   USA  (2,230 posts)  [Biography] bio
Date Reply #1 on Sun 25 Apr 2010 09:24 PM (UTC)  quote  ]
Message
So basically you have a prompt "service", and functions can be "registered" to be executed on each prompt? And you also want to be able to tell if a function has been queued. Well, it's not too hard. :) Here's an example of what I might do (and have done before):

local queue = {}

function Register(func)
  if queue[func] then       -- check if it's in the queue
    return nil, "Already queued"
  end
              -- position, callback
  local entry = {#queue+1, func}
  table.insert(queue, entry) -- adds to the end of the queue
  queue[func] = entry        -- marks that it's in the queue
end

function Unregister(func)
  if not queue[func] then
    return
  end
  local entry = queue[func]     -- get the entry back
  table.remove(queue, entry[1]) -- get the position and dequeue it
  queue[func] = nil             -- mark that it's not in the queue
end

function DoStuff()
  local next_entry = table.remove(queue, 1) -- get from the front
  local func = next_entry[2]
  
  -- do stuff!
  
  -- make sure to update the positions of the rest of the entries
  for _,entry in ipairs(queue) do
    -- shift the position marker
    entry[1] = entry[1] - 1
  end
end



It looks a little complex, but it's not. A queue like this would look something like this in memory...


queue = {
  [1] = {1, function_foo}, -- entry 1
  [2] = {2, function_bar}, -- entry 2
  [3] = {3, function_baz}, -- entry 3
  
  [function_foo] = {1, function_foo} -- same table as entry 1
  [function_bar] = {2, function_bar} -- same table as entry 2
  [function_baz] = {3, function_baz} -- same table as entry 3
}


Every time you take an item off the queue, you make sure that the rest of the entries' positions are updated, so you can basically just use table.remove(queue, queue[function_foo][1]) to remove the entry wherever it is now.



This is how you say you're using it now:

prompt:queue (function ()
  affs:add ("affliction")
end)


You create the function and pass it to the queue in one go. But this gives you no way to refer to the function again, as you said. You'd want to do this instead:

function_foo = function ()
  affs:add ("affliction")
end

prompt:queue(function_foo)


That lets you use some "prompt:unqueue" function later, just passing in the original function:

prompt:unqueue(function_foo)



Also, since you seem to be calling all of the queued functions at once, you probably don't need to update the entries' positions in the "DoStuff" function, because the queue will be empty afterwards anyways. It's just a general example of how to do a listener queue. :)

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Ksajal   (17 posts)  [Biography] bio
Date Reply #2 on Sun 25 Apr 2010 09:45 PM (UTC)  quote  ]

Amended on Sun 25 Apr 2010 10:09 PM (UTC) by Ksajal

Message
I like the code.

But this means I will still have to create an unique name for each function I am registering.

And you forgot to delete the variable containing the function from the memory when you unregister the function.

EDIT:
I think I'm gonna use it like this, in case an unique name for each function is the only way:

function prompt:queue (f, id)

for k, v in ipairs (self ["queued"] do
if v==id then
return
end --if
end --for

self ["queued"] [#self ["queued']] = id
self ["ids"] [id] = f

end --function

I only need to use it in a for loop:

for k, unique_id in ipairs (prompt ["queued"]) do
self ["ids"] [unique_id] ()
end --for

I'm only storing the function in one place.

Thank you for the help!
[Go to top] top

Posted by Twisol   USA  (2,230 posts)  [Biography] bio
Date Reply #3 on Sun 25 Apr 2010 10:17 PM (UTC)  quote  ]
Message
Ksajal said:
But this means I will still have to create an unique name for each function I am registering.

If by "name" you mean variable, then yes. If you're giving away a function with no way to refer to it later, there's no reason to expect to be able to deregister it.

Ksajal said:
And you forgot to delete the variable containing the function from the memory when you unregister the function.


I don't see what you mean, sorry. I table.remove and set to nil in Deregister.

Oh, I see. In DoStuff, for the one being used.


Your string ID works, certainly, and I'm glad you have a solution. Different functions will never clash, though, while anyone can pass in an already-used string ID. ;)

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Nick Gammon   Australia  (19,500 posts)  [Biography] bio   Forum Administrator
Date Reply #4 on Sun 25 Apr 2010 10:37 PM (UTC)  quote  ]
Message
Ksajal said:

But this means I will still have to create an unique name for each function I am registering.


What is the problem exactly? You can compare functions directly.


print (print == print) --> true
print (print == tostring)  --> false


- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Ksajal   (17 posts)  [Biography] bio
Date Reply #5 on Sun 25 Apr 2010 10:50 PM (UTC)  quote  ]
Message
Twisol said:

Your string ID works, certainly, and I'm glad you have a solution. Different functions will never clash, though, while anyone can pass in an already-used string ID. ;)

I'm usually calling prompt:queue from other functions, like this:

function affs:add_queue (name, val)
prompt:queue (function () affs:add (name, val)end, "affs_add_"..name)
end --function

Or if I need to do some very special stuff:

prompt:queue (function () some stuff end, "user_"..tostring (os.time ()))

They won't ever clash, me thinks.

Nick Gammon said:

What is the problem exactly? You can compare functions directly.

I've tried in my case, it doesn't work

one = function () Note ("") end
two = function () Note ("") end
Note (one == two)-->false
[Go to top] top

Posted by Nick Gammon   Australia  (19,500 posts)  [Biography] bio   Forum Administrator
Date Reply #6 on Sun 25 Apr 2010 11:00 PM (UTC)  quote  ]
Message
Ksajal said:


Nick Gammon said:

What is the problem exactly? You can compare functions directly.

I've tried in my case, it doesn't work

one = function () Note ("") end
two = function () Note ("") end
Note (one == two)-->false





They are two functions that happen to do the same thing.


one = function () Note ("") end
two = one
Note (one == two) --> true


In your case, stop inlining the functions if you can. eg. instead of:


prompt:queue (function () affs:add ("affliction")end)


do:



-- define function once
function add_affliction () 
  affs:add ("affliction")
end

-- queue it if required
if something then
  prompt:queue (add_affliction)
end -- if


By only defining functions once, you now can just test if they are the same.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Ksajal   (17 posts)  [Biography] bio
Date Reply #7 on Sun 25 Apr 2010 11:16 PM (UTC)  quote  ]
Message
I think I understand what you mean, but I have hundreds of afflictions, hundreds of cures, hundreds of defenses, and so on, declaring a function for each is just too much.

I've tested my unique ids table solution, it works exactly as it should so far, I'm gonna stick with it.
[Go to top] top

Posted by Twisol   USA  (2,230 posts)  [Biography] bio
Date Reply #8 on Sun 25 Apr 2010 11:30 PM (UTC)  quote  ]

Amended on Sun 25 Apr 2010 11:32 PM (UTC) by Twisol

Message
Ksajal said:
I've tried in my case, it doesn't work

one = function () Note ("") end
two = function () Note ("") end
Note (one == two)-->false


Well, of course. They're two separate instances of a function. In other languages, you wouldn't expect for "new Object() == new Object()".

If you want a really messed up way to do it you can do this. Nick's going to kill me just for suggesting such an outlandish approach:

string.dump(function() end) == string.dump(function() end)


I can't test it right now - posting from my phone - but that should work for most simple cases. I can't guarantee it will if you start throwing in upvalues and modifed environments (setfenv), but it would be interesting to experiment with.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Twisol   USA  (2,230 posts)  [Biography] bio
Date Reply #9 on Sun 25 Apr 2010 11:37 PM (UTC)  quote  ]
Message
Ksajal said:

I think I understand what you mean, but I have hundreds of afflictions, hundreds of cures, hundreds of defenses, and so on, declaring a function for each is just too much.

I've tested my unique ids table solution, it works exactly as it should so far, I'm gonna stick with it.


You're not the first to write a curing system, and others have dealt with that problem already, more or less. Store the functions in your own tables, so they're not loose. Look at ACS2, a somewhat outdated system for Achaea, to see how they've done some of this. I don't recommend it for style or best practices, but it does work...

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Nick Gammon   Australia  (19,500 posts)  [Biography] bio   Forum Administrator
Date Reply #10 on Mon 26 Apr 2010 01:33 AM (UTC)  quote  ]
Message
Twisol said:

If you want a really messed up way to do it you can do this. Nick's going to kill me just for suggesting such an outlandish approach:

string.dump(function() end) == string.dump(function() end)




Apart from the aesthetic considerations, it just doesn't work:


s1 = string.dump (function () print "hi there" end)
s2 = string.dump (function () print "hi there" end)
 
print (s1 == s2)  --> false


s1 = string.dump (function () end)
s2 = string.dump (function () end)

print (s1 == s2)  --> false


Ksajal said:

I think I understand what you mean, but I have hundreds of afflictions, hundreds of cures, hundreds of defenses, and so on, declaring a function for each is just too much.


But you have hundreds of something, right? Because you are testing if one is equal to the other?

Generally when you seem to have hit a limitation of the language you need to revisit what you are doing. For example, if you are processing afflictions, and you want to queue up a cure, do a simple keyed lookup (or sequential lookup) of your queue of things you are planning to cure, to see if it is there already. But not by trying to compare functions per se. Stick to affliction names or cure names, or something more high level.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Ksajal   (17 posts)  [Biography] bio
Date Reply #11 on Mon 26 Apr 2010 11:32 AM (UTC)  quote  ]

Amended on Mon 26 Apr 2010 01:54 PM (UTC) by Ksajal

Message
Nick Gammon said:

Generally when you seem to have hit a limitation of the language you need to revisit what you are doing. For example, if you are processing afflictions, and you want to queue up a cure, do a simple keyed lookup (or sequential lookup) of your queue of things you are planning to cure, to see if it is there already. But not by trying to compare functions per se. Stick to affliction names or cure names, or something more high level.

This is what I am doing:

-I have a vector with all the afflictions, in the order I want to cure them, from the most dangerous to the least dangerous.
-I have a module affs which adds the affliction when I get the affliction message: affs:add (affliction) (the new affliction is stored in the the affs ["current"] table).

The line for an affliction can be an illusion (another player used a skill to make you see the line). And if it is an illusion, there is a possibility that you detect the illusion before the prompt. You detect it based on a % chance (there is a skill), or by checking against different things (like if the illusion was supposed to prone you, you can check for prone at the next prompt-the prompt shows without a doubt when you are prone-, and a few other means).

Sometimes you can be certain that an affliction line is not an illusion (for that I use affs:add), and other times you cannot (I use the queue for that).

I am not using the queue only for afflictions and I sometimes need the queue to execute a set of functions that are only used with that trigger, like this (just an example):

prompt:queue (function () EnableTrigger ("name") affs:add ("affliction") Note ("AFFLICTION")end)

And I need to be sure I am not queued the same function twice.

When I detect an illusion, I delete certain queued function (for that I use another table entry in the prompt queue), which I previously marked for deletion when I stored them into the queue (this is the previous function, marked for deletion):

prompt:queue (function () EnableTrigger ("name") affs:add ("affliction") Note ("AFFLICTION")end, "delete_it_if_illusion")

That is a little besides the point, the problem still remains, I need an unique name for the function for both comparing with previous entries in the queue, and for storing it for removal in case of an illusion. Problem which I solved by assigning an unique id for the function queued, like this ("affs_add_paralysis" is the id):

prompt:queue (function () affs:add ("paralysis") end, "affs_add_paralysis", "delete_it_if_illusion")
[Go to top] top

Posted by David Haley   USA  (3,881 posts)  [Biography] bio   Moderator
Date Reply #12 on Mon 26 Apr 2010 02:01 PM (UTC)  quote  ]
Message
All of your operations appear to be data-driven, meaning that you can define the operation based on the data it operates on, and you are not forced to define it as the functional operation itself.

It's unclear to me why (or even if) you need to queue whole functions and not the data they're operating on. For example, why this:

prompt:queue (function () affs:add ("paralysis") end, "affs_add_paralysis", "delete_it_if_illusion")

and not this:
prompt:queue ("paralysis", "delete_it_if_illusion")


(I'm not sure why you need the other parameter so I'm leaving it there for now.)

If you need to have a case where the note is printed, you could do:
prompt:queue ({aff="paralysis", print_note=false, enable_trigger=false}, "delete_it_if_illusion")

and
prompt:queue ({aff="paralysis", print_note=true, enable_trigger=true}, "delete_it_if_illusion")

etc.

Now you can easily test equality of two affliction chunks. After all you are trying to store data in your queues, not whole functional operations.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
[Go to top] top

Posted by Ksajal   (17 posts)  [Biography] bio
Date Reply #13 on Mon 26 Apr 2010 02:18 PM (UTC)  quote  ]

Amended on Mon 26 Apr 2010 02:36 PM (UTC) by Ksajal

Message
Example of uses:

--the function
function prompt:queue (f, unique_id, if_I_remove_it_in_case_of_illusion)

for k, v in ipairs (self ["queued"] do
if v==id then
return
end --if
end --for

self ["queued"] [#self ["queued']] = id
self ["ids"] [id] = f

if if_I_remove_it_in_case_of_illusion then
self ["check"] [id] = true
end --if

end--function

--uses
prompt:queue (function () system:cured_asleep ()end, "cured_asleep", "remove_it_if_illusion")

--or

prompt:queue (function () system:cured_asleep flags:add_check ("recklessness") ()end, "some_unique_id", true)

--or

prompt:queue (function ()
  system:cured ("slitthroat")
  if bals:get ("elixir")==0.5 and not string.find (flags:get ("applying_salve") or "nil", "health") then fst:elixir () end
  if bals:get ("purg")==0.5 then fst:purg ()end
  if bals:get ("herb")==0.5 then fst:herb ()end
  end, "another_unique_id", true)

--or

prompt:queue (function () defs:lostdef (defense) end, "defs_lostdef_defense", true)

I'm not only storing affliction names.
[Go to top] top

Posted by David Haley   USA  (3,881 posts)  [Biography] bio   Moderator
Date Reply #14 on Mon 26 Apr 2010 02:36 PM (UTC)  quote  ]
Message
My suggestion works with more than just names, but it does seem like your functions are complex enough to not be just data. But then the question is why exactly can't you give these functions proper names or at least values that you can directly compare?

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


5,332 views.

This is page 1, subject is 2 pages long: 1 2  [Next page]

[Reply to this subject]  Reply to this subject   [New subject]  Start a new subject   [Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

[Home]

Written by Nick Gammon - 5K

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( http://www.gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Web site powered by FutureQuest.Net]