Register forum user name Search FAQ

Gammon Forum

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

Easy Text editor?

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


Pages: 1  2  3  4  5 

Posted by Isthiriel   (113 posts)  Bio
Date Reply #60 on Fri 30 Mar 2007 06:04 PM (UTC)

Amended on Sat 31 Mar 2007 08:03 AM (UTC) by Isthiriel

Message
I spent some time thinking of a lot of very complicated ways that would almost-but-not-quite work until I asked myself what I was trying to acheive.

The problem is access to the world variable from a thread that hasn't been spawned by MUSHclient/WSH.

The simple answer: defer the execution of code requiring world from the Tk event thread to a WSH thread!

Isthiriel's Workarounds and Hacks'R'Us
or the uses and abuses of Python's exec statement.

Add to TextEditor.__init__:

		self._queue = []
		self._qlock = thread.allocate_lock()


And the following functions to TextEditor:

	def queue(self, code, locals = {}):
		#with self._qlock:
		self._qlock.acquire()
		self._queue.append((code, locals))
		self._qlock.release()

	def poll(self):
		#with self._qlock:
		self._qlock.acquire()
		while len(self._queue):
			code, locals = self._queue.pop(0)
			exec code in globals(), locals
		self._qlock.release()

(The with keyword is supposed to obviate the .acquire()/.release() calls as of Python 2.5. Supposed to.)

And replace the current global edit() function with:

apps = []

def edit():
	global apps
	apps.append(TextEditor(master = Tk()))
	thread.start_new_thread(apps[-1].mainloop, ())

def poll():
	global apps
	for app in apps:
		app.poll()


And the final step:
Add a timer to your world that fires every 0.1 seconds (or 0.2 seconds or whenever you feel like it really -- remember this is limited by the global timer-granularity preference) and calls the "poll" function.

Ok, now if we need to call a function that uses world we can defer it by quoting it and passing it as the parameter to queue().

For example, if we add the following to CreateMenu():

		filemenu.add_command(label = 'Send', command = self.OnSend)


And then add the OnSend function to TextEditor:

	def OnSend(self):
		tx = self.tx.get("0.0", END)
		self.queue("world.Send(tx)", locals())	    


The File menu now has a "Send" option that dumps the contents of the text box to the mud :)

This seems to work with no great disadvantages. The biggest problems are (1) the delay between clicking something in the editor and the timer firing in MUSHclient to actually handle the code and (2) there is a rumoured memory leak somewhere in the binding between MUSHclient, WSH and Python which is supposedly around 4KB per script call. So you want the timer rate to be as small as possible while keeping the potential leak manageable (0.1 seconds between calls => 40KB/sec => 2.5MB/min => 150MB/hr).

Oh, and as it stands any code still in the queue when the editor window is closed, disappears. (This could be fixed fairly easily.)

PS. Recompiling the script file while an editor window is open makes BAD THINGS happen.

PPS. Just checked the memory leak and with the timer set to 0.1 sec it's leaking about 5MB/min, so on a box with 512MB and a session time around 3-4 hours it's not practical to have a timer interval < 1 sec :(

PPPS. If you have an error in your script when you recompile, the timer will cause you to get a lot of "Could not find poll()" errors. For this reason the first five and last five lines of my .pys are:

vTimer = 'ScrStartup_TimersEnabled'
if None == world.GetVariable(vTimer):
	world.SetVariable(vTimer, world.GetOption('enable_timers'))
	world.SetOption('enable_timers', 0)
	world.DoCommand('Save')

if None != world.GetVariable(vTimer):
	world.SetOption('enable_timers', world.GetVariable(vTimer))
	world.DeleteVariable(vTimer)
	world.DoCommand('Save')
world.ColourNote('white', 'black', "Script Compiled")

This code disables the timers (and remembers their enabled/disabled state) for the duration of the compile (not a bad thing) and if there is an exception or a parse error the timers stay off while the compilation aborts. The next time you recompile (with hopefully fixed code) the timers are reenabled (or not, if they were disabled before you started the first recompile).
Top

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

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


157,748 views.

This is page 5, subject is 5 pages long:  [Previous page]  1  2  3  4  5 

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

Go to topic:           Search the forum


[Go to top] top

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