This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
GopherAtl's profile picture

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

Started by GopherAtl, 17 October 2012 - 12:41 AM
GopherAtl #1
Posted 17 October 2012 - 02: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
SpoilerCan be run "globally" if set up in startup, or can be set up by an individual program. Start it up by calling goroutine.run and passing it your "main" function, which will become the master coroutine.
All variations of the "spawn" function cause a coroutine to be immediately run once - until it's first pullEvent or other yield - before returning to the code that called spawn. Coroutines must be given unique names when spawned, or the spawn…() functions will return nil.

Functions
Spoilergoroutine.run(function)
The first function you'll call, just pass it your "main" function and spawn any additional coroutines from it.

goroutine.launch()
Intended to be called only from startup, and only with proper checking that goroutine has not already been launched!! (see the example under "Usage" below) Calling this from startup without proper checking will put your computer in an infinite loop, requiring a boot floppy to recover it!

goroutine.spawn(name,func,…)
The generic spawn function, creates a new coroutine with the specified name, running the function "func", and runs it once, then continues executing after the call to "spawn." The new coroutine will be a child of the coroutine that called spawn, and will end when that coroutine ends, if it hasn't ended already. Any extra parameters after "func" will be arguments passed to func. The new coroutine will use it's parent's term redirect, with the top-level coroutine always using term.native as it's redirect. Returns a table describing the goroutine, or nil and an error message if it failed.


goroutine.spawnWithRedirect(name,func,redirect,…)
Like spawn, but allows you to specify a redirect target for this coroutine to use. term will be redirected to this target every time the coroutine resumes. This would allow, for example, making one coroutine always output to a monitor peripheral, without having to manually redirect and restore the terminal.

goroutine.spawnPeer(name,function,…)
Just like spawn, except instead of having the current coroutine as a parent, it shares the current coroutine's parent. This means it will not end when the current coroutine ends, but instead keep running as long as the current coroutine's parent runs.

goroutine.spawnPeerWithRedirect(name,function,redirect,…)
Combination of spawnPeer and spawnWithRedirect, spawns as a peer with the specified redirect.


goroutine.spawnBackground(name,function,…)
Another variant, spawnBackground creates a coroutine has the top-level routine as it's parent. If goroutines is run only from in your program, then this means it will be a child of main, and will still exit if main exits. However, if goroutine is launched properly in your startup file, then the coroutine will be a child of the special "root" coroutine, and will continue running even after the program exits. Background coroutines are all redirected to a special "nil" redirect, so calls to print(), write() or any of the term methods will have no effect.

goroutine.kill(coName)
Kills the coroutine named coName. Coroutines cannot use this function to kill themselves, and should exit by either returning from their top function or by calling error(). Returns true if successful, otherwise false and an error message.

goroutine.assignEvent(coName,sEvent)
Assigns all events of type sEvent to be sent exclusively to the coroutine named coName. If it exists and is alive, that coroutine will be sent any events of this type that arrive. The coroutine may choose to pass on the event by calling goroutine.passEvent, but if it does not do so, then no other coroutines will see this event at all. Can only be called by the main coroutine, as a basic security precaution, so any event assignments your program needs should be made in main. Event assignments are automatically released if the coroutine they are assigned to exits or errors.

goroutine.passEvent(coName)
Passes the currently-handled event to the named coroutine. passEvent has no effect unless it is called from a coroutine whose most-recent event was assigned to it by assignEvent, or passed to it by another coroutine that it was assigned to calling passEvent. The coroutine the event is passed to is not run immediately, but will run and receive the event as soon as the current coroutine exits. If coName is nil or "", the event will be passed to all waiting coroutines as if it were not assigned.

goroutine.releaseEvent(event)
Releases a previously-assigned event, causing it to be sent to all waiting coroutines normally again. Can only be called by the main coroutine, or by the coroutine to which the event is assigned.

goroutine.waitForEvent(event, …)
behaves like os.pullEvent, but accepts more than one parameter. All passed parameters will be used as additional filters, matched to the corresponding event parameters, and the function will exit only when all specified arguments match the event's arguments. Parameters can be "skipped" by passing nil, allowing filtering to ignore the value of some parameters.
Example:

goroutine.waitForEvent("key",keys.enter) --would wait for the enter key to be pressed
goroutine.waitForEvent("rednet_message",40) -- would wait for a rednet message from computer with id 40
goroutine.waitForEvent("redent_message",nil,"ping") -- waits for a rednet message "ping" from any computer

goroutine.findNamedCoroutine(coName)
Finds and returns the coroutine object, as returned by the spawn functions, with the specified name.

goroutine.running()
returns the coroutine object for the code that called running()

goroutine.list()
Returns a table containing an array of names of all active coroutines.


Events
SpoilerThe goroutine api generates the following events, which can be handled by your program:

"coroutine_start", coName
Sent whenever a new coroutine is spawned.

"coroutine_end", coName
Sent when a coroutine ends for any reason

"coroutine_error", coName, err
Sent when a coroutine exits unexpectedly due to an error() call. err is the text of the error message generated.
coroutine_error is always followed by a coroutine_end message.

Usage
SpoilerGoroutine can be used in two ways. First, it can be launched in startup, using goroutine.launch(), so that the main shell itself is a coroutine it manages. In this setup, programs run from the shell can use spawnBackground to create coroutines that will continue running even after those programs exit. In fact, such background routines will may continue running even if the shell itself exits, causing the computer to appear to "hang." This is expected but undesired behavior that may be addressed in a future version. It should still respond to ctrl+r or ctrl+s to reboot or shutdown.

Example code to properly launch goroutines from a startup file:
Spoiler
--this should always be the very first thing in your startup file.
--any other code before these two lines will run twice, which may have unintended effects.
os.loadAPI("apis/goroutine")  --change the path as necessary to where you've stored goroutine
goroutine.launch()

--the rest of your startup code goes here
pastebin: Cifjwb0Z


The second way is to simply launch goroutine from within a program, using goroutine.run(). In this way, you get most of the benefits of the goroutine api, but you will not be able to leave a coroutine running after your program exits (though you could spawn a new shell in a coroutine within your program, making it appear that your program has exited). Programs written this way should not, in general, be affected if goroutine in fact was installed as the top-level coroutine, so unless your program absolutely requires a startup-level launch of goroutines, it should be written in this way for maximum compatibility.

Example goroutine program structure:
Spoiler

--first check if goroutine is loaded and if not load it
if not goroutine then
  local ok= os.loadAPI("api/goroutine") --change this to the right path to where you saved it
  if not ok then
	error("program requires goroutine api to run!")
  end
end

--any variables you want to be visible to all coroutines should be declared at the top
local myVariable=1

--declare any coroutine functions next
local function myCoroutine()
  --each coroutine will usualy be built around an event loop, like this one.
  --in this example it pulls rednet messages, but it could be any event type, or even multiple types.
  while true do
	 local _, sender, message, dist = os.pullEvent("rednet_message")
	
	 --do stuff with the message here...
	 print("rednet message from "..sender..": "..message)

  end	
end

--your main function, this will be the top-level coroutine. The function name can be whatever you want.
local function main()
  --often coroutines will be spawned right at the start, to run in the background during the whole program
  --they can be spawned at any time and from any coroutine, though. In fact, if you ONLY spawn coroutines
  --here, you may not need goroutine at all, the parallel functions might do the job fine.
  goroutine.spawn("rednetListener", myCoroutine)

  --main is ultimately a coroutine like any other, and usually you'll have an program loop of some kind
  while true do
	local _, key=os.pullEvent("key")
	if key==keys.backspace then
	  --break out of the loop
	  break		
	end
  end
  --once this guy exits, his children will exit to, so any cleanup can go here
end

--this launches your main coroutine in a way that will work whether goroutines is launched from startup or not
goroutine.run(main)
--code after this will run only after your coroutines all exit.
pastebin : jRXVC1AZ

Shell Utilities
SpoilerThese programs serve as example programs, but also act as a handy set of basic utility applications for use from the CraftOS shell when launching goroutines from startup. These programs are highly recommended!

ps
lists the actively running processes (coroutines). The "ids" provided are really just indices, and will change when processes are started or stopped.
Spoiler

local list=goroutine.list()

