Jump to content




Gopher's APIs - Old, and broken, but still good. Yah. Still good.

api

83 replies to this topic

#1 GopherAtl

  • Members
  • 888 posts

Posted 17 October 2012 - 01:41 AM

Actual Latest Update: redirect and ggui have both been updated to handle the changes to the term api from several CC versions back. They should now work on old and new versions of CC.
Latest update: I've added a new gui API, ggui.
The current release has only a limited set of gui elements - labels, buttons, text fields, and graphic elements for displaying images created in paint. However, I've tried to make these core elements as robust as possible. Text fields support all the keys you would expect, remember the cursor position, and even support standard gui form behavior where enter moves focus to the next control and, if it's a button, activates it. Graphic elements can slice a subsection out of a source image, allowing variable-sized images, and letting multiple graphic elements be stored in a single paint file. All elements use styles, which can be overwritten to change text alignment, image alignment, foreground and background colors, etc., with support for automatically detecting and switching styles for color or b/w computers. All in all, a pretty solid foundation, I think, but it is a WIP. Documentation will come later, for now, you'll have to settle for the two sample programs, you can get them and the api on pastebin using this handy installer pastebin get fq8SinNB.


More examples coming soon, several of them are already written but I want to polish and test them a bit more before I post!

Decided to stop making more threads and pull all my general computer APIs into a single thread, since I've been extending them to interoperate more and more. You can still use all of them independently as well, of course.

Overview
The following are the APIs in this thread:
goroutine
A very handy API for more advanced coroutine applications. Sometimes parallel is plenty for a job, but sometimes you need a bit more freedom. Goroutine gives you that. Used within a single application, it gives you the ability to dynamically spawn new coroutines on the fly, assign terminal redirects to specific coroutines, kill a coroutine from another, assign events to be received only by specific coroutines, and to wait not just for a given type of event, but an event with a given set of parameters. If run properly from startup, it can do all that and more, allowing programs to spawn background routines that continue after the program that spawned them exits.

redirect
The buffer api creates buffer objects that can be used with term.redirect. Buffers can be any size, they support color or black and white, and you can easily copy a specific area of the buffer to the screen. Buffers can also be made "active," which will make them "write through" to the terminal. In this mode, you can create multiple buffers, each mapped to a different area of the screen, each of those areas having it's own cursor position and color settings, able to be cleared and scrolled without affecting the rest of the screen, and allowing use of print() with the expected word-wrapping behavior.

ctrlkeys
This little guy provides a coroutine you can run in the background - either with goroutines, or just using parallel.waitForAny - that detects certain patterns of key events in order to - quite accurately - generate new "ctrl_key" events when a user holds down control and then presses certain keys. Unfortunately, it only works with the "typable" keys, that generate char events - a-z, 1-0, punctuation, etc - and not with the special keys like enter, backspace, or the arrow keys.

ggui (BETA)
The latest addition, still a work in progress, is a basic gui library. My hope is to make it easy to use, with helpful error messages and a high tolerance for doing the wrong thing.


Now for the detailed info on each one!
goroutine
Spoiler

redirect
Spoiler


ctrlkeys
Spoiler

ggui
Spoiler

Advanced Examples

multishell
not sure if this even works anymore, haven't bothered testing, seems slightly redundant with a built-in multishell program on all advanced computers these days...
Similar to the linux utility screen, multishell uses goroutines, ctrlkeys, and redirect to let you run up to 10 shell sessions at once, switching between them by pressing ctrl+1 through ctrl-0.
pastebin: e4Uf8M3p
More on this program can be found in it's thread, here




More coming soon eventually.. Examples that use two or more of the above APIs to pull off some really neat things.
Basic 1-on-1 chat program

License
Use these however you like; if you're distributing software that includes or is derived from any of my APIs, I just that you include a link to this thread and credit me, as a basic courtesy. Well, unless you make something really crappy, in which case, please don't give me any credit.

Edited by GopherAtl, 14 March 2015 - 02:00 AM.


#2 faubiguy

  • Members
  • 213 posts

Posted 17 October 2012 - 02:19 AM

