Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.
 Entire forum ➜ SMAUG ➜ Lua ➜ wait.lua

wait.lua

It is now over 60 days since the last post. This thread is closed.     Refresh page


Pages: 1 2  

Posted by Darwin   USA  (125 posts)  Bio
Date Thu 17 Jan 2008 09:24 AM (UTC)

Amended on Thu 17 Jan 2008 09:34 AM (UTC) by Darwin

Message
I managed to take the wait.lua that's supplied with MUSHclient and apply it to the Lua code for SMAUG. The following is the output of the accept function from a test task. Note the timestamp in the prompt.

Ranger Richard tells you 'I was going to tell you something, but...
Accepted task: Test

<[Thu Jan 17 05:15:21 2008] 30045ma 44gp [Room: 21126] > 

Ranger Richard tells you 'I've forgotten what it was.

<[Thu Jan 17 05:15:22 2008] 30045ma 44gp [Room: 21126] > 

Ranger Richard tells you 'No bother. I'll think of it later.

<[Thu Jan 17 05:15:24 2008] 30045ma 44gp [Room: 21126] > 

Ranger Richard tells you 'Oh yeah! I can pause in my speech.

<[Thu Jan 17 05:15:34 2008] 30045ma 44gp [Room: 21126] > 

And here's the accept function:

  accept = function ()
        function cl ()
        send ("&WRanger Richard tells you 'I was going to tell you something, but...")
        wait.wpause(1)
        send ("&WRanger Richard tells you 'I've forgotten what it was.")
        wait.wpause(1)
        send ("&WRanger Richard tells you 'No bother. I'll think of it later.")
        wait.wpause(10)
        send ("&WRanger Richard tells you 'Oh yeah! I can pause in my speech.")
        end
        wait.make(cl)
    end,

To get this to work, I added
wait_update = require("wait").update        -- called during violence_update() to update any pauses in scripts

to the startup.lua script and, as the comment states, I added a call to it in the violence_update() function in fight.c.

in fight.c:violence_update()

    lst_ch = NULL;
    pulse = (pulse+1) % 100;

    for ( ch = last_char; ch; lst_ch = ch, ch = gch_prev )
    {
	set_cur_char( ch );
        call_lua(ch, "wait_update", NULL);

	if ( ch == first_char && ch->prev )
	{
	   bug( "ERROR: first_char->prev != NULL, fixing...", 0 );
	   ch->prev = NULL;
	}
Top

Posted by Darwin   USA  (125 posts)  Bio
Date Reply #1 on Thu 17 Jan 2008 09:29 AM (UTC)

Amended on Thu 17 Jan 2008 09:33 AM (UTC) by Darwin

Message
I probably mangled the original wait.lua code, but here is what I have that is working now.
module (..., package.seeall)

threads = {}
beats = {}

function update ()

  local t = {}
  for _,v in pairs(threads) do
    t[_] = v
  end

  for k,v in pairs(t) do
    if os.time() >= beats[k] then
      wresume(k)
    end
  end

end

function wresume (name)

  local thread = threads [name]
  if thread and os.time() >= beats[name] then
    beats[name] = nil
    threads[name] = nil
    assert (coroutine.resume (thread))
  end

end

function wpause (beat)

  local seed = { os.time(), mt.rand(), math.random() }
  mt.srand(seed)
  local id = ""
  repeat
    id = mt.rand() .. "_" .. os.time() .. "_" .. mt.rand(1,100)+1
  until (threads[id] == nil)
  threads [id] = assert (coroutine.running (), "Must be in coroutine")
  if beat ~= nil then
    beats[id] = os.time() + beat
  else
    beats[id] = os.time() + 1
  end

  return coroutine.yield ()

end

function make (f)
  assert (type (f) == "function", "wait.make requires a function")
  coroutine.wrap (f) () -- make coroutine, resume it
end -- make

I had some problems originally with the table of threads when they would get updated and not get removed, so I iterated over the table and copied them into a temporary table. Once the thread was updated, it was deleted from the original table.

I also had problems with multiple pauses ending up with the same id, which is why I have the repeat in the wpause function. It ensures that the id it generates is not already in use.

All the random number functions just seem to be filler really. I can't determine if they help or hinder any at all.

Any suggestions on improving this script are welcomed.
Top

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #2 on Thu 17 Jan 2008 07:44 PM (UTC)
Message
This is really cool. It's a good demonstration of how powerful Lua coroutines are for adding seamless delays to scripts. I have a command module that lets you seamlessly delay player commands (by time, or until more input is received, or until some other condition is true); it's just so much nicer than the timer mechanism in SMAUG. :)