print("Active coroutines:")
for i=1,#list do
  print(string.format("  %2d : ",i)..list[i])
end
Pastebin:aFb82sN7


bg
This lets you run a program in the background. It's output will be compeltely suppressed, so it will not affect the display. It is intended to use to launch programs created to run in the background, for example, waiting for and responding to special rednet commands.

Spoiler

if not goroutine then
  if not os.loadAPI("goroutine") then
	print("goroutine API is required!")
	return
  end
end

--args
local args={...}

local function usage()
  print("usage:\nbg <program> [args...]")
  error()
end

if #args<1 then
  usage()
end

local program=args[1]

program=shell.resolveProgram(program)
if not program then
  print("Couldn't find program '"..program.."'!")
  return
end

local name=program
local index=0
while goroutine.findNamedCoroutine(name) do
  index=index+1
  name=program..index
end

if goroutine.isActive() then
  --just spawn!
  print("Spawning '"..program.."' in background.")

  goroutine.spawnBackground(name, shell.run, ...)

else
  --not running we have to run ourselves.
  local function main(...)
	goroutine.spawnBackground(name, shell.run, ...)
	print("Launching new local shell. Exit this shell to terminate monitor program!")
	shell.run("shell")
  end
  goroutine.run(main,...)
end
pastebin:3muLEAL8

kill
This program can be used from the shell to terminate a background process. It takes one argument, the index or name of the coroutine to be killed.

Spoiler

local l=goroutine.list()
local args={...}

if args[1]==nil then
  print("usage:\nkill <procname>\nkill <procnumber>")
end

local procnum=tonumber(args[1])
if procnum and procnum>0 and procnum<=#l then
  print("Killing coroutine '"..l[procnum].."'")
  goroutine.kill(l[procnum])
  return
end

for i=1,#l do
  if l[i]==args[1] then
	print("Killing coroutine '"..l[i].."'")
	goroutine.kill(l[i])
	return
  end
end

print("That coroutine does not exist")
Pastebin:1NXTUuPr

monitor
This program is like the vanilla monitor program, except that it spawns the program as a coroutine and immediately returns to a shell prompt.
Spoiler

if not goroutine then
  if not os.loadAPI("goroutine") then
	print("goroutine API is required!")
	return
  end
end

--args
local args={...}

local function usage()
  print("usage:\nmonitor <side> <program> [args...]")
  error()
end

if #args<2 then
  usage()
end

local side=args[1]
local program=args[2]
local progArgs={select(2,...)}

if peripheral.getType(side)~="monitor" then
  print("There is not a monitor on side '"..side.."'!")
  return
end

program=shell.resolveProgram(program)
if not program then
  print("Couldn't find program '"..program.."'!")
  return
end

local function runOnMonitor()
  local co=coroutine.create(shell.run)
  local event=progArgs
  local filter={}

  while true do
	local ok, err=coroutine.resume(co,unpack(event))
	if not ok then
	  error(err)
	end

	event = { os.pullEventRaw() }
	--transform monitor_touch events to this monitor
	if event[1]=="monitor_touch" and event[2]==side then
	  event[1]="monitor_click"
	  event[2]=1
	end
  end
end


if goroutine.isActive() then
  --just spawn!
  print("Spawning "..program.." on side "..side)
  goroutine.spawnWithRedirect(side..":"..program, runOnMonitor, peripheral.wrap(side))
else
  --not running we have to run ourselves.
  local function main()
	goroutine.spawnWithRedirect(side..":"..program, runOnMonitor, peripheral.wrap(side))
	print("Launching new local shell. Exit this shell to terminate monitor program!")
	shell.run("shell")
  end
end
Pastebin:M7afj4NM


Source
pastebin
pastebin get SUW8cq9j goroutine

redirect
SpoilerThe redirect api has only one directly-called function, createRedirectBuffer, which creates and returns a new redirect buffer object, but that object has all the methods necessary to be used as a redirect target on basic and advanced computers, as well as a few extra methods for managing the buffer and drawing it's contents to the screen.

There are two ways to get the contents of a buffer to the screen. The first is explicitly calling buffer.blit, which allows you to copy all or part of the buffer to some position on the screen. The second us with the buffer.makeActive method, which allows you to map the buffer to some portion of the screen, which will cause anything written to the buffer to immediately draw to that area of the screen as well. The first is useful for implementing a form of back buffering, or for allowing switching between two or more screens of data without having to rebuild those screens each time you switch. The second is useful for splitting the screen into smaller areas, each of which can be written to independently. Each of these buffered areas would have it's own independent cursor position and active colors, as well as scrolling and clearing without affecting the rest of the screen.

Redirect buffers can certainly be used independently, but they're at their most when combined with goroutines. goroutines has built-in support for redirect targets (any redirect targets, not just those created by the redirect api), and by combining the two you can easily do things like create a chat application that has one coroutine reading input, mapped to a single line buffer at the bottom of the screen, while another coroutine using a buffer fitting the rest of the screen prints the scrolling chat history.

A note about the buffer objects: in order to be compatible with term.redirect, the buffer object methods are wrapped to supply the buffer object as a parameter automatically, so despite being lua objects, you call their methods with "." instead of ":"

Functions
SpoilerAPI Functions
Spoilerredirect.createRedirectBuffer(width,height,fg, bg, isColor)
Creates a new redirect buffer, of the specified width and height. fg and bg are optional parameters specifying the text (fg) and background (bg) colors for the buffer, which will be used to fill the new buffer as well as being the initial color for calls to buffer.write(). isColor is optional as well, and is a boolean to specify if the buffer should be color or not. Defaults to whatever the terminal running the program is.

examples:

--creates a full-screen buffer, with the default colors (white on black).
myBuffer=redirect.createRedirectBuffer(51,19)

--creates a 20 by 10 buffer, with a blue background and yellow text. Forces the buffer to be color even if this computer is not.
myBuffer=redirect.createRedirectBuffer(51,19,colors.yellow,colors.blue,true)

Buffer Object Methods
Spoilerthese are called on the object returned by createRedirectBuffer
In addition to these methods, they have all the standard methods of any terminal redirect object: write, scroll, setCursorPos, isColor, getCursorPos, setTextColor, setBackgroundColor, setCursorBlink, clear, and clearLine.

buffer.resize(width,height)
Resizes the buffer without destroying it's contents. If the new size is smaller, text past the new edges will disappear. If it is larger, the new area will be whitespace. Works basically like resizing arrays of monitor peripherals by placing more or breaking some while they are in use. Note that if a buffer is made smaller, and then enlarged back to original size, the text that had disappeared will still be present, if the lines have not scrolled off the screen and clear() or clearLine() have not been called.

buffer.blit(sourceX, sourceY, destX, destY, width, height)
Blits, or copies, part or all of the buffer to the active terminal object. Note that where this draws is controlled by term.redirect, so if you call blit() while you have redirected the terminal to this buffer, it will copy to itself, which may not be what you want - though it can be useful!

With no parameters, using all defaults, this buffer.blit() will copy the entire screen.

parameters:
sourceX and sourceY define the position in the buffer to use as the top-left corner of the copied area. Defaults to 1,1 if not specified.
destX and destY define the position on the screen (or current redirect) to copy to. Default to 1,1.
width, height are the width and height of the area to copy. They default to the dimensions of the buffer.

buffer.makeActive(scrX,scrY)
Makes this buffer "active," meaning any changes to the buffer will immediately be presented to the screen. Unlike blit, an active buffer draws directly to term.native, ignoring the current redirect and going straight to the screen. scrX and scrY are optional, defaulting to 1,1, and specify the position on the screen that the top-left corner of the buffer will write to.

Buffer Object Data
SpoilerIn addition to methods there are variables stored in the buffer objects that can be accessed directly to get information about the buffer.

buffer.width, buffer.height
the width and height of the buffer

buffer.curX, buffer.curY
the cursor position on the buffer (same as returned by buffer.getCursorPos())

buffer.cursorBlink
boolean indicating if the cursor is blinking or not. Same value passed in the last call to buffer.setCursorBlink

buffer.textColor, buffer.backgroundColor
the current text and background color of the buffer

Raw buffer access
In addition to these members, the buffer object can be indexed as a two-dimensional array to access the characters and colors at every character position in the buffer.
buffer[y][x].char the character at x,y
buffer[y][x].backgroundColor the background color at x, y
buffer[y][x].textColor the foreground color at x, y

