Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use socket for SC -> JS communication #37

Closed
wants to merge 9 commits into from

Conversation

ssfrr
Copy link

@ssfrr ssfrr commented Sep 27, 2017

This PR still needs some testing but seems functional. The two main parts are:

  1. Now uses a separate socket for getting sending back the result of an
    interpret call from SCLang. This way you can rely on getting the data
    back intact even if there's other stuff going over STDOUT. For example, if
    you're printing OSC debugging info while at the same time requesting data
    to use for auto-complete in the editor.
  2. Reworks the STDOUT handling state machine to work line-by-line. I wasn't
    planning on doing this as part of this PR but I ran into trouble where
    sometimes the state machine wasn't grabbing the CAPTURE:END blocks. IMO
    this setup is easier to read and no longer makes any assumptions about
    alignment of the callback data.

I still need to walk through the state machine to verify it's correctly
handling all the cases that the old one did before this is ready for full
review, but I figured I'd post progress in case you wanted to take a glance.

@crucialfelix
Copy link
Owner

You will want to use the test suite. Once you turn on jest it runs those tests before you can even blink.

I'm not sure what the address in use error is in here: https://travis-ci.org/crucialfelix/supercolliderjs/jobs/280304528

@crucialfelix
Copy link
Owner

Looks great so far !

@ssfrr
Copy link
Author

ssfrr commented Sep 28, 2017

OK, it's passing all the tests. I've done a little refactoring (I moved the socket stuff into sclang.js from sclang-io.js, and made sure the connection is cleaned up on quit. I also confirmed that this fixes #34.

Also closes #35.

I've tested it a bit running from node directly and also playing around in Atom, and AFAICT it seems to be working fine. Definitely kick the tires and let me know if there are issues.

@ssfrr ssfrr changed the title [WIP] Use socket for SC -> JS communication Use socket for SC -> JS communication Sep 28, 2017
@crucialfelix
Copy link
Owner

I will definitely kick tires and do code review. Great work !

@crucialfelix
Copy link
Owner

crucialfelix commented Oct 7, 2017

I haven't yet had a chance to test this. I will probably want to merge it to develop, but then there are some issues that need to be solved before a release.

Currently you could only run one supercollider.js at the same time. I often run many.

So the socket should be able to use unix domain sockets like /var/run/supercolliderjs_{PID} (id of the node.js process) and pass that through to supercollider. That would guarantee a unique, secure and easily configurable channel of communication.

But we should also support open TCP connections. It would be fun (and possibly not entirely useless) to connect to an sclang running on a beagleboard.

READY: 'ready'
CONNECTING: 'connecting',
READY: 'ready',
CAPTURING: 'capturing',
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want to keep capturing state in a separate variable. These states are broadcast to listeners mainly for watching when sclang is up and ready (or died etc.) You wouldn't want to broadcast a change any time there is an incoming response.

Copy link
Owner

@crucialfelix crucialfelix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the years go by quick. I do apologize. I have looked at it a few times, but kids, jobs and other ambitions are pretty distracting.

The main goal of the PR (as I understand it) is to allow larger responses to be returned from sclang without breaking or getting co-mingled with other STDOUT that sclang may be burping up.

The main blocker to landing this is that it doesn't just add a TCP connection for returning responses. It also reworks the state machine and parsing. It adds more places for things to go wrong or for internal state to get out of sync.

It's a bit messier than the previous implementation and doesn't fix or change anything.

The main win here is the TCP socket for responses.

So I'd like to include just that, make it optional, configurable port and leave the rest of the state and IO parser as is. It would still be able to parse STDOUT results. The TCP version if enabled would allow results to be sent that way. Probably it could send the Errors back too. They can get quite big.

I know you've moved on to other exciting things in life, so I'm not asking for changes.

@@ -24,6 +25,9 @@ import { SclangResultType } from '../Types';
// 'any' just opts out of type checking
type ChildProcessType = any; // child_process$ChildProcess;

const RESULT_LISTEN_HOST = 'localhost';
const RESULT_LISTEN_PORT = 22967;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TCP port is hardcoded, and so there could only be one sclang running at a time.

stateWatcher.on(name, (...args) => {
this.emit(name, ...args);
});
}
stateWatcher.on('state', newState => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that the stateWatcher event listener should be keeping track of changes. It is only supposed to re-emit the event. The consumer can keep track and ignore if it needs to.

The reason you had to put this in is because you added "CAPTURING" as a new state, but the intention of the states is only to track the major lifecycle of the sclang interpreter.

As you have it now it would go back and forth from READY to CAPTURING.

I think you are assuming that there can only be one interpreter call active at a time. In fact, you can launch many of them and each one many do async things over in supercollider lang, and return results or errors at any point in the future.

compiled = code.compile;

if(compiled.isNil, {
"\nSUPERCOLLIDERJS:%:CAPTURE:END".format(guid).postln;
"SUPERCOLLIDERJS:%:CAPTURE:END".format(guid).postln;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newlines are actually necessary in case sclang posted, not postln. The SUPERCOLLIDERJS messages need to always start on a newline.

if(resultSocket.isNil, {
"resultSocket must be connected before returning data".error;
}, {
var msg = (
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could have just done this.stringify(msg). Looks like you tried to do that but () is an Event in sc, not a simple dict.

@@ -15,9 +15,12 @@ export const STATES = {
COMPILED: 'compiled',
COMPILING: 'compiling',
COMPILE_ERROR: 'compileError',
READY: 'ready'
READY: 'ready',
CAPTURING: 'capturing'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It adds a new STATES "CAPTURING", but the intention of the states is only to track the major lifecycle of the sclang interpreter. As you have it now it would go back and forth from READY to CAPTURING.

I think you are assuming that there can only be one interpreter call active at a time. In fact, you can launch many of them and each one many do async things over in supercollider lang, and return results or errors at any point in the future.

@@ -33,70 +36,83 @@ export const STATES = {
*/
export class SclangIO extends EventEmitter {
states: Object;
responseCollectors: Object;
capturing: Object;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

responseCollectors and capturing is a dict storing responses. There may be more than one active at any time. They can post overlapping messages to STDOUT.

if (!stf.re.global) {
break;
}
var inputLines = input.split('\n');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see that parsing line by line is possibly better than multiline matching. I can also see a cleaner way to do it (than either my previous implementation or the one in this PR). But by changing this as much as you did, I'm now confused and can't quite tell what might break.

Yes, the tests run, but the reason I hadn't merged before is because I can't be sure now.

@ssfrr
Copy link
Author

ssfrr commented Jun 24, 2019

Thanks for taking the time to look at this, and sorry the PR co-mingled too many different things.

A bit after working on this PR I switched back to using the main SC IDE, so unfortunately I don't think I'll have the time to work on this any time in the near future. I won't be offended if you want to close the PR, or rip out any bits that might be useful if you still want to switch to TCP transport.

@crucialfelix
Copy link
Owner

I'm definitely going to transplant the TCP transport and make that work. I will try to cherry pick it in if I can, but definitely give you the credit. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants