fixthissite Posted June 5, 2015 Share Posted June 5, 2015 (edited) DOWNLOAD (since JARs are not allowed to be used in scripts, the .rar contains the source files, which you can include in your project) UPDATED: The two systems have been merged. Let me know if there are any issues. Two people came to me asking how to implement certain functionality: 1. To link certain nodes to other nodes. Since some nodes won't come after other nodes (Bank will only come after WalkToBank), there's no reason to loop through EVERY node in the script depending on the current node. How could one implement a system that only check to see which node shohld be next out of the possible nodes, not all the nodes. 2. To allow other nodes to execute while the current node is executing. When the current node is WalkToBank and your player has low hp, the script will not eat, since the current node is WalkToBank and not eat. You could add a priority system, but the problem is not "performing one node before the other"; it's performing one node while another node is processing. There exists an asynchronous node executor, which executes a node on a background thread. But this does not account for possible memory incosnistencies by implementing memory barriers. Not to mention, such a burden isn't actually needed; a single-threaded system could give the same functionality, without the need for atomicy and synchronization, as long as the script is running at a specific frame rate (a guide on that coming soon; pm me if you aren't sure what that means). I have packaged both together. I haven't tested it with an actual script (performed some lazy tests), so let me know if there are any problems with it. NodeLinkerManager The idea is to take a group of nodes, and "map" them to a specific node. For example, the WalkToBank would only be followed to the Bank node, and in a pk script, a WalkToWild node will only be followed by FightPlayer and LookForPlayer. We want to link nodes to their possible outcomes. Usually, a node based script looks like this: class MyScript extends Script { private List<Node> nodes; public void onStart() { nodes = new ArrayList<>(); nodes.add(new WalkToBank(this)); // add other nodes } public int onLoop() { for(Node node : nodes) if(node.canProcess()) node.process(); } } abstract class Node { private MethodProvider api; protected Node(MethodProvider provider) { this.api = api; } protected MethodProvider api() { return api; } public abstract void process(); public abstract boolean canProcess(); } final class WalkToBank extends Node { // ... } The problem with this is, depending on the amount of nodes (which increase as decomposition is applied), the for loop in the onLoop method could take longer than it needs. This is because it could be checking nodes that could not possibly come after the current node. Instead, we want to check only the nodes that could possibly come after the current node. For this, you must specify which nodes can come after which nodes. This is done through the @Linked annotation: import fts.node.Node; @Linked(nodes = { Bank.class }) final class WalkToBank extends Node { // ...same as usual } The other difference is how you add nodes. First, you need to declare the NodeLinkerManager in your script. You then add the type of the node you want. Any linked nodes that are specified through the @Linked annotation are instantiated, as well as the node you specified. To ensure no excess objects are created, NodeLinkerManager handles ALL instantiation; you simply specify the type of a node through a class literal: final class MyScript extends Script { private NodeLinkerManager manager; public void onStart() { manager = new NodeLinkerManager(this); manager.add(WalkToBank.class); } public int onLoop() { manager.process(); return 2; } } NodeFragmentManager There are some actions that should be performed while other actions are performing. For example, if you are fighting, you might wanna check if you should eat or pot. You could hard-code the logic into the Fight node, but what if there are actions that apply to all nodes? The node fragment system sees those actions as "node fragments". A NodeFragment is the exact same thing as a Node, other than the ability to specify a NodeFragment when creating a node. You would specify the NodeFragment through the @Fragmented annotation: @Fragmented(fragments = { Eat.class; }) final class WalkToBank extends Node { // ... } final class Eat extends NodeFragment { // ...same as a regular Node } When the current node is WalkToBank, Eat will also process. This will allow the bot to eat while it's walking to the bank (if needed). Edited September 11, 2015 by fixthissite 7 Quote Link to comment Share on other sites More sharing options...
Novak Posted June 5, 2015 Share Posted June 5, 2015 Node is a horrible name for framework (even though I use it too). Task is a much more suitable term. Also, i find child and parent tasks very redundant and irrelevant with rs scripting. Most scripts will never be complicated enough to need parent/child nodes. Storing all the tasks in a priority queue then using a comparator to determine which should execute first is the best way to do it IMO 1 Quote Link to comment Share on other sites More sharing options...
fixthissite Posted June 5, 2015 Author Share Posted June 5, 2015 Node is a horrible name for framework (even though I use it too). Task is a much more suitable term. Also, i find child and parent tasks very redundant and irrelevant with rs scripting. Most scripts will never be complicated enough to need parent/child nodes. Storing all the tasks in a priority queue then using a comparator to determine which should execute first is the best way to do it IMO Your proposal requires more processing than this. Using these systems, all the heavy work is done before onLoop starts, and the heavy work doesn't take long at all. As for naming, it's nice to know you have preferences. We all do. I'm going based off what I've seen to be popular, not what each person likes best. It was requested, so it's definitely not irrelevant. Some scripts are more advanced than others, which require increased decomposition to maintain. Not to mention, the lack of effort needed by the client as well as the type safety. Maybe you should post your test system and let the developers decide which is easiest to use. Quote Link to comment Share on other sites More sharing options...
Novak Posted June 5, 2015 Share Posted June 5, 2015 (edited) here we go with you being defensive again just like on the other site, we proved you wrong when you thought null is bad. keep taking constructive criticism to heart, I'm sure you will get somewhere also your argument over cpu usage is negligible Edited June 5, 2015 by Novak Quote Link to comment Share on other sites More sharing options...
fixthissite Posted June 5, 2015 Author Share Posted June 5, 2015 (edited) here we go with you being defensive again just like on the other site, we proved you wrong when you thought null is bad. keep taking constructive criticism to heart, I'm sure you will get somewhere I was just stating facts. If you stated facts, maybe we would get to a conclusion. So what if I'm defensive? If I feel I'm correct, what's wrong with defending myself? If you aren't giving me any reasons as to why this code is inefficient, how are others supposed to be aware? By the way, mind saying who you are on the other site? And just for kicks, check out The Billion Dollar Mistake by Tony Hoare, where the inventor of the null pointer said himself it was a mistake. Edited June 5, 2015 by fixthissite Quote Link to comment Share on other sites More sharing options...
Novak Posted June 5, 2015 Share Posted June 5, 2015 You are overcomplicating the framework. Anybody who would actually use this has their own system in place that they have written. The CPU usage you are talking about saving is negligible. Its like arguing if statements vs a switch. Sure, it may be more CPU efficient, but the amount is so small that it is not relevant. Since you are linking things, i guess node can be a correct term. A standard "Node" framework like you outline in the OP is a bad name because of what node actually means. By no means is it bad, it just is overcomplicating something that should be rather simple Quote Link to comment Share on other sites More sharing options...
fixthissite Posted June 5, 2015 Author Share Posted June 5, 2015 (edited) You are overcomplicating the framework. Anybody who would actually use this has their own system in place that they have written. The CPU usage you are talking about saving is negligible. Its like arguing if statements vs a switch. Sure, it may be more CPU efficient, but the amount is so small that it is not relevant. Since you are linking things, i guess node can be a correct term. A standard "Node" framework like you outline in the OP is a bad name because of what node actually means. By no means is it bad, it just is overcomplicating something that should be rather simple How is a simple annotation "over-complicating things"? I'm sorry if you don't understand the language enough to use an annotation (which they really aren't that hard; I even gave an example), but the verbosity is not a problem for the functionality it gives. Even beginners can easily use this kind of syntax, and seeing how it's aimed at people who use the node framework, I don't see a problem. Please, mind telling me who you are on the other site, so I know who I'm talking to from there? Edited June 5, 2015 by fixthissite Quote Link to comment Share on other sites More sharing options...
The_Red_Flag Posted June 5, 2015 Share Posted June 5, 2015 I'd like to say thanks for the interesting post. As someone with no preferences and a beginner coder, maybe I'll remember this down the line 1 Quote Link to comment Share on other sites More sharing options...
Bobrocket Posted June 6, 2015 Share Posted June 6, 2015 This is very interesting. In all of my years of programming, I've never come across stuff like this. Quote Link to comment Share on other sites More sharing options...
fixthissite Posted June 6, 2015 Author Share Posted June 6, 2015 This is very interesting. In all of my years of programming, I've never come across stuff like this.Metaprogramming is uncommon around these parts, but is use substantially in frameworks such as Spring and Hibernate. It's been becoming more popular with the release of Java 8, which allows you to annotate just about anything.I used it in this situation to reduce the amount of typing needed by the client (the person using this code). I wanted you guys to easily wire nodes to other nodes when you create the node class, without altering the current node design (to avoid confusion). I also gave the job of creating the actual node objects to the node managers, so you won't have to worry about accidentally creating an excess object, or forgetting to add a node that's needed by another node. As for the crazy code, you'll see a lot of Class<? extends Node>, which is probably what's throwing you off. Every class in your program has a class object (can be received with the instance method getClass() or a class literal: ClassName.class). As you're using this code, you'll notice yourself using quite a few class literals; that's what the Class<? extends Node> type is for: Class<? extends Node> clazz = BankNode.class; But you don't need to be reading that stuff anyways, unless you're really interested in the code behind all this. Quote Link to comment Share on other sites More sharing options...
FrostBug Posted June 9, 2015 Share Posted June 9, 2015 (edited) Hmm, I personally prefer FSM styled frameworks (state transitions) in comparison to these node frameworks which require evaluation of state in every loop cycle. But definitely an interesting approach to improving their potential Are you working on any scripts? I am quite interested in seeing what you could produce Edited June 9, 2015 by FrostBug 2 Quote Link to comment Share on other sites More sharing options...
fixthissite Posted June 9, 2015 Author Share Posted June 9, 2015 (edited) Hmm, I personally prefer FSM styled frameworks (state transitions) in comparison to these node frameworks which require evaluation of state in every loop cycle. But definitely an interesting approach to improving their potential Are you working on any scripts? I am quite interested in seeing what you could produce Trust me, I'm a huge fan of finite state automata (I actually suggested a FSM addition to the API, which got rejected.. Don't be fooled by the lack of foundation for the states in the post, was just pitching an idea).If implemented right, a FSM can save you quite a bit of processing. In the Node framework, if you had a WalkToWild node, it's conditions would be "hasFood && hasGear && !inWild". These conditions would make sense when transitioning from Start to WalkToWild, but doesn't make as much sense when transitioning from Bank to WalkToWild. After banking, we can be sure that we have food and gear (if not, better add a WalkToStore node or have it transition to EXIT, which I don't think any node frameworks account for exiting). So checking if we have food and gave gear is not needed after banking. In a FSM based script, we could specify the EXACT logic for transitioning between states. The problem is, we need to write logic for EVERY possible transition. This doesn't scale as easily as encapsulating all the rules for a node inside a node. So now it's about "Do I wanna save CPU time by removing these excess checks, at the price of increased verbosity?". That's where it comes down to opinion. I found this video (https://youtu.be/wsmMOJj6ETo), which explains different AI techniques (for gaming purposes); a pretty interesting watch (and introdroduction) for those getting into AI. Constantly checking the current state allows for more percise interpretation. For example, lets say WalkToBank was processing, but your bot came across an expensive item on the ground while walking there. Unless your WalkToBank state manually checks for items between each step, it's not gonna notice it. In a node framework, you could have a Loot node, which gets triggered when a good item is near you on the ground. Since the states could change at any second in a node framework, it allows you to account for more actions without needing to manually implement that logic for each state. I was all for FSM scripts when I got here. Now, due to the environment and goals, I feel the node framework was a very nice contribution to the community, and whoever proposed it first should get a cookie. Is it the most efficient? Nah. But it most definitely has it's benefits. As for the excess processing that arises from it, unless you're planning on implementing some intense system, it's nothing anyone should worry about. Sorry for the long post. Could talk about automata all day :P I was planning on working on a script with 2 other devs here, based on the goal-oriented framework I was going to release to the public (but decided not to due to it's complexity; didn't want it dying out before people saw it's potential), but came across quite a few roadblocks (rules with timing, pricing, script complexity, ect..). I've heard there's a lot more money doing something a bit more advanced, somewhat similar, but in a free market, so I'm looking at my options. I feel scripting isn't where I belong anyways; doesn't take advantage of the variety of technologies I've familiarized myself with /: Edited June 9, 2015 by fixthissite 1 Quote Link to comment Share on other sites More sharing options...
Psvxe Posted June 30, 2015 Share Posted June 30, 2015 Alright, I got the following NPE but I can't figure out what causes the script to throw it. Console output: [ERROR][Bot #1][06/30 01:00:40 PM]: Error in script executor! java.lang.NullPointerException at fts.linked.NodeLinkerManager.run(NodeLinkerManager.java:49) at nl.psvxe.scripts.fightcave.Manager.onLoop(Manager.java:36) at org.osbot.rs07.event.ScriptExecutor$InternalExecutor.run(qh:114) at java.lang.Thread.run(Thread.java:745) NodeLinkerManager class: public void run() { for(Node node : currentNodeSet) { //<------------- Error line (49) if(node.validate()) { currentNodeSet = nodeSet.get(node.getClass()); node.execute(); } } }Manager class: @SuppressWarnings("unchecked") @Override public void onStart(){ manager = new NodeLinkerManager(this); try { manager.add(AttackHandler.class); manager.add(EatHandler.class); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { e.printStackTrace(); } } @Override public int onLoop() { manager.run(); //<------------ Error line (36) return random(100,200); }What could cause this? I'm out of ideas. Quote Link to comment Share on other sites More sharing options...
Novak Posted June 30, 2015 Share Posted June 30, 2015 Alright, I got the following NPE but I can't figure out what causes the script to throw it. Console output: [ERROR][Bot #1][06/30 01:00:40 PM]: Error in script executor! java.lang.NullPointerException at fts.linked.NodeLinkerManager.run(NodeLinkerManager.java:49) at nl.psvxe.scripts.fightcave.Manager.onLoop(Manager.java:36) at org.osbot.rs07.event.ScriptExecutor$InternalExecutor.run(qh:114) at java.lang.Thread.run(Thread.java:745) NodeLinkerManager class: public void run() { for(Node node : currentNodeSet) { //<------------- Error line (49) if(node.validate()) { currentNodeSet = nodeSet.get(node.getClass()); node.execute(); } } }Manager class: @SuppressWarnings("unchecked") @Override public void onStart(){ manager = new NodeLinkerManager(this); try { manager.add(AttackHandler.class); manager.add(EatHandler.class); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { e.printStackTrace(); } } @Override public int onLoop() { manager.run(); //<------------ Error line (36) return random(100,200); }What could cause this? I'm out of ideas. if its on the line with the for loop, then currentNodeSet is null Quote Link to comment Share on other sites More sharing options...
fixthissite Posted June 30, 2015 Author Share Posted June 30, 2015 (edited) Seems I forgot to set the initial "currentNodeSet". I uploaded this without testing, which seems to always be a problem. I'll make sure to test anything I release in the future To fix this, you're going to need to find a set that best fits your bot when it starts. You can do this in a few different ways. I recommend setting it in onStart, right before continuing to onLoop: void onStart() { manager.add(...); manager.add(...); manager.initCurrentSet(); } Within the Manager class, you would add public void initCurrentSet() { for(Node node : allNodes.values()) { if(node.canProcess()) { currentNodeSet = nodeSets.get(node); break; } } } I'll be fixing this up shortly after my current project, which will make it easier for people to apply it to their scripts. Sorry for the inconvienence (wrote these systems from my phone ) Edited June 30, 2015 by fixthissite 2 Quote Link to comment Share on other sites More sharing options...