It is also possible to draw by directly modifying this array, but drawing this way is not recommended when also using buffer.makeActive, as changes will not be detected or drawn to the screen automatically. This method works fine if you are using buffer.blit() to draw changes to the screen.

Example Programs
SpoilerExample using buffers alone are coming, as soon as I have time and figure out what those examples ought to be.

Source
http://pastebin.com/fU9Kj9zr
pastebin get fU9Kj9zr redirect


ctrlkeys
SpoilerWhen loaded and launched as a background coroutine, either with goroutines or parallel.waitForAny, will generate new ctrl_key events for many common keyboard shortcuts. It cannot generate all possible ctrl+key combinations, only those where the key is one that can send a "char" event, such as a-z, 0-9, and any other punctuation or symbol keys. Due to the way ctrl-keys are detected, it can't generate ctrl+tab, ctrl+enter, ctrl+arrow, or any other special key combination events. There is absolutely nothing I can do about this, believe me, if it was possible I would've implemented them as well!

API Functions
Spoilerctrlkeys.waitForAny(…)
ctrlkeys.waitForAll(…)
These work exactly like parallel.waitForAny and parallel.waitForAll, except that all the functions will receive ctrl_key events. For info on using them, see the wiki documentation for the Parallel API

ctrlkeys.run(…)
This function is exactly like shell.run(), except the program will get ctrl_key events. For information on using it, see the wiki documentation for shell.run

ctrlkeys.coGenCtrlKeyEvents()
This function should not be called directly; it is the actual function used to create a coroutine that generates ctrlkey events. It can be passed directly into parallel.waitForAny or any of the goroutine.spawn() functions.

Events
Spoiler"ctrl_key", key, char, bShift, bAlt
The event itself, the whole point of the api! key is the key that was pressed while holding ctrl, same as returned in normal "key" events. char is the character that would have been typed; the char sent with the key will depend on whether shift was pressed or not.
bShift and bAlt are true if shift and alt, respectively, were pressed as well.

A note about bShift and bAlt, they are not 100% reliable! Due to quirks in CC events, I can know for a fact that ctrl is still held when a character key is pressed, but not shift or alt. The bottom line is that bShift and bAlt do not tell you if shift and alt were still held down when the character key was pressed. What they do tell you is whether shift or alt were pressed after control and before the key. They are not exactly what you might expect, but they are at least consistent in what they are.

Usage
SpoilerBasic Usage
SpoilerThis simple example runs ctrlkeys and just prints to the screen each event, exiting when ctrl-T is pressed (doesn't have to be held!)

--load the api first
os.loadAPI("ctrlkeys")
local function dumpKeys()
  while true do
	e={os.pullEvent()}
	if e[1]=="ctrl_key" and e[2]==keys.t then
	  print("ctrl-t detected, exiting...")
	  return
	end
	--print the event and it's parameters
	local str=" "
	for i=1,#e do
	  str=str..tostring(e[i]).." "
	end
	print(str)
  end
end
--run the function
ctrlkeys.waitForAny(dumpKeys)

http://pastebin.com/ttrYf9Du
pastebin get ttrYf9Du ctrlkeySample

Using with goroutine
SpoilerAny time goroutine is active, whether activated in startup or by a program, just add this line to top of your main coroutine function, or to your startup file after launching goroutine:

goroutine.spawn(ctrlkeys.coGenCtrlKeyEvents)

There you go! If you do this in startup, all programs will automatically get key_ctrl events without having to do anything special. Most standard programs will simply ignore the events, but ctrlkeys does not block any events, so having it installed won't prevent any programs not made for it from functioning as usual.
Source
http://pastebin.com/d6CmF10y
pastebin get d6CmF10y ctrlkeys

ggui
Spoilerggui is a WIP still in the beta stage.

Proper documentation will come as the feature set gets finalized.


current features:
Basic gui elements - labels, edit boxes, graphic elements, and buttons.

Multi-monitor programs using "screens", which are containers for collections of gui elements

Managed event handling - the pullEvent loop is hidden away inside the api, and you simply tell the api what functions to call when different events occur and then call ggui.run!

Anchor-based positioning - elements have a position and size, with the position interpreted based on anchorX and anchorY values. By default anchored "left" and "top", and behaving as you would expect, can be anchored right, bottom, and center as well. Position is interpreted as an offset from that edge, so position 1,1 with anchors left and top will put your element in the top-left corner, while the same position with anchors right and bottom will go bottom-right. Center anchors use the position as the center of the element,

styles - customizable styles allow control of all aspects of the gui elements appearance, which can be set up with separate color and mono versions, allowing your programs to easily support both color or black and white computers.

planned features:
more gui elements to work with, including
- popup menus
- radio button groups
- checkboxes
- screen-based dialog boxes
- tabbed pages
…and more.

Downloads
I've put together some sample programs to help test and demo the new API; you can download them all together with this installer:

Demo Installer - pastebin run fq8SinNB ggui - will install it to the ggui directory; feel free to replace ggui with the directory of your choice.

or, if you'd just like to look at the code, you can go to the api and examples directly on pastebin:
ggui api - pastebin get i0EyjZ2W ggui
example login with splash screen - hV53TCNx
sample redstone monitoring/control program - RNTj60Rd - "Monitor" program for some reason!


Screenshots of the sample programs
Spoiler

redstone sample on a 2x4 monitor:

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 on 14 March 2015 - 01:00 AM
faubiguy #2
Posted 17 October 2012 - 03:19 AM
This looks useful. I don't have any need for coroutines right now, but if I do later, I might use this.
CoolisTheName007 #3
Posted 17 October 2012 - 02: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?
GopherAtl #4
Posted 17 October 2012 - 03: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!
brett122798 #5
Posted 10 January 2013 - 07:08 PM
This is great! Thank you so much!
GopherAtl #6
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!
GravityScore #7
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…
ArchAngel075 #8
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 :)/>
GopherAtl #9
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!
brett122798 #10
Posted 12 January 2013 - 03:31 PM
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)
GopherAtl #11
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?
brett122798 #12
Posted 12 January 2013 - 05:57 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?
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

-- Excuse me for my messy code, havent gotten around to making it better

-- FYI, this code has no use, just testing functions





function round(number)
if number-math.floor(number) > .5 then
return math.ceil(number)
else
return math.floor(number)
end
end










function test1()
term.setCursorPos(1, 35)
print("Hi!1")
sleep(2)
goroutine.spawn(tester, test2)
sleep(2)
print("Hi!3")
sleep(5)
end

function test2()
print("Hi!2")
end


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









function readColorBackground(color)
if color == "white" then
term.setBackgroundColor(1)
elseif color == "orange" then
term.setBackgroundColor(2)
elseif color == "magenta" then
term.setBackgroundColor(4)
elseif color == "lightBlue" then
term.setBackgroundColor(8)
elseif color == "yellow" then
term.setBackgroundColor(16)
elseif color == "lime" then
term.setBackgroundColor(32)
elseif color == "pink" then
term.setBackgroundColor(64)
elseif color == "gray" then
term.setBackgroundColor(128)
elseif color == "lightGray" then
term.setBackgroundColor(256)
elseif color == "cyan" then
term.setBackgroundColor(512)
elseif color == "purple" then
term.setBackgroundColor(1024)
elseif color == "blue" then
term.setBackgroundColor(2048)
elseif color == "brown" then
term.setBackgroundColor(4096)
elseif color == "green" then
term.setBackgroundColor(8192)
elseif color == "red" then
term.setBackgroundColor(16384)
elseif color == "black" then
term.setBackgroundColor(32768)
end
end

function readColorText(color)
if color == "white" then
term.setTextColor(1)
elseif color == "orange" then
term.setTextColor(2)
elseif color == "magenta" then
term.setTextColor(4)
elseif color == "lightBlue" then
term.setTextColor(8)
elseif color == "yellow" then
term.setTextColor(16)
elseif color == "lime" then
term.setTextColor(32)
elseif color == "pink" then
term.setTextColor(64)
elseif color == "gray" then
term.setTextColor(128)
elseif color == "lightGray" then
term.setTextColor(256)
elseif color == "cyan" then
term.setTextColor(512)
elseif color == "purple" then
term.setTextColor(1024)
elseif color == "blue" then
term.setTextColor(2048)
elseif color == "brown" then
term.setTextColor(4096)
elseif color == "green" then
term.setTextColor(8192)
elseif color == "red" then
term.setTextColor(16384)
elseif color == "black" then
term.setTextColor(32768)
end
end