As a minor stylistic quibble, I would have made the threads and beats tables local to the module (unless other modules need to access them).

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
Top

Posted by Darwin   USA  (125 posts)  Bio
Date Reply #3 on Thu 17 Jan 2008 11:25 PM (UTC)
Message
I think I had played around with making the threads and beats tables local at one point. I'm not sure why I removed the local from them. There isn't anything outside of that module that access them.
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #4 on Fri 18 Jan 2008 03:02 AM (UTC)

Amended on Fri 18 Jan 2008 03:18 AM (UTC) by Nick Gammon

Message
Nice idea! However I think you can simplify it somewhat:


module (..., package.seeall)

local threads = {}

function update ()

  -- for each active thread, see if the time is up
  
  for k, v in pairs (threads) do
    if os.time () >= v then
      threads [k] = nil  -- delete from table now
      assert (coroutine.resume (k))
    end
  end
end

function wpause (seconds)
  threads [assert (coroutine.running (), "Must be in coroutine")] = os.time () + (seconds or 1)
  return coroutine.yield ()
end

function make (f)
  assert (type (f) == "function", "wait.make requires a function")
  coroutine.wrap (f) () -- make coroutine, resume it
end -- make



The first thing is, you don't need some complicated algorithm to make a unique key. I am using the thread itself as a key (the coroutine address) because since a coroutine can only be paused in one place, therefore the coroutine is unique.

Now for a simpler update handler - we can iterate all existing threads (I assume you are doing this because you might have multiple waits active) - once we find one whose time is up, we delete it immediately from the table, and then resume the coroutine. It is permitted to delete items from a Lua table during a table iteration.

I tested with two simultaneous threads running and it worked fine.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #5 on Fri 18 Jan 2008 03:09 AM (UTC)
Message
To call it you could make an anonymous function too, like this:


  accept = function ()

    wait.make( function ()
      send ("&WRanger Richard tells you 'I was going to tell you something, but...")
      wait.wpause(1)
      send ("&WRanger Richard tells you 'I've forgotten what it was.")
      wait.wpause(1)
      send ("&WRanger Richard tells you 'No bother. I'll think of it later.")
      wait.wpause(10)
      send ("&WRanger Richard tells you 'Oh yeah! I can pause in my speech.")
      end  -- function
      )  -- end of wait.make
      
    end,  -- end accept



- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #6 on Fri 18 Jan 2008 03:16 AM (UTC)

Amended on Fri 18 Jan 2008 03:22 AM (UTC) by Nick Gammon

Message
Oh, and you can probably rename wpause as pause, why not make it easier to read?

Or, do what you did with wait_update and do this:


pause = require("wait").wpause


Then you just have to "pause" not wait.wpause.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Darwin   USA  (125 posts)  Bio
Date Reply #7 on Sat 19 Jan 2008 12:35 AM (UTC)
Message
Nice, Nick. :)
I knew posting that here would result in some cleaner code.
Well, I now remember why the local was taken off of those tables: I had removed them so I could access those tables while debugging them and I just forgot about putting it back. :)

As for the anonymous function; I tried doing that once before and it didn't work. But, that was probably during some of my testings as I haven't tried doing that again yet.
Top

Posted by Darwin   USA  (125 posts)  Bio
Date Reply #8 on Sun 20 Jan 2008 03:41 AM (UTC)
Message
David, I'd be interested to see a sample (or example) of how you have done the waiting until some user input was recieved. There are many other things that could be done with something like that.
Top

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #9 on Sun 20 Jan 2008 06:22 AM (UTC)
Message
Sure. Be warned, though, that what I did requires making changes to very many places. :-) Give me a day or two to write it up, then I'll post it. The short story is that I intercept newlines in Lua before sending them to the interpret function. The Lua handler -- actually a method on an 'actor' object -- checks if the actor is waiting for input; if so, it resumes the actor's command coroutine with the input.

The C interpret function, meanwhile, will first try to send the command to Lua to see if Lua can handle it. If not, it does the normal interpret stuff. But if Lua can handle it, it creates a coroutine for the relevant actor (i.e. character) and command function and executes it.

That command may then do things like:

local input = darkstone.waitForMoreInput()

which puts the actor in waiting mode (see first paragraph), and the command coroutine is stored. When more input eventually comes in, the coroutine is resumed directly without passing through the C or Lua interpret functions.