This looks useful. I don't have any need for coroutines right now, but if I do later, I might use this.

#3 CoolisTheName007

  • Members
  • 304 posts

Posted 17 October 2012 - 01:27 PM

I actually started editing the parallel API before you posted this, and I have implemented a class for creating thread managers, that way, we can have a thread manager inside a thread. Beats me why we would need that, but seems cool.
It still doesn't have all the functionality of your goroutines, but I'm getting there, and I'll include a banner saying I re-used part of your code and ideas. For now:
-Do you know that almost everything in Lua can be used as a key? That, plus the fact that tables, and threads created by coroutine.create() (which probably are tables 'with' a metatable), allows to index your coroutines by themselfs: coroutines.co={co=co,name=name,...} without duplicating data.
-I thought a little and setting names by string seems essential to allow coroutines to specify other coroutines.
-It seems that goroutine.waitForEvent can only take one event to wait for.
-A lot of the functionality depends on accessing goroutines API namespace from coroutines, and when implementing in the form of a class, I see 2 options: the programmer knows the name of the thread manager object --or-- global class functions send special events like 'sendTo' thread_name event_to_be_sent . Both can be implemented at the same time.

What do you think?

#4 GopherAtl

  • Members
  • 888 posts

Posted 17 October 2012 - 02:29 PM

1) I do know anything can be used as a key in lua. If I decide to require unique names I will probably make it use names as keys, but for now I am content the way it is. Having to know the coroutine's address before looking up the coroutine would just make it more expensive to lookup by name than using an array as I do now.

2) er, yes, names are meant to be strings. I don't enforce this, you could pass whatever you wanted as name in spawn, but...yes.

3) waitForEvent does indeed wait for one specific event, filtered further by some set of parameter criteria. To wait for multiple you still fall back on os.pullEvent. If I ever find a need for it, I might add a waitForEvents() that takes multiple events and filter groups, but for now it seems more than adequate.

4) they are not implemented as objects. Having to keep and pass a copy of a goroutine object around in order to invoke methods on it would complicate use for no real gain. So far, for messages between goroutines, a combination of use of variables in a shared scope and custom event messages sent with os.queueEvent have been more than adequate, and for the methods that even require a target thread, you'd have to lookup the object by name through the goroutines API first anyway, so it makes more sense to just pass the name.

Thanks for the feedback, though, and good luck with your own api!

#5 brett122798

  • Members
  • 298 posts
  • LocationIn the TARDIS at an unknown place in time.

Posted 10 January 2013 - 07:08 PM

This is great! Thank you so much!

#6 GopherAtl

  • Members
  • 888 posts

Posted 11 January 2013 - 06:44 PM

glad you found it useful! One of these days I'll get around to making an updated version. Let me know if you have any problems, or have feature suggestions/requests!

#7 GravityScore

  • Members
  • 796 posts
  • LocationLand of Meh

Posted 11 January 2013 - 09:05 PM

My god this is useful......

I've been wanting something like Python's thread.start_new_thread (simple and to the point) for ages, but this is wayyyy better! :D

I really need to look in the API and Utilities section more often...

#8 ArchAngel075

  • Members
  • 149 posts

Posted 12 January 2013 - 01:04 AM

for some improvement, try adding this to where it opens the rednet :
(this will auto detect the modem and use the side it is found on, plus it works for any peripheral by changing one word)

for s = 1,table.maxn(SidesSearch) do
  if peripheral.isPresent(SidesSearch[s]) == true then
   if peripheral.getType(SidesSearch[s]) == "modem" then -- change modem to whatever peripheral you wish to scan for
    mon = peripheral.wrap(SidesSearch[s])
    side = SidesSearch[s]
   end
  end
end
if side == nil then
--this will be fore whatever you deem to happen should no modem be found, perhaps let the user know...
else
--this is if a peripheral you seek is found...
rendet.open(side)
end

I might find this usefull once i learn more about coroutines and how i can apply them :)

#9 GopherAtl

  • Members
  • 888 posts

Posted 12 January 2013 - 05:34 AM