function setColorBox(x1, y1, x2, y2, color)
prevx, prevy = term.getCursorPos()
differencex = x2 - x1
differencey = y2 - y1
countx = 0
county = 0
repeat
term.setCursorPos(x1 + countx, y1 + county)
readColorBackground(color)
print(" ")
countx = countx + 1
if (countx - 1) == differencex then
countx = 0
county = county + 1
end
until county == differencey + 1
term.setBackgroundColor(32768)
term.setCursorPos(prevx, prevy)
count = 1
end

function setColorPixel(x, y, color)
term.setCursorPos(x, y)
readColorBackground(color)
print(" ")
term.setBackgroundColor(32768)
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)
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



setColorBox(1, 1, 5, 5, "lightGray")



goroutine.spawn(tester2, button1)


setColorBox(4, 4, 32, 16, "red")
setColorBox(6, 6, 30, 14, "lightBlue")
setColorBox(10, 9, 26, 9, "white")
term.setCursorPos(14, 9)
term.setTextColor(32768)
term.setBackgroundColor(1)
print("Click Me!")
term.setCursorPos(1, 18)
print("1234567890123456789012345678901234567890")
sleep(5)
while true do
event,arg,x,y = os.pullEvent("mouse_click")
if (x >= 10 and x <= 26) and y == 9 then
setColorBox(4, 20, 20, 20, "green")
break
end
end

setButton(3, 9, 15, "blue", "white", "Durr")
GopherAtl #13
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.
brett122798 #14
Posted 12 January 2013 - 06:48 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.
That's not the button that won't go.. Here, just use this script instead..
Spoiler

-- Excuse me for my messy code, havent gotten around to making it better

-- FYI, this code has no use, just testing functions





function round(number)
if number-math.floor(number) > .5 then
return math.ceil(number)
else
return math.floor(number)
end
end



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









function readColorBackground(color)
if color == "white" then
term.setBackgroundColor(1)
elseif color == "orange" then
term.setBackgroundColor(2)
elseif color == "magenta" then
term.setBackgroundColor(4)
elseif color == "lightBlue" then
term.setBackgroundColor(8)
elseif color == "yellow" then
term.setBackgroundColor(16)
elseif color == "lime" then
term.setBackgroundColor(32)
elseif color == "pink" then
term.setBackgroundColor(64)
elseif color == "gray" then
term.setBackgroundColor(128)
elseif color == "lightGray" then
term.setBackgroundColor(256)
elseif color == "cyan" then
term.setBackgroundColor(512)
elseif color == "purple" then
term.setBackgroundColor(1024)
elseif color == "blue" then
term.setBackgroundColor(2048)
elseif color == "brown" then
term.setBackgroundColor(4096)
elseif color == "green" then
term.setBackgroundColor(8192)
elseif color == "red" then
term.setBackgroundColor(16384)
elseif color == "black" then
term.setBackgroundColor(32768)
end
end

function readColorText(color)
if color == "white" then
term.setTextColor(1)
elseif color == "orange" then
term.setTextColor(2)
elseif color == "magenta" then
term.setTextColor(4)
elseif color == "lightBlue" then
term.setTextColor(8)
elseif color == "yellow" then
term.setTextColor(16)
elseif color == "lime" then
term.setTextColor(32)
elseif color == "pink" then
term.setTextColor(64)
elseif color == "gray" then
term.setTextColor(128)
elseif color == "lightGray" then
term.setTextColor(256)
elseif color == "cyan" then
term.setTextColor(512)
elseif color == "purple" then
term.setTextColor(1024)
elseif color == "blue" then
term.setTextColor(2048)
elseif color == "brown" then
term.setTextColor(4096)
elseif color == "green" then
term.setTextColor(8192)
elseif color == "red" then
term.setTextColor(16384)
elseif color == "black" then
term.setTextColor(32768)
end
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)
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)



GopherAtl #15
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.
brett122798 #16
Posted 12 January 2013 - 07:08 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.
That's actually not true. If you run my previous code and click that button while the program is running, it does nothing.
GopherAtl #17
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.
brett122798 #18
Posted 12 January 2013 - 07:15 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.
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.
GopherAtl #19
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.
brett122798 #20
Posted 12 January 2013 - 07:32 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.
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.
GopherAtl #21
Posted 12 January 2013 - 07:49 PM
I will probably rework the example to not be an example startup file, or at least include an example use without the startup file, as in most cases it is probably better to call run() in your program rather than having goroutines running behind the shell. I have some changes planned which will make it behave better when run globally from startup, but making the changes hasn't been a priority.

Unrelated, I belatedly updated the main post to document some new features I posted to the pastebin version a couple of weeks ago. Spawned coroutines can now have their own specified terminal redirect objects, which will automatically be used whenever the routine runs.
brett122798 #22
Posted 12 January 2013 - 07:58 PM
I will probably rework the example to not be an example startup file, or at least include an example use without the startup file, as in most cases it is probably better to call run() in your program rather than having goroutines running behind the shell. I have some changes planned which will make it behave better when run globally from startup, but making the changes hasn't been a priority.

Unrelated, I belatedly updated the main post to document some new features I posted to the pastebin version a couple of weeks ago. Spawned coroutines can now have their own specified terminal redirect objects, which will automatically be used whenever the routine runs.
I've figured everything out(I believe) and it's just amazing. Absolutely incredible! By the way, I'm planning on making an OS, and this API would be a huge part of it. My question to you is, could I put this API into my code so people wouldn't have to download a bunch of things? You'd surely get credit in the credits.
GopherAtl #23
Posted 12 January 2013 - 08:33 PM
Sure, so long as I'm credited in some way, do with it as you will. :)/>
brett122798 #24
Posted 12 January 2013 - 08:34 PM
Sure, so long as I'm credited in some way, do with it as you will. :)/>
Thanks so much! :D/>
ArchAngel075 #25
Posted 13 January 2013 - 01:15 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!

yeah, i looked at it now and read the fine print stating "sample program" my mistake :)/>
Exerro #26
Posted 13 January 2013 - 07:38 AM
ok this looks very interesting but i dont have a clue if it will do what i need…i need a program to run the shell and modify the term and os api so my program gets every key pressed and every char written to the screen but if i use parallel either it will only display once or keep running the shell over and over again…i want something that will run a coroutine in the background even after the program has finished…can this api do that?
edit: I downloaded it and every time i try running it a random number is displayed :l the number is 24

edit2: got something working but now i implemented my code it doesnt work
Spoiler

os.loadAPI("goroutine")
screen.write = term.write
display = {}
for i = 1,51 do
display[i] = {}
for k = 1,19 do
  display[i][k] = ""
end
end
function string.split(str,pat)
local t = {}
local fpat = "(.-)" .. pat
local last_end = 1
local s, e, cap = str:find(fpat, 1)
while s do
  if s ~= 1 or cap ~= "" then
table.insert(t,cap)
  end
  last_end = e+1
  s, e, cap = str:find(fpat, last_end)
end
if last_end <= #str then
  cap = str:sub(last_end)
  table.insert(t, cap)
end
return t
end
function term.write( text )
text = tostring(text)
text = string.split(text,"")
w, h = term.getCursorPos()
w = (w > 51 and 51) or (w < 1 and 1) or (w)
h = (h > 19 and 19) or (h < 1 and 1) or (h)
for i = 1,#text do
  display[(w+i)-1][h] = text[i]
end
end
function test()
for i = 1,#display do
  for k = 1,#display[i] do
   term.setCursorPos(i,k)
   screen.write(display[i][k])
  end
end
end
--[[function getInput()
event, p1, p2, p3, p4, p5 = os.pullEvent()
if not (event == "timer" and p1 == z) then
  return event, p1, p2, p3, p4, p5
end
end]]
function main()
goroutine.spawn("display",test)
--writePos()
shell.run("shell")
end
goroutine.run(main)
GopherAtl #27
Posted 13 January 2013 - 10:09 AM
It looks like what you want is a redirect wrapper that buffers what is written to the screen. I have an api for that as well that I was planning to release eventually. I'm not sure what you want to do with the buffer exactly, but the two together may let you do it with ease.