Note that for all of this to work, I created the actor "class" in Lua; this is both an extension of the C++ character class and an object in its own right. It stores a handle to the C++ object, needed because most functionality is still implemented in C++. But it also stores new data, like the coroutines etc.

Incidentally note that unlike Nick's task implementation, I have a single global Lua state. This allows actors to be treated as first-class Lua objects like any other. It does however make the whole song and dance about binding C++ objects and Lua actor objects (described above) necessary.

I'm still not sure how to divide Lua and C++. I'm thinking that having data in both Lua and C++ objects is probably a Rather Bad Idea in the long run but a good transition. In the end of the day, I think the data should either be in C++ and exposed to Lua via accessors, or be in Lua and not really manipulated by the C++ side of things. I go back and forth on this issue and have yet to come up with something I'm entirely comfortable. But that's a discussion for another day I suppose. :-)

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
Top

Posted by ThomasWatts   USA  (66 posts)  Bio
Date Reply #10 on Sun 20 Jan 2008 12:55 PM (UTC)
Message
On the subject of accessing, just use "__index" and "__newindex" of the metatable that will already be associated with the 'actor'. That way all the data that C requires in changeable from Lua, but Lua could still have it's own data as well.
Also, great job Darwin on the conversion of wait.lua. Coroutines do make timers and counters ALOT easier to implement in Lua.
Top

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #11 on Sun 20 Jan 2008 04:15 PM (UTC)
Message
Quote:
On the subject of accessing, just use "__index" and "__newindex" of the metatable that will already be associated with the 'actor'. That way all the data that C requires in changeable from Lua, but Lua could still have it's own data as well.

I'm not sure what exactly you mean, but trust me, I am already using the __index/etc. metatable keys very liberally. :P I already have a setup where both C and Lua have data and where both can edit each other's data. My problem is that I'm not sure that's the best approach in the long run, nor am I sure where to draw the line between what data belongs on which side of the fence.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #12 on Mon 21 Jan 2008 05:26 AM (UTC)
Message
Quote:

I'd be interested to see a sample (or example) of how you have done the waiting until some user input was recieved.


I have done a prototype of replacing the nanny routine in SMAUG with a Lua script. In many ways it does what I hoped, and looks much nicer. The script simply asks questions and waits for answers, in a very natural-to-code way.

However the problem is interfacing with the SMAUG code. To say the current nanny routine is a mess would be an understatement. I tend to regard the SMAUG code like a delicate flower - best not to touch it, or it will fall apart.

For example, inside the huge switch statement that makes up that function (has no-one heard of breaking things into smaller functions?) there is this sort of stuff: once you have read the Message of the Day, it checks if the current player level is zero, and if so, starts assigning strength, intellect and so on.

The whole thing is so muddled up it is hard to incorporate a Lua interface. For example, since the code for setting up a new player is buried in the middle of the code in nanny for what you do once the player has read the MOTD, it is not easy to just call it when the Lua stuff finishes.

I have got something working, and the Lua side looks neat, but the C side is basically taking the current mess and turning it into a different mess.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #13 on Mon 21 Jan 2008 05:57 AM (UTC)
Message
I agree. I think it comes down to living with an ugly transition period as you move from the old to new styles. An unfortunate necessity but the only other option is starting from scratch, I think...

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
Top

Posted by ThomasWatts   USA  (66 posts)  Bio
Date Reply #14 on Mon 21 Jan 2008 07:26 AM (UTC)
Message
The starting from scratch is what I've been doing. I was originally doing what Nick was and slowly moving everything function by function into Lua. After awhile I got frustrated by the obfuscation(see comment by Nick about nanny func) and unneeded complexity of the Smaug code. So now I only have the networking and timers on the C side. I haven't looked into luaSockets yet, but have heard of too many stability issues.

Similar to what David said about his interpret function, I checked to see if the command existed in Lua before using the C version, it's inexpensive to keep pushing a descriptor pointer into Lua.

On to __index, etc, __newindex should really be called __assign instead. Set a metatable to your descriptor pointer, and inside the __newindex function check to see if the variable exists on the C side. If it doesn't it's a simple rawset to a fixed Lua table structure(using the descriptor as an index) for the variable. Rinse-Repeat.

In the stock SmaugFUSS the fence is currently well defined. Anything used on the C side should remain on the C side. If you move enough functions to Lua then eventually the C variables can move to Lua as well.
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.


87,662 views.

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

It is now over 60 days since the last post. This thread is closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.