Took me a minute to figure out what you meant, as the API doesn't use rednet; you meant in the sample program! I was deliberately keeping the sample program simple, as the point is to demonstrate basic use of goroutines, but for real rednet programs, this is a good suggestion!

#10 brett122798

  • Members
  • 298 posts
  • LocationIn the TARDIS at an unknown place in time.

Posted 12 January 2013 - 03:31 PM

View Postbrett122798, on 10 January 2013 - 07:08 PM, said:

This is great! Thank you so much!
Hey, I have a small problem(Life or Death), I THINK it ignores os.pullEvent, not 100% sure if that's where the problem is though.

EDIT: Here's my code:

function button1()
setButton(6, 15, 17, "lightBlue", "white", "Click This!")
end

function setButton(x1, x2, y1, color, textColor, text)
countx = 0
repeat
term.setCursorPos((x1 + countx), y1)
readColorBackground(color)
print(" ")
countx = countx + 1
until x1 + countx == (x2 + 1)
differencex = x2 - x1
x = math.max(math.floor((differencex / 2) - (#text / 2)), 0)

-- cursorpos = round((x2 - x1)/2)
-- cursorpos = cursorpos + x1
term.setCursorPos(x + x1 + 1, y1)
readColorText(textColor)
print(text)
while true do
local event, arg, x, y = os.pullEvent("mouse_click")
if (x >= x1 and x <= x2) and y == y1 then
break
end
end
term.setCursorPos(20, 30)
print("Clicked!")
end

goroutine.spawn(tester2, button1)


#11 GopherAtl

  • Members
  • 888 posts

Posted 12 January 2013 - 03:48 PM

uhm. Ignores how? I'm gonna need more to go on than that to help! What's actually happening, and can I see source?

#12 brett122798

  • Members
  • 298 posts
  • LocationIn the TARDIS at an unknown place in time.

Posted 12 January 2013 - 05:57 PM

View PostGopherAtl, on 12 January 2013 - 03:48 PM, said:

uhm. Ignores how? I'm gonna need more to go on than that to help! What's actually happening, and can I see source?
What exactly is happening is, the button gets drawn on the screen, however, it is not clickable. I'm just supposing that os.pullEvent isn't taking place or something.

Here's the whole source if you want to run it(I'm running the screen at 100x35):
Spoiler


#13 GopherAtl

  • Members
  • 888 posts

Posted 12 January 2013 - 06:43 PM

Hrm. Well, you're calling sleep(5) in the main program before you start waiting for input, for some reason, but if I wait the 5 seconds and then click on "click this", it seems to work. I'd suggest cleaning up your code, never leave test stuff around once you're done testing, it confuses things and makes it very hard to tell what's going on.

:edit: after looking at the code a bit more, there are a lot of problems, but none of them are really related to goroutines. I'm not sure coroutines in general are the best way to do what you appear to be trying to do with this code so far. Whenever you click one of the two buttons, the other goes away, but you're left still trying to click on the first one. If you click on the "click me!" button, and then the "durr" button, the program exits back to shell, but the coroutine you spawned to handle the "Click This!" button is left running in the background!

The only real goroutine-related issue with this is that it is spawning routines that can outlive the program, but that should not be able to outlive the program.

In the case of watching for clicks on multiple buttons at once, I'd suggest using tables that define all the buttons and checking all of them from a single os.pullEvent() loop, rather than using separate coroutines for each one. General-purpose gui coding is a bit tricky, you might also look at some other people's gui code on the forums to get an idea how to handle some of the basics.

#14 brett122798

  • Members
  • 298 posts
  • LocationIn the TARDIS at an unknown place in time.

Posted 12 January 2013 - 06:48 PM

View PostGopherAtl, on 12 January 2013 - 06:43 PM, said:

Hrm. Well, you're calling sleep(5) in the main program before you start waiting for input, for some reason, but if I wait the 5 seconds and then click on "click this", it seems to work. I'd suggest cleaning up your code, never leave test stuff around once you're done testing, it confuses things and makes it very hard to tell what's going on.
That's not the button that won't go.. Here, just use this script instead..
Spoiler


#15 GopherAtl

  • Members
  • 888 posts

Posted 12 January 2013 - 07:04 PM

yeah, that's spawning a button in the background then exiting the program immediately. The coroutine listening to the button is left running in the background even though the program exited. If you run it again immediately, you'd wind up with 2 of the button coroutines running. That is one of the things goroutines is intended to let you do, but obviously it's not what you want to happen in your program. I'm afraid goroutines does not work quite the way you think it does, and I'm not sure it's actually a good way to approach the problem you're working on.

#16 brett122798

  • Members
  • 298 posts
  • LocationIn the TARDIS at an unknown place in time.

Posted 12 January 2013 - 07:08 PM

View PostGopherAtl, on 12 January 2013 - 07:04 PM, said:

yeah, that's spawning a button in the background then exiting the program immediately. The coroutine listening to the button is left running in the background even though the program exited. If you run it again immediately, you'd wind up with 2 of the button coroutines running. That is one of the things goroutines is intended to let you do, but obviously it's not what you want to happen in your program. I'm afraid goroutines does not work quite the way you think it does, and I'm not sure it's actually a good way to approach the problem you're working on.
That's actually not true. If you run my previous code and click that button while the program is running, it does nothing.

#17 GopherAtl

  • Members
  • 888 posts

Posted 12 January 2013 - 07:11 PM

I did exactly that, and other than not fitting on my regulation-sized screen, it worked fine. If you've been testing this way without rebooting for a while, lord only knows how many stray coroutines you've got still running in the background, and the resulting behavior is something I can't begin to guess at. But as I said, I did in fact copy the last code you posted, put it on a computer with goroutines loaded in startup, and ran it. It performed as I described.

#18 brett122798

  • Members
  • 298 posts
  • LocationIn the TARDIS at an unknown place in time.

Posted 12 January 2013 - 07:15 PM

View PostGopherAtl, on 12 January 2013 - 07:11 PM, said:

I did exactly that, and other than not fitting on my regulation-sized screen, it worked fine. If you've been testing this way without rebooting for a while, lord only knows how many stray coroutines you've got still running in the background, and the resulting behavior is something I can't begin to guess at. But as I said, I did in fact copy the last code you posted, put it on a computer with goroutines loaded in startup, and ran it. It performed as I described.
You're not getting it.. the button does not detect being pressed even with the program still in session and everything. And yes, I've rebooted millions of times.

#19 GopherAtl

  • Members
  • 888 posts

Posted 12 January 2013 - 07:21 PM

Wait. I see what's going on... you're calling goroutine.spawn() without ever calling goroutine.run(). It doesn't work that way.

Look at the sample program in the first post. It shows you how to setup goroutine to run from startup, so that programs can simply call goroutine.spawn(). I assumed this was what you were doing, so it was what I was doing to test your program.

You have to call goroutine.run() to launch a "main" coroutine first, and that coroutine can then call goroutine.spawn() to spawn more.

:edit: Somehow goroutine.run got left out of the function list in the original post, added it to the top to make it more clear that goroutine.run must be called first for those who go straight to the function list without looking closely at the sample program.

#20 brett122798

  • Members
  • 298 posts
  • LocationIn the TARDIS at an unknown place in time.

Posted 12 January 2013 - 07:32 PM

View PostGopherAtl, on 12 January 2013 - 07:21 PM, said:

Wait. I see what's going on... you're calling goroutine.spawn() without ever calling goroutine.run(). It doesn't work that way.

Look at the sample program in the first post. It shows you how to setup goroutine to run from startup, so that programs can simply call goroutine.spawn(). I assumed this was what you were doing, so it was what I was doing to test your program.

You have to call goroutine.run() to launch a "main" coroutine first, and that coroutine can then call goroutine.spawn() to spawn more.

:edit: Somehow goroutine.run got left out of the function list in the original post, added it to the top to make it more clear that goroutine.run must be called first for those who go straight to the function list without looking closely at the sample program.
Oh.. yeah, never knew there was such a function. Everything works all fine n' good now! Hooray!

Although, I'm quite mixed up with the API and your startup is confusing.





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users