The buffer is on pastebin here: http://pastebin.com/fU9Kj9zr ( pastebin get fU9Kj9zr buffer )

here's a simple example I threw together, uses goroutines and buffers to run a shell with a background coroutine that prints the contents of the screen to a file when you press F12. (only prints the characters, not the color data - you can access color info just as easily though) Lots of detailed comments in this, plus as a bonus included my usual error logging coroutine, which is a must-have when debugging programs using goroutines.

Spoiler

--get the size of the screen
local w,h=term.getSize()
--create fullscreen buffer
local myBuffer=buffer.createRedirectBuffer(w,h)
--make it active
myBuffer.makeActive()

--define a coroutine to save a screenshot
function saveScreenshots()
  local index=1
  while true do
	local _,key=os.pullEvent("key")
	if key==keys.f12 then
	  file=fs.open("screenshot"..index,"w")
	  --increment index for next time
	  index=index+1
	  for y=1,h do
		for x=1,w do
		  file.write(myBuffer[y][x].char)
		end
		file.write("\n")
	  end
	  file.close()
	end
  end
end
--[[
this is a handy coroutine to run while debugging any app using goroutines,
it detects coroutine_error messages and appends them to a log file.
Without it, you have no way of knowing why a coroutine crashed, because they
will usually crash silently, without displaying the error to the screen
like they normally would.
--]]
function logCoroutineErrors()
  while true do
	local _, name, err=os.pullEvent("coroutine_error")
	local file=fs.open("goroutine.log","a")
	file.write("coroutine '"..name.."' crashed with the following error: "..err)
	file.close()
  end
end

function runShell()
  shell.run("shell")
end

--main function
function main()
  --spawn the error logging coroutine
  goroutine.spawn("errorLogger",logCoroutineErrors)

  --spawn the screenshot coroutine
  goroutine.spawn("screenshots",saveScreenshots)

  --spawn a runShell coroutine using our redirect
  goroutine.spawnWithRedirect("shell",runShell,myBuffer)

  --wait for a coroutine to end
  local _,name=os.pullEvent("coroutine_end")
  --shut it all down and exit

  --kill the coroutines - we could skip the one named in the event but this won't hurt anything
  goroutine.kill("shell")
  goroutine.kill("screenshots")
  goroutine.kill("errorLogger")

end
--run it
goroutine.run(main)
--when it exits, restore the redirect
term.restore()
--[[
you could do an os.shutdown() here if you were running from startup and
didn't want them to be able to access a shell without your background
coroutines
--]]

you can grab this program from pastebin as well, here http://pastebin.com/khYEAUDM ( pastebin get khYEAUDM example ) If you're not calling os.loadAPI on buffer and goroutines in startup, or just using this as startup, you'd have to add the lines to the top to load both APIs first.
Exerro #28
Posted 14 January 2013 - 01:24 AM
i was trying to make a recording program and term.write would go through the program first and save it
GopherAtl #29
Posted 14 January 2013 - 09:25 PM
you would probably want to implement a custom redirect buffer to do that. The code for the buffer api might give you some ideas how to approach that; basically you'd want to append to a file each call to write, clear, clearLine, setCursorPos, and maybe setCursorBlink, probably with timing data, in a way that could be read and repeated by another program.

two basic approaches: it could run from shell, similar to the mon command to run a program on a monitor, where you say, ex, "record worms" and it runs worms, recording it. Wouldn't need goroutines for that. Other option is having goroutines run in startup, putting a video capture routine in the background, with recording toggled by pressing some key. That would use coroutines, tho not necessarily goroutine (parallel.waitForAny in startup could do the same).

Explaining in more detail than that is beyond the scope of this thread, I'm afraid!
GopherAtl #30
Posted 16 January 2013 - 05:14 PM
Bump for complete rework of the original thread, and the formal introduction of the redirect api (previously the buffer api).

Also integrating ctrlkeys here, requested the original thread be locked. One thread will be easier to follow/manage, and my apis are increasingly designed to be used together.
Eric #31
Posted 17 January 2013 - 12:33 PM
Where'd the download link for goroutines go? I'd like to read the code, but there seems to be no download link!
GopherAtl #32
Posted 17 January 2013 - 12:34 PM
oh! sorry about that, somehow overlooked that when compeltely redoing the post >.< fixing now…
MudkipTheEpic #33
Posted 19 January 2013 - 03:28 PM
Used goroutine.launch() in startup without anything else, (testing for what it does :P/>) and it crashed my MC when i went into the world… every time o_O XD
GopherAtl #34
Posted 19 January 2013 - 05:12 PM
crashed the cc computer, or minecraft? O_o and version?
MudkipTheEpic #35
Posted 20 January 2013 - 05:03 AM
crashes my minecraft window, and goroutine current version and cc 1.481
GopherAtl #36
Posted 20 January 2013 - 05:40 AM
show me your startup file?

I'm assuming you weren't checking if it was loaded already, though I'm rather surprised that would crash minecraft - just tried it myself, didn't do anything like that for me. Anywy, give the update a try.
MudkipTheEpic #37
Posted 20 January 2013 - 01:14 PM
Here's my startup:


os.loadAPI("goroutine")
goroutine.launch()

Edit: Trying da update…

Edit: Edit: Replicated error on a new world with that script( latest version ), and then opening a new computer. The CC computer screen went blank, and then MC crashed.
Edited on 20 January 2013 - 12:33 PM
GopherAtl #38
Posted 20 January 2013 - 05:37 PM
wait, you're installing it into rom?



don't do that. It is designed to run from the normal startup, after everything else has loaded. I might look into making a version meant to be installed in rom, but it would install itself very differently than this does.

:edit: I've added the check directly to the launch function which may eliminate the problem for you, but it was not intended to be run that way so. You say you placed an ew computer and opened it and it immediately crashed, so where exactly are you putting that?
MudkipTheEpic #39
Posted 21 January 2013 - 04:10 AM
wait, you're installing it into rom?



don't do that. It is designed to run from the normal startup, after everything else has loaded. I might look into making a version meant to be installed in rom, but it would install itself very differently than this does.

:edit: I've added the check directly to the launch function which may eliminate the problem for you, but it was not intended to be run that way so. You say you placed an ew computer and opened it and it immediately crashed, so where exactly are you putting that?

Actually, I installed it into a regular computer's startup, not rom/startup. Here's what I did:
  1. Place cpu down.
  2. Get goroutine off pastebin.
  3. Write that script in "edit startup".
  4. Reboot cpu. Cpu will start to flash the CraftOS screen.
  5. Place another cpu down.
  6. Screen will be blank, and Minecraft will crash in a few seconds.

Trying the new version now…..

Edit: You dont even have to place another CPU down! Mc crashes with no error screen. The console said it spawned 1964 coroutines….. o_O
Edited on 21 January 2013 - 03:15 AM
GopherAtl #40
Posted 21 January 2013 - 05:09 AM
show me your startup file?

I'm assuming you weren't checking if it was loaded already, though I'm rather surprised that would crash minecraft - just tried it myself, didn't do anything like that for me. Anywy, give the update a try.

Check if it is loaded before loading. the startup code in the OP gives you code to do this.



goroutine

Usage
SpoilerGoroutine can be used in two ways. First, it can be launched in startup, using goroutine.launch(), so that the main shell itself is a coroutine it manages. In this setup, programs run from the shell can use spawnBackground to create coroutines that will continue running even after those programs exit. In fact, such background routines will may continue running even if the shell itself exits, causing the computer to appear to "hang." This is expected but undesired behavior that may be addressed in a future version. It should still respond to ctrl+r or ctrl+s to reboot or shutdown.

Example code to properly launch goroutines from a startup file:
Spoiler
--this should always be the very first thing in your startup file.
--is goroutine already loaded?
if not goroutine then
--it is not, load it now
os.loadAPI("apis/goroutine") --change the path as necessary to where you've stored goroutine
--now launch it
goroutine.launch()
--nothing else in this if block will run, if launch was successful
print("Unexplained error launching goroutine!")
else
--it IS already loaded
print("goroutine initialized.")
end

