Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

802
LINES

< > BotCompany Repo | #1030627 // DynGazelleMultiBot

JavaX fragment (include) [tags: use-pretranspiled]

Uses 44637K of libraries. Click here for Pure Java version (35156L/196K).

// Gazelle's "left brain hemisphere" (where all the bots run)

set flag DynModule. // for transpilation

//set flag dm_evalJava_withModule_debug.
//set flag veryQuickJava3_debug.

// store bot data per conversation
concept Conversation {
  S cookie;
  S dataStruct;
}

asclass DynGazelleMultiBot > DynGazelleBot {
  switchable int maxEvalResultLength = oneMegabyte_int();
  switchable double evalTimeout = 30.0;
  switchable long botProcessed; // timestamp of last post processed by bots
  switchable int maxBotAnswerLength = 100000;
  switchable bool runAutoBots = true;
  switchable bool doPython = true;
  switchable int maxRuntimeMinutes = 60;
  transient new L<Bot> bots;
  Map<Long, GazellePost> allPosts = syncTreeMap();
  transient Map<Long, O> loadedCodePosts = syncTreeMap();
  transient bool considerMasterPostsSafe;

  sclass CouldntLoadCode {}

  transient Lock codeLoadLock = lock();

  volatile long requestsServed;
  transient double systemLoad;
  transient long processSize;
  transient Lock statsLock = lock();

  transient ReliableSingleThread rstPersistRarely = dm_rstWithPreDelay(this, 60.0, r change);
  
  set flag NoNanoHTTPD.
  !include #1029545 // API for Eleu

  class Bot {
    S name;
    
    *() {}
    *(S *name) {}
    *(S *name, IVF1<GazellePost> *handlePost) {}
    
    swappable void handlePost(GazellePost post) {}
    
    GazelleBotCred cred() { ret GazelleBotCred(_user, _botToken, name).server(gazelleServer); }
    
    void postReply(GazellePost post, S text, S type, S title default null) {
      if (empty(text) && empty(title))
        text = "<no output>";
      gazelle_createPost(cred(), text, type, refs := post.id, +title);
    }

    // post: post we are replying to
    void createPostFromBotResult(GazellePost post, IF0 f, IF1<S> modifyBotInfo default null) {
      S text, type = "Code Result", title = "", botInfo = "";
      bool overrideLastPost = false;
      try {
        O result = f!;
        if (result == null) ret;
        print("Result type: " + className(result));
        if (result cast CreatePost) {
          print("Is CreatePost");
          O[] params = toObjectArray(result.params);
          text = (S) optPar text(params);
          type = (S) optPar type(params, type);
          title = (S) optPar title(params);
          botInfo = (S) optPar botInfo(params);
          overrideLastPost = boolPar overrideLastPost(params);
        } else
          text = str_shortenSyntheticAndStandardToString(result);
      } catch print e {
        text = getStackTrace(e);
      }
      if (empty(text) && empty(title))
        text = "<no output>";
      botInfo = callFOrKeep(modifyBotInfo, botInfo);
      GazelleBotCred cred = cred();
      if (nempty(botInfo))
        cred.botInfo = botInfo;

      if (overrideLastPost) {
        // find last post
        S _botInfo = botInfo;
        GazellePost lastPost = firstThat(repliesTo(post), p -> eqic(p.botInfo, _botInfo));
        print("Last post with bot info " + botInfo + ": " + lastPost);
        if (lastPost != null) {
          gazelle_editPost(cred, lastPost.id, text, type, +title, refs := post.id);
          ret;
        }
      }
      
      gazelle_createPost(cred, text, type, +title, refs := post.id);
    }
  }

  srecord CreatePost(L params) {}
  
  start {
    dm_require("#1017856/SystemLoad");
    dm_vmBus_onMessage systemLoad(voidfunc(double load) {
      if (setField_noPersist(systemLoad := load))
        distributeDivChanges("serverLoadLeftHemi");
    });
    dm_vmBus_onMessage processSize(voidfunc(long processSize) {
      if (setField_noPersist(+processSize))
        distributeDivChanges("memLeftHemi");
    });
    
    // test if we can share classes with dynamically loaded code
    hotwire_addSharedClasses(Pair);

    dm_useLocalMechListCopies();
    dbIndexing(Conversation, "cookie");
    
    grabLoop.handlePosts = posts -> {
      dm_mediumRefreshTranspiler();
      
      grabLoop.handlePosts_base(posts);
      
      for (GazellePost post : posts)
        setField(botProcessed := max(botProcessed, post.modified));
    };
    
    // legacy conversion to sort allPosts
    setField_noCheck(allPosts := asSyncTreeMap(allPosts));

    bots.add(new Bot("Math Bot", post -> {
      if (post.creating) ret;
      gazelle_mathBot1_handlePost_2(_user, _botToken, post);
    }));
    
    bots.add(new Bot("Code Safety Checker") {
      void handlePost(GazellePost post) {
        if (post.creating) ret;
        if (post.isJavaXCode())
          gazelle_createPost(cred(), codeSafetyCheckResult(post.text), "Code Safety", refs := post.id);
      }
    });

    bots.add(new Bot("Python Code Safety Checker") {
      void handlePost(GazellePost post) {
        if (post.creating) ret;
        if (post.isPythonCode())
          gazelle_createPost(cred(), pythonCodeSafetyCheckResult(post.text), "Python Code Safety", refs := post.id);
      }
    });
    
    bots.add(new Bot("Safe Code Runner") {
      void handlePost(GazellePost post) {
        if (post.creating) ret;
        if (post.isJavaXCode()) {
          int runtime = min(maxRuntimeMinutes, parseFirstInt(jextractIC("runtime <int> minutes", post.type)));
          S code = post.text;
          if (isCodePostSafe(post)) {
            S _code = prepareCode(code, post);
            //S out = shorten(maxEvalResultLength, runCode(code));
            createPostFromBotResult(post, () -> evalCode(runtime*60.0, _code, post));
          }
        }
      }
    });
    
    bots.add(new Bot("Python Safe Code Runner") {
      void handlePost(GazellePost post) {
        if (post.creating) ret;
        if (!doPython) ret;
        if (post.isPythonCode()) {
          S code = post.text;
          if (isPythonCodeSafe(code)) {
            IF0 f = () -> {
              PythonInterpreter interpreter = jython();
              PyCode compiled = interpreter.compile(code);
              TraceFunction traceFunction = new {
                @Override
                public TraceFunction traceCall(PyFrame frame) {
                  ret null with ping();
                }
        
                @Override
                public TraceFunction traceReturn(PyFrame frame, PyObject ret) {
                  ret null with ping();
                }
        
                @Override
                public TraceFunction traceLine(PyFrame frame, int line) {
                  ret null with ping();
                }
        
                @Override
                public TraceFunction traceException(PyFrame frame, PyException exc) {
                  ret null with ping();
                }
              };
              Py.getThreadState().tracefunc = traceFunction;
              ret interpreter.eval(compiled);
            };
            
            createPostFromBotResult(post, () -> evalWithTimeoutOrFail(evalTimeout, f));
          }
        }
      }
    });
    
    bots.add(new Bot("Run Code On All Posts") {
      void handlePost(GazellePost post) {
        if (post.creating) ret;
        if (eqic(post.type, "Instruction") && match("Please run this code on all posts", post.text)) {
          long ref = gazelle_firstPostRef(post.id);
          if (ref == 0) ret;
          
          S code = gazelle_text(ref);
          if (!isCodeSafe(code)) ret;
          
          S code2 = "ret func(S post) { " + code + " };";
          O function = evalCode(code2);
          
          new LS lines;
          S out = shorten(maxEvalResultLength, runCode(code));
          for (GazellePost post2 : cloneValues(allPosts)) {
            lines.add("Post " + post2.id + " (" + quote(shorten(20, post2.text)) + "): " + shorten(80, runFunc(() -> callF(function, post2.text))));
          }

          gazelle_createPost(cred(), lines(lines), "Code Result", refs := post.id);
        }
      }
    });
    
    bots.add(new Bot("Mark identifiers safe") {
      void handlePost(GazellePost post) {
        if (post.creating) ret;
        if (/*eqic(post.type, "Instruction")
          &&*/ post.creator.isMaster
          && match("Mark safe", post.text)) {
          S text = getPost(first(post.postRefs)).text;
          LS ids = tok_identifiersInOrder(regexpFirstGroupIC("Unknown identifiers: (.+)", text));
          print("Marking safe: " + ids);
          postReply(post, markSafe(ids), "Marked safe");
        }
      }
    });
    
    bots.add(new Bot("Post Deleter") {
      void handlePost(GazellePost post) {
        if (post.creating) ret;
        if (/*eqic(post.type, "Instruction")
          &&*/ post.creator.isMaster
          && match("Delete posts", post.text)) {
            long ref = gazelle_firstPostRef(post.id);
            if (ref == 0) ret;
            S text = gazelle_text(ref);
            L<Long> postIDs = allToLong(regexpAllFirstGroups(gazelle_deletePostRegexp(), text));
            print("Deleting posts: " + postIDs);
            if (nempty(postIDs)) {
              Map result = gazelle_deletePosts(cred(), postIDs);
              postReply(post, str(result), "Deletion result");
            } else
              postReply(post, "No mentioned posts found", "Deletion result");
        }
      }
    });
    
    bots.add(new Bot("Detector Runner") {
      void handlePost(GazellePost post) {
        if (post.creating) ret;
        ret unless eqic(post.type, "Instruction")
          && match("Please run detector", post.text);
          
        try {
          long detectorID = post.refWithTagOrFail("detector");
          long posExamplesID = post.refWithTagOrFail("positive examples");
          long negExamplesID = post.refWithTagOrFail("negative examples");
        
          S code = getPost(detectorID).text;
          LS posExamples = tlft(getPost(posExamplesID).text);
          LS negExamples = tlft(getPost(negExamplesID).text);
          LPair<S, Bool> examples = trueFalseBPairs(posExamples, negExamples);
          if (!isCodeSafe(code)) fail("Detector code not safe");
  
          IF1<S, O> detector = proxy IF1(evalCode(code));
          new LS good;
          new LS bad;
          new Scorer scorer;
          long time = sysNow();
          evalWithTimeoutOrFail(evalTimeout, r {
            for (Pair<S, Bool> example : examples) {
              S result = runFunc(() -> detector.get(example.a));
              bool ok = eq(result, str(example.b));
              scorer.add(ok);
              S line = (ok ? "OK" : example.b ? "False negative" : "False positive")
                  + " (" + shorten(10, result) + "): " + example.a;
              (ok ? good : bad).add(line);
            }
          });            
          time = sysNow()-time;

          S text = "Detector code:\n\n" + indentx(code) + "\n\n"
            + n2(good, "correct answer") + ", " + n2(bad, "bad answer") + ". Runtime: " + n2(time) + " ms\n\n"
            + or2(trim(lines(concatLists(bad, ll(""), good))), "No errors");
          S title = "Score for detector " + detectorID + ": " + scorer;
          
          gazelle_createPost(cred(), text, "Detector Score", +title,
            refs := joinWithSpace(ll(post.id, detectorID, posExamplesID, negExamplesID)),
            refTags := linesLL_rtrim("", "detector", "positive examples", "negative examples"));
        } catch e {
          postReply(post, getStackTrace(e), "Error");
        }
      }
    });
    
    bots.add(new Bot("Auto Bot Runner") {
      void handlePost(GazellePost post) {
        if (!runAutoBots) ret;
        if (post.creating) ret;

        print("Auto Bot Runner: " + post.id);
        for (GazellePost botPost : values(allPosts)) pcall {
          continue unless eqicOneOf(botPost.type, "JavaX Code (Bot run on every post)", "JavaX Code (Live Auto Bot)")
            && botPost.creator.isMaster;

          print("Processing auto-bot " + botPost.id + " on " + post.id);
          
          O code = codeForPost(botPost);
          if (shortNameIs CouldntLoadCode(code))
            continue with print("Couldn't load code for auto-bot " + botPost.id);
          print("Code type: " + className(code));
          O botInstance = evalWithTimeoutOrFail(evalTimeout, () -> callFOrNewInstance(code));
          print("Bot instance type: " + className(botInstance));

          setOpt(botInstance, postID := post.id);
          setOpt(botInstance, post := post.text);
          setOpt(botInstance, postType := post.type);

          // prevent endless loop of auto-bots replying to themselves or each other
          // (unless they explicitly request this)
          if (post.isAutoBotMade()) {
            // not a bot by master user? don't allow override
            if (!botPost.isMasterMade()) continue;

            // check for override request
            if (!isTrue(evalWithTimeoutOrFail(evalTimeout,
              () -> call(botInstance, "runOnAutoBotPost")))) continue;
          }

          createPostFromBotResult(post, () -> evalWithTimeoutOrFail(evalTimeout,
            () -> call(botInstance, "calc")),
            botInfo -> joinNemptiesWithColon("Auto-Bot " + botPost.id, botInfo));
        }
      }
    });

    dm_doEvery(60.0, 3600.0, r removeDeletedPosts);
  }
  
  void handlePost(GazellePost post) {
    allPosts.put(post.id, post);
    change();
    //if (grabLoop.firstGrab) ret;
    loadedCodePosts.remove(post.id);
    if (post.modified > botProcessed) {
      print("modified post: " + post.id + " - " + post.modified + "/" + botProcessed);
      for (Bot bot : bots) pcall {
        bot.handlePost(post);
      }
    }
  }

  // if post != null, store transpilation
  O evalCode(S code, GazellePost post default null) {
    ret evalCode(evalTimeout, code, post);
  }
  
  O evalCode(double timeout, S code, GazellePost post default null) {
    //printWithIndent("CODE> ", code);
    veryQuickJava_transpiled.set(post != null ? "" : null); // request transpilation
    try {
      // TODO: don't makeDependent
      ret dm_javaEvalWithTimeout(timeout, code);
    } finally {
      if (post != null) {
        S java = veryQuickJava_transpiled!;
        print("Transpilation for " + post.id + ": " + shorten(java));
        saveTextFile(transpilationFile(post.id), nullOnEmpty(java));
      }
    }
  }

  // assumes code is safety-checked
  S runCode(S code) {
    printWithIndent("CODE> ", code);
    ret runFunc(() -> str_shortenSyntheticAndStandardToString(dm_javaEval(code)));
  }

  // run IF0 with timeout, exception to string, convert result to string
  S runFunc(IF0 f) {
    ret str_shortenSyntheticAndStandardToString(evalWithTimeoutOrException(evalTimeout, func {
      try {
        ret str_shortenSyntheticAndStandardToString(f!);
      } catch e {
        ret getStackTrace(e);
      }
    }));
  }
  
  L<GazellePost> repliesTo(GazellePost post) {
    ret filter(values(allPosts), p -> contains(p.postRefs, post.id));
  }
  
  L<GazellePost> repliesWithTag(GazellePost post, S tag) {
    Pair<Long, S> pair = pair(post.id, upper(tag));
    ret filter(repliesTo(post), p -> contains(mapPairsB toUpper(p.taggedRefs()), pair));
  }
  
  GazellePost getPost(long id) {
    ret allPosts.get(id);
  }
  
  S getPostText(long id) {
    ret getPost(id).text;
  }
  
  Cl<GazellePost> getAllPosts() {
    ret values(allPosts);
  }

  CreatePost createPost(O... _) {
    ret new CreatePost(asList(_));
  }
  
  CodeSafetyChecker codeSafetyChecker() {
    new CodeSafetyChecker checker;
    checker.init();
    checker.markSafe("getAllPosts");
    ret checker;
  }
  
  PythonCodeSafetyChecker pythonCodeSafetyChecker() {
    new PythonCodeSafetyChecker checker;
    checker.init();
    checker.markSafe("getAllPosts");
    ret checker;
  }
  
  S codeSafetyCheckResult(S code) {
    CodeSafetyChecker checker = codeSafetyChecker();
    checker.checkCode(code);
    ret checker.verbalCheckResult();
  }
  
  S pythonCodeSafetyCheckResult(S code) {
    PythonCodeSafetyChecker checker = pythonCodeSafetyChecker();
    checker.checkCode(code);
    ret checker.verbalCheckResult();
  }
  
  bool isCodePostSafe(GazellePost post) {
    ret considerMasterPostsSafe && post.creator.isMaster
      || isCodeSafe(post.text);
  }
  
  bool isCodeSafe(S code) {
    CodeSafetyChecker checker = codeSafetyChecker();
    checker.checkCode(code);
    ret checker.isSafe();
  }

  bool isPythonCodeSafe(S code) {
    PythonCodeSafetyChecker checker = pythonCodeSafetyChecker();
    checker.checkCode(code);
    ret checker.isSafe();
  }

  S prepareCode(S code, GazellePost post) {
    if (tok_isStaticLevelCode(code)) ret code;
    
    code = tok_addReturn(code);

    CodeInRewriting cir = new(javaTok(code));
    augmentCode(cir, post);
    ret cir!;
  }
  
  // define implicit vars and functions
  void augmentCode(CodeInRewriting cir, GazellePost post) {
    // post = text of parent post
    if (cir.contains("post")) {
      long ref = gazelle_firstPostRef(post.id);
      if (ref != 0)
        cir.add("S post = " + quote(gazelle_text(ref)) + ";");
    }
    
    // post = text of grandparent post
    if (cir.contains("post2")) {
      long ref = gazelle_firstPostRef(gazelle_firstPostRef(post.id));
      if (ref != 0)
        cir.add("S post2 = " + quote(gazelle_text(ref)) + ";");
    }
    
    // postType = typeof parent post
    if (cir.contains("postType")) {
      GazellePost post2 = getPost(gazelle_firstPostRef(post.id));
      if (post2 != null)
        cir.add("S postType = " + quote(post2.type) + ";");
    }
    
    if (cir.contains("getAllPosts"))
      cir.add([[
        embedded Cl<GazellePost> getAllPosts() {
          ret lazyMap_bitSet quickImport(asList((Cl) dm_call(dm_current_generic(), "getAllPosts")));
        }
      ]]);

    if (cir.contains("createPost"))
      cir.add([[
        embedded O createPost(O... _) {
          ret dm_call(dm_current_generic(), "createPost", _);
        }
      ]]);

    // optimize gazelle_text
    if (cir.contains("gazelle_text"))
      cir.add([[
        embedded S gazelle_text(long id) {
          ret (S) dm_call(dm_current_generic(), "getPostText", id);
        }
      ]]);
  }
  
  class CodeInRewriting {
    LS tok;
    new LS additions;
    
    *() {}
    *(LS *tok) {}
    
    bool contains(S token) {
      ret containsToken(tok, token);
    }
    
    S get() {
      ret lines(additions) + join(tok);
    }
    
    void add(S code) {
      addIfNempty(additions, code);
    }
  }

  O html(IWebRequest req) {
    {
      lock statsLock;
      requestsServed++;
      rstPersistRarely.trigger();
      requestServed();
    }

    new Matches m;
    
    if (eqic(req.uri(), "/favicon.ico"))
      ret serveFile(loadLibrary(#1400439), faviconMimeType());

    if (startsWith(req.uri(), "/htmlBot/", m) && isInteger(m.rest())) {
      req.noSpam();
      long postID = parseLong(m.rest());
      GazellePost post = getPost(postID);
      if (post == null) ret serve404("Post " + postID + " not found");
      O code = codeForPost(post);
      IF0 calc;

      // Case 1: Static page (code just returns a string)
      if (code instanceof S)
        calc = () -> code;
        
      // Case 2: Code is an argumentless function
      else if (implementsInterfaceShortNamed IF0(code))
        calc = toIF0(code);

      else {
        // Case 3: Code is IF1<IWebRequest, ?>
        // Sadly, for a lambda like (IF1<IWebRequest, S>) req -> ..., we can't find the
        // IWebRequest type by reflection. So we find the IWebRequest interface by name.
        Class reqType = getClassInRealm("main$IWebRequest", code);
        //print(+reqType);
        if (reqType == null) fail("IWebRequest not found in bot");
        O wrappedReq = proxy(reqType, req);
        calc = () -> callF(code, wrappedReq);
      }
      
      O result = evalWithTimeoutOrFail(evalTimeout, () -> calc!);
      ret hcomment("Made by HTML bot " + postID) + "\n" + str(result);
    }
    
    if (startsWith(req.uri(), "/chatBotReply/", m) && isInteger(m.rest())) {
      req.noSpam();
      long postID = parseLong(m.rest());
      GazellePost post = getPost(postID);
      if (post == null) ret serve404("Post " + postID + " not found");
      O code = codeForPost(post);
      if (shortNameIs CouldntLoadCode(code)) ret withHeader(serve500("Couldn't load code for post"));
      S q = req.params().get("q"); // user input
      bool initial = eq(req.params().get("initial"), "1");
      S cookie = req.params().get("cookie");
      if (!initial && empty(q)) ret withHeader(serveText(""));

      S answer = "";
      if (implementsInterfaceShortNamed IF1(code)) { // stateless bot
        if (!initial)
          answer = evalWithTimeoutOrFail(evalTimeout, () -> strOrEmpty(callF(code, q)));
      } else {
        print("Stateful bot. cookie: " + cookie);
        if (empty(cookie)) ret withHeader(serve500("Need cookie for stateful bot"));
        Conversation conv = uniq(Conversation, +cookie);
        O instance = code;
        if (instance == null) ret withHeader(serve500("No bot instance"));
        print("Stateful bot instance: " + instance);
        if (nempty(conv.dataStruct)) {
          O data = unstructureInRealm(conv.dataStruct, instance); // hopefully this is safe
          print("Unstructured: " + data);
          copyAllThisDollarFields(instance, data); // probably not needed anymore
          instance = data;
        }
        O _instance = instance;
        answer = evalWithTimeoutOrFail(evalTimeout, () -> strOrEmpty(
          initial ? callOpt(_instance, "initialMessage") : call(_instance, "answer", q)));
        cset(conv, dataStruct := structure(instance));
        print("Structured: " + conv.dataStruct);
      }

      if (initial && empty(answer))
        answer = "Bot " + post.id + " ready";

      ret withHeader(serveJSON(litorderedmap(answer := shorten(maxBotAnswerLength, answer))));
    }
    
    if (startsWith(req.uri(), "/transpilation/", m) && isInteger(m.rest())) {
      long postID = parseLong(m.rest());
      GazellePost post = getPost(postID);
      if (post == null) ret serve404("Post " + postID + " not found");
      S src = loadTextFile(transpilationFile(post.id));
      if (empty(src))
        src = "No transpilation found for post " + postID + "." + 
          (!post.isJavaXCode() ? " Code is not a code post." : " Please try \"Touch post\" and wait a few seconds");
      else pcall {
        src = javaPrettyPrint(src);
      }
      ret serveText(src);
    }

    if (eq(req.uri(), "/")) {
      ret "Loaded code posts:"
        + ul(keys(loadedCodePosts));
    }
    
    ret serve404();
  }

  O codeForPost(GazellePost post) {
    lock codeLoadLock;
    O code = loadedCodePosts.get(post.id);
    if (code == null) {
      try {
        S codeText = post.text;
        if (!isCodePostSafe(post)) fail("Code is not safe: " + codeSafetyCheckResult(codeText));
        codeText = prepareCode(codeText, null);
        code = evalCode(codeText, post);
      } catch print e {
        code = new CouldntLoadCode;
      }
      loadedCodePosts.put(post.id, code);
    }
    dm_pointSubmoduleToMe(mainClass(code));
    ret code;
  }

  File transpilationFile(long postID) {
    ret programFile("Post-Transpilations/" + postID + ".java");
  }

  O withHeader(O response) {
    call(response, 'addHeader, "Access-Control-Allow-Origin", "*");
    ret response;
  }

  O _getReloadData() {
    ret loadedCodePosts;
  }
  
  void _setReloadData(Map<Long, O> data) {
    if (data != null)
      loadedCodePosts = data;
  }

  void forgetLoadedCodePosts { clear(loadedCodePosts); }

  void enhanceFrame(Container f) {
    super.enhanceFrame(f);
    internalFramePopupMenuItems(f,
      "Forget loaded code", rEnter forgetLoadedCodePosts,
      "Remove deleted posts", rThreadEnter removeDeletedPosts);
  }

  void removeDeletedPosts {
    Cl<Long> posts = asSet(grabLoop.allPostIDs());
    print("Keeping " + nPosts(posts));
    if (syncRemoveAllExcept(allPosts, posts)) change();
    print("New count: " + nPosts(allPosts));
    removeAllExcept(loadedCodePosts, posts);
  }

  // web sockets

  class WebSocketInfo {
    S uri;
    SS params;

    new Set<Pair<S, O>> liveDivs; // id/content info
  }
  
  transient Map<virtual WebSocket, WebSocketInfo> webSockets = syncWeakHashMap();

  void cleanMeUp_webSockets {
    closeAllKeysAndClear((Map) webSockets);
  }

  void handleWebSocket(virtual WebSocket ws) {
    set(ws, onClose := r { webSockets.remove(ws) });
    set(ws, onOpen := rEnter {  
      S uri = cast rcall getUri(ws);
      SS params = cast rcall getParms(ws);
      print("WebSocket opened! uri: " + uri + ", params: " + params);
      new WebSocketInfo info;
      info.uri = uri;
      info.params = params;
      webSockets.put(ws, info);
    });

    setFieldToIVF1Proxy(ws, onMessage := msg -> { temp enter(); pcall {
      WebSocketInfo info = webSockets.get(ws);
      if (info == null) ret;
      S data = rcall_string getTextPayload(msg);
      Map map = jsonDecodeMap(data);
      O div = map.get("liveDiv");
      if (div cast S) {
        S contentDesc = div;
        syncAdd(info.liveDivs, pair(div, (O) contentDesc));
        reloadDiv(ws, div, calcDivContent(contentDesc));
      }
    }});
  }
  
  transient ReliableSingleThread_Multi<S> rstDistributeDivChanges = new(1000, lambda1 distributeDivChanges_impl);

  void distributeDivChanges(S contentDesc) {
    rstDistributeDivChanges.add(contentDesc);
  }

  void distributeDivChanges_impl(S contentDesc) enter {
    //print("distributeDivChanges_impl " + contentDesc);
    S content = null;
    for (Pair<virtual WebSocket, WebSocketInfo> p : syncMapToPairs(webSockets)) {
      for (S div : asForPairsWithB(p.b.liveDivs, contentDesc)) {
        if (content == null) content = calcDivContent(contentDesc);
        if (content == null) { print("No content for " + contentDesc); ret; }
        reloadDiv(p.a, div, content);
      }
    }
  }

  void reloadDiv(virtual WebSocket ws, S div, S content) {
    print("Reloading div " + div + " through WebSocket");
    S jsCode = replaceDollarVars(
      [[ $("#" + $div).html($content);]],
      div := jsQuote(div), content := jsQuote(content));
    dm_call(ws, "send", jsonEncode(litmap(eval := jsCode)));
  }

  S calcDivContent(S contentDesc) {
    if (eq(contentDesc, "webRequestsLeftHemi"))
      ret n2(requestsServed);
      
    if (eq(contentDesc, "serverLoadLeftHemi"))
      ret formatDoubleX(systemLoad, 1);

    if (eq(contentDesc, "memLeftHemi"))
      ret str_toM(processSize);

    null;
  }
  
  void requestServed {
    distributeDivChanges("webRequestsLeftHemi");
  }
  
  void setFirstGrab {
    grabLoop.firstGrab = true;
    change();
  }
} // end of module

Author comment

Began life as a copy of #1029997

download  show line numbers  debug dex  old transpilations   

Travelled to 4 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, vouqrxazstgt

No comments. add comment

Snippet ID: #1030627
Snippet name: DynGazelleMultiBot
Eternal ID of this version: #1030627/22
Text MD5: 267beffe9a303e1f7704f10549c1b0e5
Transpilation MD5: b88ba97144c14a2cb0c7a0d1a7cdc9cc
Author: stefan
Category: javax / gazelle.rocks
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2021-06-28 22:16:01
Source code size: 27408 bytes / 802 lines
Pitched / IR pitched: No / No
Views / Downloads: 330 / 651
Version history: 21 change(s)
Referenced in: #1034167 - Standard Classes + Interfaces (LIVE, continuation of #1003674)