--the rest of your startup code goes here
pastebin: Cifjwb0Z
Still no clue why this crashes for you, tho, doesn't cause any problems at all for me even when I deliberately run it wrong, as you seem determined to do.
MudkipTheEpic #41
Posted 21 January 2013 - 07:12 AM
show me your startup file?

I'm assuming you weren't checking if it was loaded already, though I'm rather surprised that would crash minecraft - just tried it myself, didn't do anything like that for me. Anywy, give the update a try.

Check if it is loaded before loading. the startup code in the OP gives you code to do this.



goroutine

Usage
SpoilerGoroutine can be used in two ways. First, it can be launched in startup, using goroutine.launch(), so that the main shell itself is a coroutine it manages. In this setup, programs run from the shell can use spawnBackground to create coroutines that will continue running even after those programs exit. In fact, such background routines will may continue running even if the shell itself exits, causing the computer to appear to "hang." This is expected but undesired behavior that may be addressed in a future version. It should still respond to ctrl+r or ctrl+s to reboot or shutdown.

Example code to properly launch goroutines from a startup file:
Spoiler
--this should always be the very first thing in your startup file.
--is goroutine already loaded?
if not goroutine then
--it is not, load it now
os.loadAPI("apis/goroutine") --change the path as necessary to where you've stored goroutine
--now launch it
goroutine.launch()
--nothing else in this if block will run, if launch was successful
print("Unexplained error launching goroutine!")
else
--it IS already loaded
print("goroutine initialized.")
end

--the rest of your startup code goes here
pastebin: Cifjwb0Z
Still no clue why this crashes for you, tho, doesn't cause any problems at all for me even when I deliberately run it wrong, as you seem determined to do.

Ya, I like to look into programs and report bugs. Well, I was just trying to help report a bug… ;)/>
GopherAtl #42
Posted 25 January 2013 - 08:24 AM
well, I've made a new version that is resistant to this sort of thing. Reloading the goroutine API now copies state over from the previous loaded instance, rather than clobbering it. So you can now legimitately launch it from startup in the background with this more trivial code at the beginning of your startup:


--these 2 lines should always be first - code before these will run twice.
os.loadAPI("goroutine")
goroutine.launch()

--normal startup here...
MudkipTheEpic #43
Posted 25 January 2013 - 11:57 AM
well, I've made a new version that is resistant to this sort of thing. Reloading the goroutine API now copies state over from the previous loaded instance, rather than clobbering it. So you can now legimitately launch it from startup in the background with this more trivial code at the beginning of your startup:


--these 2 lines should always be first - code before these will run twice.
os.loadAPI("goroutine")
goroutine.launch()

--normal startup here...

Thank you, and have a good day. :P/>
brett122798 #44
Posted 30 January 2013 - 06:33 PM
Kinda realized, I was the one to bring this topic back to life! :P/>
Skullblade #45
Posted 31 January 2013 - 01:32 AM
Congratulations Brett you deserve a slow sarcastic clap…*le claps*
GopherAtl #46
Posted 05 February 2013 - 09:07 AM
fixed some bugs in goroutines, proving I really need to test more; goroutine.kill() now actually works as intended, and the automatically-generated go.log file is more readable.

Just as importantly, I've added a set of utility programs to run from shell when goroutine is loaded in the background. Documentation is in the original post, under goroutine->shell utilities, but a quick summary:
ps - list the active coroutines
bg - run a program in the background, returning to shell
monitor - like the built-in monitor program, but runs monitor program in background and returns to the shell immediately, so you can continue to use the computer normally
kill - kill a coroutine
GopherAtl #47
Posted 07 February 2013 - 08:54 AM
:bump:
added proper handling of term.redirect to goroutines, which was long-overdue, so coroutines can now call term.redirect without producing unexpected behavior.

Also, finally added one of those long-promised "advanced examples," my multishell program, which uses all three APIs: goroutine to handle multiple shell instances running at once, redirect to create and manage redirect buffers for each, and ctrlkeys to generate the ctrl_key events it uses to switch between shells. This is such a handy program, I find myself using it all the time lately, so I've also made a separate thread for it in programs, where an installer program can be downloaded to automatically download the program and all three APIs, but I list it here as well as example code showing how powerful these APIs can be when used together.
Sxw #48
Posted 10 February 2013 - 07:05 AM
Just wanna tell you, i'm working on a project that uses these, ill be sure to credit you when its done :)/>
GopherAtl #49
Posted 10 February 2013 - 10:04 PM
sxw: most excellent, glad to hear it, can't wait to see what you come up with!

Found &amp; fixed some glitches in redirect and the new multishell example, now multishell is working in color, and added some checks to prevent certain things like textutils.tabulate that send fractional values to setCursorPos from causing text to become invisble - meaning you can actually see the full output of the ls command, and other tabulated output, now. Also fixed a conditional in redirect that was causing the last row not to display.

There remains an issue that multishell doesn't work properly when run after goroutines was launched in the background. Will be addressing that tomorrow, for now, no more code, sleeps.
Sxw #50
Posted 11 February 2013 - 05:10 AM
Is there a way to make just 1 routine not receive a certain event? I'm building a multiuser ssh and using goroutine.spawnWithRedirect, but i need it to only receive key events from the remote client. It will run in the background and might have multiple users connected to different terminals at once…
GopherAtl #51
Posted 11 February 2013 - 08:49 AM
hrm. No way to block a particular event to a single coroutine at present, closest thing is assigning the events to one particular coroutine, and that coroutine can decide which other coroutine, if any, to pass it to. What I do with my multishell program is assign all the user input events to a manager coroutine which then passes them to the coroutine that is currently active, so no other coroutines get key events. That system needs some love, as it needs to send the event to children of that coroutine as well; currently only the main coroutine, the one passed into goroutine.run(), can call goroutine.assignEvent to assign events a given coroutine.
anonimo182 #52
Posted 12 February 2013 - 11:21 AM
Nice program, gonna use it in a future program!
FuuuAInfiniteLoop(F.A.I.L) #53
Posted 17 February 2013 - 12:40 PM
for redirect you should add the function to the buffers to run a program without need of redirect to the buffer and shell. run so we can create windows running at the same time!
GopherAtl #54
Posted 17 February 2013 - 03:10 PM
by combining goroutine and redirect, you can already do this quite easily. If you call goroutine.spawnWithRedirect, and pass in a redirect buffer object, goroutines will automatically switch the redirects appropriately when swiching coroutines. If you set the dimensions right when creting the buffers and set the positions correctly when calling makeActive, you can even do split-screen systems (though currently there's some issues with the apparent cursor position when using read in one buffer while sharing the screen with other active buffers, which I hope to resolve in the next version)

:edit: here's an example that demonstrates having an input routine calling read() on the bottom line while another routine displays text to a scrolling area above it, as you might want in many applications, including chat clients and other programs that accept user commands while displaying updates based on external events.

Spoiler

--coroutine that simply calls read() in a loop and sends results as "input" events for other coroutines
function coInputLine()
  while true do
    write(">")
    --get a line of input
    local input=read()
    --send as event to main routine
    os.queueEvent("input",input)
  end
end

 
function main()
  local scrW,scrH=term.getSize()

  --create redirect buffer for log view
  local logView=redirect.createRedirectBuffer(scrW,scrH-2)
  --and one for input line
  local inputBuffer=redirect.createRedirectBuffer(scrW,1)

  --draw a separator line
  term.clear()
  term.setCursorPos(1,scrH-1)
  write(string.rep("=",scrW))

  --spawn the input routine using it's redirect
  goroutine.spawnWithRedirect("input",coInputLine,inputBuffer)
  --redirect ourselves to the buffer
  term.redirect(logView)

  --make both buffers active, so they'll draw to screen
  logView.makeActive(1,1)
  inputBuffer.makeActive(1,scrH) 
  while true do
    local event={os.pullEventRaw()}
    if event[1]=="input" then
	  print("local>"..event[2])
    elseif event[1]=="modem_message" then
	  --1.5-compatible version
	  print(event[3]..">"..event[4])
    elseif event[1]=="rednet_message" then
      --pre-1.5 version
      print(event[2]..">"..event[3])
    elseif event[1]=="coroutine_end" and event[2]=="input" then
      --exit if input coroutine exited
      break
    elseif event[1]=="terminate" then
      break
    end
  end

  --restore redirect on exit
  term.restore()
  term.setCursorPos(1,scrH)
  print("\nGoodbye..")
end

--run the main program function through goroutines
goroutine.run(main)
 

Run it and you whatever you type will appear in the top list; attach modems (remember to open them before running this program) and send to it over rednet from another computer and you'll see that it also displays those messages, while not interfering at all with the input line at the bottom.
Pastebin: f9q93gmT
FuuuAInfiniteLoop(F.A.I.L) #55
Posted 20 February 2013 - 03:10 PM
I want to use redirect and goroutine in my os(as part of the windows) if you allow me send me a PM
GopherAtl #56
Posted 20 February 2013 - 03:37 PM
As I said at the end of the opening post, anyone is free to use these apis however they like. That's why I made, documented, and released them :)/>
GopherAtl #57
Posted 13 March 2013 - 03:38 PM
Update bump!

I've added a new api, ggui. No documentation in the OP yet, but there are two sample programs with it that demonstrate (almost) all of the features.

current gui elements:
labels - just that. display text.
buttons - like labels, but can be selected and clicked, and generate events when they are.
text fields - basic, single-line text fields.
graphics - "pixel" image elements that import files made in cc paint

general features:
mouse and keyboard-based focus control - tab or arrow to cycle through focus-enabled elements (buttons and text fields currently), click or enter to activate buttons.

dynamic styles - built-in defaults that can be overridden and extended to alter the colors, decoration characters, alignment, and other behaviors of all elements, with the ability to define different styles for color or black-and-white computers for easy compatibility with both

graphic slicing - when creating graphic elements, can optionally specify source x, y, width, and height values, allowing only a portion of the image to be loaded and rendered. Caching of the original source allows multiple graphic elements to efficiently use different parts of a single graphic file, basically allowing a form of spritesheets.

event callback system - write functions to handle different events and then assign them as event handlers. Handlers can specify additional filters beyond just the event type, so all the ugly if-if-elseif-end-elseif-elseif-end nonsense you normaly go through to figure out what event you're dealing with is hidden away in the api, keeping your code cleaner.


The sample programs:
.
login - a sample login screen, that works on color or b/w computers. Demonstrates buttons, labels, text fields, graphics, styles, and event handlers. (note: this is not a secure login program, it is simply a demonstration of the gui system.)

redstone - a program for monitoring and setting redstone state on all sides of a computer. Demonstrates buttons, labels, styles, event handlers, and custom tab ordering.

Downloads

Installer pastebin get fq8SinNB gguisetup
Installer will download the api, the two sample programs, and the graphics files required by the samples. just download and run "guisetup <dir>" to install to a specified directory.

If you don't care about examples and just wanna poke through the api itself, here ya go…
The api pastebin get i0EyjZ2W ggui
GopherAtl #58
Posted 16 March 2013 - 07:59 AM
update, still beta and still no documentation but ggui has been revised to include the first pass on implementing screens, which will be used for a lot of things, but for now are being used to let you write programs that run across multiple monitors at once. Particularly when combined with the new lan peripherals in cc 1.51, this will let you run a whole room full of interactive monitors off of a single computer with relative ease.

since people like pictures more than words, and the ggui beta is actually visual, unlike my other apis for which screenshots just don't make sense at all, some screenshots!


Same program, basic computer (actually, advanced computer I tricked into thinking it was a basic computer…)

aand the redstone program, running on monitors.


Links are the same as the last post, and can be found in the OP as well.
Jan #59
Posted 16 March 2013 - 11:00 PM
…snip…
That looks very nice! But does it have keyboard support?
Best would be if you could be able to control the same program with a touch-monitor or a keyboard.
That an on-screen keyboard would show up if you are in a text-field, while the program is running on a monitor
And that you can use TAB or arrow keys to switch fields if you are running the same program on a grey computer

Maybe this hybrid-interface is not optimal for every type of program (paint i.e.), but it would be handy for the program-maker,
because he doesnt have to make a touch-monitor and keyboard version of his program.
GopherAtl #60
Posted 17 March 2013 - 03:00 AM
But of course it has keyboard support! b&amp;w support would be silly if it didn't :)/> And I actually plan on adding an on-screen keyboard for touch-screen monitors, but have not done so yet.

Focusable elements (currently buttons and text fields) are automatically added to a tab group in the order they're created, which you can cycle through with up/down, left/right (not with text fields, which capture these for cursor movement), and tab. You can also override this tab order, setting up a different tab order manually using functions - the redstone sample program demonstrates this.
Jan #61
Posted 17 March 2013 - 04:03 AM
But of course it has keyboard support! b&amp;w support would be silly if it didn't :)/> And I actually plan on adding an on-screen keyboard for touch-screen monitors, but have not done so yet.

Focusable elements (currently buttons and text fields) are automatically added to a tab group in the order they're created, which you can cycle through with up/down, left/right (not with text fields, which capture these for cursor movement), and tab. You can also override this tab order, setting up a different tab order manually using functions - the redstone sample program demonstrates this.
Okay, great :D/>
Fenthis #62
Posted 15 May 2013 - 07:27 PM
ggui is absolutely awsome. One question: is there a way to register custom event handlers for events that the UI does not handle? Say I want to have a handler for the Misc Peripheral's sort-event running in the background. Also, can a co-routine modify the UI and somehow tell it to redraw, if I chose to do that as a background process?
GopherAtl #63
Posted 21 May 2013 - 06:36 PM
been quite busy for a while, first time on here, or even thinking about minecraft, in over a month… not sure I'll be particularly active in the immediate future either. While I'm here…

Fenthis: You can call addEventHandler functions to handle ANY events, not just ggui-specific events. For example, the login sample program has this listener line:

guiScreen.addEventHandler(  {"button_activate", guiScreen.id, loginButton} ,  onLogin  )
which calls the onLogin function (defined in the example) when an event "button_activate" occurs, where it's first argument equals guiScreen.id (the id of the screen) and the second argument is loginButton, the id of the login button. You could just as easily do something like this:



local onRednetMessage(event,modemSide, senderChannel, replyChannel, message, senderDistance)
  --//yer code here
end

guiScreen.addEventHandler(  {"modem_message" } ,  onRednetMessage )

This would cause onRednetMessage to be called every time a modem_message event occurs. This can also be used with any custom event types, sent by your code or other APIs using os.queueEvent(). Thanks to this system, many common things you might be tempted to do with coroutines, you can do without coroutines, using only event handlers. (my past APIs and programs had been rather coroutine-heavy, something I made a deliberate effort to move away from with the ggui api, because coroutines are resource-intensive in luaj)

If you do have background coroutines, I can't think of any reason that any number of coroutines can't modify the ggui at the same time, as long as they have a way to access the same gui objects, which they should if the coroutines are defined in the same program.
GopherAtl #64
Posted 17 July 2013 - 01:12 AM
found and fixed two minor bugs…

goroutines - background processes were crashing if they attempted to print or do certain other terminal i/o ops
redirect - can now correctly create black and white buffers on color computers. Before, it would always promote it to color if you were on an advanced computer.
CoderPuppy #65
Posted 30 August 2013 - 02:05 PM
Bug: Labels are constrained to a single line. This is because makeLabelString constrains the entire string's length to the width of the label. It could easily be fixed by looping through the lines and constraining each to the width.

Edit: Also I want to be able to override the ggui event handlers. Example: Override left and right key handlers to switch between menus.
Edit: Also can I hide/remove elements.
GopherAtl #66
Posted 30 August 2013 - 03:24 PM
that is not a bug, just a design choice. If labels wrapped to multiple lines, it could cause problems with automatic resizing of screens in some cases. It's not ready for release yet, but I've been working on ggui, and just added textArea components, which are multi-line, can scroll vertically, are word-wrapped, and can be either read-only or editable. Those will be what you want.

As for overriding the event handlers, hmm. I might be able to add the ability to override key event handlers, I'll have to think about the best way to implement it for a bit. Your specific use case sounds like you want a more robust form of navigation besides the basic tab ordering it uses now, which is something I've been contemplating improving as well.

re: hiding or removing elements, at present, no. I don't think I'll be allowing removing elements at all, but I will probably add enabling/disabling elements, and I certainly plan on adding page stacks, which will allow most forms of dynamic gui alteration you might want - page stacks are basically tab groups, without the tabs (which are just a menu of buttons for selecting pages anyway)

Not sure how many features will make it into the next update, but certainly TextAreas will, and probably frames, scroll bars, and with luck, list boxes as well. Various forms of pop-ups (menus, pull-downs, dialog boxes, etc) will happen eventually but are unlikely in the next update.
Kermina #67
Posted 02 September 2013 - 02:54 PM
Hi, at first I want to thank you for your redirect API. I have altered it to my needs and it works great.

But I find a bug that nobody seems to spot. It cause edit (and posibly other programs) to crash.
On line 15 of your redirect API you have:
if cx<=buffer.width then
Problem is that edit, when text is scrolled horizontaly, sets cursor x value to negative number and writes from that position.
To fix this problem simply alter line 15 to:
if cx >= 1 and cx<=buffer.width then

Since many use your redirect API, this secret flaw may cause probably big (and very rare) problems for many programs and shells. So if you know who use your redirect API, please tell them.

Thanks for all your work on these APIs.
GopherAtl #68
Posted 03 September 2013 - 12:35 PM
Ah! Thanks for that. I'll make that fix to the pastebin version now, thanks. Funny, I'm positive I ran into, and fixed, that bug once before. This is what I get for not using any sort of version control on my cc lua projects.
CoderPuppy #69
Posted 04 September 2013 - 11:39 PM
What does the "Ch" mean in "leftCh" for the ggui styles?
ElvishJerricco #70
Posted 05 September 2013 - 12:56 AM
Would like to note for ctrlkeys, you can check for shift better by seeing if the letter is capital or not. Something like bshift = char:upper() == char. Probably won't work for stuff like semicolons and stuff though unless you map out every char and it's respective shifted char, which can vary by keyboard.
GopherAtl #71
Posted 05 September 2013 - 07:17 AM
CoderPuppy: Character. They are characters drawn on to the left and right of the element, and the Focused variants are drawn only when the element has focus. Only supported by buttons and text fields, I think? leftChFocused and rightChFocused are the only ones used in the default style, I think, to add "[" and "]" surrounding buttons and text fields when they are selected, and that only in the monochrome style.

Elvish: Ehrum. When ctrl is held down, you don't get char events. That's kindof how ctrlkeys works in the first place. :)/>

Tho I should note that I've learned ctrlkeys doesn't work in an entirely consistent way between operating systems, there's inconsistencies at least with ctrl+v and ctrl+1 through ctrl+0 on osx vs windows.
ElvishJerricco #72
Posted 05 September 2013 - 10:45 AM
Elvish: Ehrum. When ctrl is held down, you don't get char events. That's kindof how ctrlkeys works in the first place.

Oh brainderp. My bad.
Geforce Fan #73
Posted 07 September 2013 - 06:08 PM
Hey, I'm experiencing a bug with this. When I use Goroutine along with the redirect thing, I can't use paintutils inside of my redirect.
edit: forgot I wasn't using the redirect with what I was doing. The bug is sitll there, not sure if it happens when you use redirect.
GopherAtl #74
Posted 07 September 2013 - 08:02 PM
I'd have to see some code, I can't think of any reason one particular api would become inaccessible?
Geforce Fan #75
Posted 05 October 2013 - 11:56 AM
By the way, is there any way to have a goroutine pause itself, like the default coroutine's yield? I'm trying to make advanced ram and cpu management, so one program can run while the other ones are frozen, then unfreeze the others when the user wants to instantly.
Also: I'm very impressed how goroutines can kill an event running an app–even shell– and the app quits aswell. Nice work there.
Lyqyd #76
Posted 07 October 2013 - 03:00 AM
I'm not sure what you mean. Coroutine.yield() or anything that wraps it should cause goroutines to yield in the same way. It would be up to you to make sure not to resume them until you wanted to.
jay5476 #77
Posted 27 October 2013 - 12:02 AM
the cursor blink after an error is red… but still writes whatever color you previously had
Lyqyd #78
Posted 28 October 2013 - 09:50 AM
Please provide complete reproduction instructions.
Symmetryc #79
Posted 22 November 2013 - 11:03 PM
Ctrl Keys seems a bit overly complex, couldn't you just do something like this?


local pullEvent = function(_filter)
    local t = {}
    while t[1] ~= (_filter or t[1] or 0) do
        t = {os.pullEvent()}
        t[1] = t[1] == "key" and (os.startTimer(0) == ({os.pullEvent()})[2] and "ctrl_key" or os.pullEvent() and t[1]) or t[1]
    end
    return unpack(t)
end

Or am I just missing something?

Edit: I've updated the code, but I can see that it has some problems; You can't pull events more than about 6 or 7 times a second, otherwise it may mistake ctrl_keys as normal keys :/.
Edited on 23 November 2013 - 08:04 AM
Magik6k #80
Posted 24 November 2013 - 09:23 AM
Awesome work! I'm definitely going to put these API's to my operating system.
Is there any limit for number of tasks created with goroutines?
GopherAtl #81
Posted 14 March 2015 - 01:55 AM
:blows dust off thread:

redirect and ggui have both been updated to work in this world of of stackless term.redirect behavior. I think. The sample programs seem to work again, in any case. Report any further issues with those two here.

goroutines and ctrlkeys… I've moved away from. The former, I've just moved away from that style of coroutine-heavy programming in CC, and ctrlkeys, I have heard said, had some platform-specific flaws in it's basic approach, and did not work consistently across different operating systems. I'm leaving them up, for now, but they won't be updated and, indeed, I've not even tested to see if they still work or not, so use at your own risk. As before, all of these APIs are wtfpl, meaning you can do whatever you want with them. Wanna use a modified version in your OS? Feel free! Got someone willing to give you cash money in exchange for a compeltely unaltered copy? Go ahead, take that sucker's money. I honestly don't care! Just do wtf you want!

Ctrl Keys seems a bit overly complex, couldn't you just do something like this?


local pullEvent = function(_filter)
	local t = {}
	while t[1] ~= (_filter or t[1] or 0) do
		t = {os.pullEvent()}
		t[1] = t[1] == "key" and (os.startTimer(0) == ({os.pullEvent()})[2] and "ctrl_key" or os.pullEvent() and t[1]) or t[1]
	end
	return unpack(t)
end

Or am I just missing something?

Edit: I've updated the code, but I can see that it has some problems; You can't pull events more than about 6 or 7 times a second, otherwise it may mistake ctrl_keys as normal keys :/.

yes. Yes, it could be much simpler. If I were writing it now, knowing all I've learned since then (ctrlkeys was one of my first apis posted on the forums), I would have made a version that didn't queue events or use timers at all.
Edited on 14 March 2015 - 12:58 AM
Geforce Fan #82
Posted 14 March 2015 - 03:05 AM
No offense, but goroutines where pretty useless :P/>
Redirect, however, is, and still is, and absolutely brilliant and super efficient API.
GopherAtl #83
Posted 14 March 2015 - 03:29 AM
goroutines was slick. My old multishell used it to great effect. It was a bit buggy, though, I'll grant you.
Geforce Fan #84
Posted 14 March 2015 - 03:59 PM
why not just use coroutines though?

edit:
I've found redirect buffers to be super laggy when active. They're great when you're going to blit(), but I'm probably going to go with a different buffer system for my OS. Game-engine will stay with redirect.
also: redrawing the screen every time there's a change…
not the best idea.

I'm going to make a buffer system that will, instead of redrawing the screen, will also write directly to it when active.
Also, the write() function will be somewhat lazy– it will do the absolute least math as possible, and save all calculaions for blit(). Meaning, it just tells the color of a pixel, and text is whatever you input–it doesn't do like [1] = "a" [2] = "b" [3] = "c", it does [1]="abc". And if you write to 2, the buffer becomes [1]="abc" [2] = "dc", but 2 has a higher operation number(a number that increases each time you write to the screen) and therefore is always done after 1.
Although, all x values are on a y table, I should have said the y table rather than the buffer.
Edited on 18 March 2015 - 01:01 AM