Skip to content

Commit

Permalink
Merge pull request #1 from feagi/Stat_call
Browse files Browse the repository at this point in the history
Stat call
  • Loading branch information
Amir-Rasteg authored May 7, 2024
2 parents b4006b2 + 3e93d9e commit 4d9d945
Show file tree
Hide file tree
Showing 16 changed files with 173 additions and 114 deletions.

This file was deleted.

73 changes: 0 additions & 73 deletions .godot/global_script_class_cache.cfg

This file was deleted.

1 change: 1 addition & 0 deletions FEAGI_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"FEAGI_domain":"127.0.0.1","enabled":true,"encryption":"NO_ENCRYPTION","feagi_output_mappings":[{"OPU_mapping_to":"MOTOR","godot_action":"move_left","neuron_X_index":0,"optional_signal_name":"","pass_FEAGI_weight_instead_of_max":false,"seconds_to_hold":0.1,"threshold":0.01},{"OPU_mapping_to":"MOTOR","godot_action":"move_right","neuron_X_index":1,"optional_signal_name":"","pass_FEAGI_weight_instead_of_max":false,"seconds_to_hold":0.1,"threshold":0.01}],"http_port":"8000","metrics":[],"output":"AUTOMATIC_SEND_WHOLE_VIEWPORT","websocket_port":"9055"}
32 changes: 32 additions & 0 deletions addons/FeagiIntegration/Feagi-Interface/FEAGIActionHolder.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

"""
Copyright 2016-2024 The FEAGI Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================
"""
extends Timer
class_name FEAGIActionHolder
## Holds down an action from a FEAGI press for the duration specified

var action_mapping: FEAGIActionMap

func setup_from_action(action_mapping_: FEAGIActionMap) -> void:
action_mapping = action_mapping_
timeout.connect(_FEAGI_released)

func FEAGI_pressed(strength: float) -> void:
stop() # reset the timer to the hold time
start(action_mapping.seconds_to_hold)
action_mapping.action(strength)

func _FEAGI_released() -> void:
action_mapping.action(0.0)

21 changes: 21 additions & 0 deletions addons/FeagiIntegration/Feagi-Interface/FEAGIHTTP.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
extends HTTPRequest
class_name FEAGIHTTP
##Allows for sending of HTTP requests to FEAGI

signal FEAGI_call_complete(response_code: int, data: PackedByteArray)

func _ready() -> void:
request_completed.connect(_on_call_complete)

func send_POST_request(base_URL: StringName, header: PackedStringArray, URL_path: StringName, json: StringName) -> void:
request(base_URL + URL_path, header, HTTPClient.METHOD_POST, json)

func send_PUT_request(base_URL: StringName, header: PackedStringArray, URL_path: StringName, json: StringName) -> void:
request(base_URL + URL_path, header, HTTPClient.METHOD_PUT, json)

func send_DELETE_request(base_URL: StringName, header: PackedStringArray, URL_path: StringName, json: StringName) -> void:
request(base_URL + URL_path, header, HTTPClient.METHOD_DELETE, json)

func _on_call_complete(_result: HTTPRequest.Result, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
FEAGI_call_complete.emit(response_code, body)
queue_free()
6 changes: 6 additions & 0 deletions addons/FeagiIntegration/Feagi-Interface/FEAGIHTTP.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bleh55any0dbr"]

[ext_resource type="Script" path="res://addons/FeagiIntegration/Feagi-Interface/FEAGIHTTP.gd" id="1_ju7qc"]

[node name="Feagihttp" type="HTTPRequest"]
script = ExtResource("1_ju7qc")
87 changes: 71 additions & 16 deletions addons/FeagiIntegration/Feagi-Interface/FEAGIInterface.gd
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ limitations under the License.
==============================================================================
"""

#region definitions and vars
extends Node
class_name FEAGIInterface
## Autoladed interface to access FEAGI from the game

const CONFIG_PATH: StringName = "res://addons/FeagiIntegration/config.json"
const CONFIG_PATH: StringName = "res://FEAGI_config.json"
const FEAGIHTTP_PREFAB: PackedScene = preload("res://addons/FeagiIntegration/Feagi-Interface/FEAGIHTTP.tscn")
const METRIC_PATH: StringName = "/v1/training/game_stats"
const DELETE_METRIC_PATH: StringName = "/v1/training/reset_game_stats"

enum FEAGI_AUTOMATIC_SEND {
NO_AUTOMATIC_SENDING,
Expand Down Expand Up @@ -68,12 +72,19 @@ const METRIC_MAPPINGS: Dictionary = {
}

signal socket_retrieved_data(data: Signal) ## FEAGI Websocket retrieved data, useful for custom integrations
signal feagi_connection_established() ## FEAGI connection has been established!
signal feagi_connection_lost() ## The websocket connection to Feagi has been lost!

#endregion

#region start

var _is_socket_ready: bool = false
var _automated_sending_mode: FEAGI_AUTOMATIC_SEND = FEAGI_AUTOMATIC_SEND.NO_AUTOMATIC_SENDING
var _socket: FEAGISocket
var _network_bootstrap: FEAGINetworkBootStrap
var _feagi_motor_mappings: Dictionary # mapped by opu + str(neuron_ID) -> [FEAGIActionMap]
#var _feagi_motor_mappings: Dictionary # mapped by opu + str(neuron_ID) -> [FEAGIActionMap]
var _feagi_action_holders: Dictionary # key: opu string + str(neuron_ID) -> value: relevant istance of [FEAGIActionHolder]
var _feagi_required_metrics: Dictionary # required metric str key -> EXPECTED_TYPE
var _viewport_ref: Viewport

Expand All @@ -91,13 +102,13 @@ func _init():

func _ready() -> void:
if !FileAccess.file_exists(CONFIG_PATH):
push_error("FEAGI: No Config located, not starting FEAGI integration!")
push_error("FEAGI: No Config located at '%s', not starting FEAGI integration!" % CONFIG_PATH)
return

var file_json: String = FileAccess.get_file_as_string(CONFIG_PATH)
var json_output = JSON.parse_string(file_json) # Dict or null
if json_output == null:
push_error("FEAGI: Unable to read config file for FEAGI, not starting FEAGI integration!")
push_error("FEAGI: Unable to read config file for FEAGI at '%s', not starting FEAGI integration!" % CONFIG_PATH)
return
var config_dict: Dictionary = json_output as Dictionary

Expand All @@ -116,7 +127,11 @@ func _ready() -> void:
push_warning("FEAGI: Unable to read motor mapping information from configuration!")
continue
var map: FEAGIActionMap = FEAGIActionMap.create_from_valid_dict(raw_mapping)
_feagi_motor_mappings[map.OPU_mapping_to.to_lower() + str(map.neuron_X_index)] = map
var feagi_action: FEAGIActionHolder = FEAGIActionHolder.new()
add_child(feagi_action)
feagi_action.name = map.OPU_mapping_to.to_lower() + str(map.neuron_X_index)
_feagi_action_holders[feagi_action.name] = feagi_action
feagi_action.setup_from_action(map)

# check and set automatic sending config,
var raw_send_config: String = config_dict["output"]
Expand Down Expand Up @@ -182,6 +197,34 @@ static func validate_settings_dictionary(checking_config: Dictionary) -> Diction
return checking_config


#endregion

#region for_user_use

## Send metrics using the keys specified in the 'Fitness Metrics' to FEAGI
func send_metrics_to_FEAGI(stats: Dictionary) -> void:
if !_is_socket_ready:
push_warning("FEAGI: Cannot interact with FEAGI when the interface is disabled!")
for input_key in stats.keys():
if input_key not in METRIC_MAPPINGS.keys():
push_error("FEAGI: Invalid key %s in input stats dict! Not sending!" % input_key)
return
if _get_value_type(stats[input_key][1]) != typeof(stats[input_key]):
push_error("FEAGI: Key %s is of invalid type!!" % input_key)
return

var http_send: FEAGIHTTP = FEAGIHTTP_PREFAB.instantiate()
add_child(http_send)
http_send.send_PUT_request(_network_bootstrap.feagi_root_web_address, _network_bootstrap.DEF_HEADERSTOUSE, METRIC_PATH, JSON.stringify(stats))

## Tell FEAGI to delete ALL of its metrics
func delete_metrics_from_FEAGI() -> void:
if !_is_socket_ready:
push_warning("FEAGI: Cannot interact with FEAGI when the interface is disabled!")
var http_send: FEAGIHTTP = FEAGIHTTP_PREFAB.instantiate()
add_child(http_send)
http_send.send_DELETE_request(_network_bootstrap.feagi_root_web_address, _network_bootstrap.DEF_HEADERSTOUSE, DELETE_METRIC_PATH, JSON.stringify({}))

## Send text data to FEAGI
func send_to_FEAGI_text(data: String) -> void:
if !_is_socket_ready:
Expand All @@ -194,6 +237,8 @@ func send_to_FEAGI_raw(data: PackedByteArray) -> void:
push_warning("FEAGI: Cannot send any data to FEAGI when the interface is disabled!")
_socket.websocket_send_bytes(data)

#endregion

#region Internals
func _network_bootstrap_complete() -> void:
print("FEAGI: Connecting to FEAGI Websocket at '%s'..." % _network_bootstrap.feagi_socket_address)
Expand All @@ -220,40 +265,50 @@ func _socket_recieved_data(data: PackedByteArray) -> void:
push_error("FEAGI: FEAGI did not return valid data!")
return
_parse_Feagi_data_as_inputs(_buffer_data as Dictionary)


# Keep these buffers non-local to minimize allocation / deallocation penalties
var _buffer_unpressed_motor_mapping_names: Array
var _buffer_motor_search_string: StringName
var _buffer_motor_search_index: int
## Parse through the recieved dict from FEAGI, and if matching patterns defined by the config, fire the defined action
func _parse_Feagi_data_as_inputs(feagi_input: Dictionary) -> void:
_buffer_unpressed_motor_mapping_names = _feagi_motor_mappings.keys()
_buffer_unpressed_motor_mapping_names = _feagi_action_holders.keys()

# Check which keys FEAGI Pressed
for from_OPU: StringName in feagi_input.keys():
for neuron_index: String in feagi_input[from_OPU].keys(): # For whatever reason, the neuron indexes sent from the controller are strings
_buffer_motor_search_string = from_OPU + neuron_index
_buffer_motor_search_index = _buffer_unpressed_motor_mapping_names.find(_buffer_motor_search_string)
if _buffer_motor_search_index != -1:
print("FEAGI: FEAGI pressed input: %s" % _feagi_motor_mappings[_buffer_motor_search_string].godot_action)
_feagi_motor_mappings[_buffer_motor_search_string].action(feagi_input[from_OPU][neuron_index], self)
_buffer_unpressed_motor_mapping_names.remove_at(_buffer_motor_search_index)

# for all keys unpressed, action with 0 strength
for unpressed_mapping_name: StringName in _buffer_unpressed_motor_mapping_names:
_feagi_motor_mappings[unpressed_mapping_name].action(0, self)

var FEAGI_action_holder: FEAGIActionHolder = _feagi_action_holders[_buffer_motor_search_string]
var FEAGI_strength: float = feagi_input[from_OPU][neuron_index]
print("FEAGI: FEAGI pressed input: %s" % FEAGI_action_holder.action_mapping.godot_action)
if has_signal(FEAGI_action_holder.action_mapping.optional_signal_name):
emit_signal(FEAGI_action_holder.action_mapping.optional_signal_name, FEAGI_strength)
FEAGI_action_holder.FEAGI_pressed(FEAGI_strength)


func _socket_change_state(state: WebSocketPeer.State) -> void:
if state == WebSocketPeer.STATE_OPEN:
print("FEAGI: FEAGI Socker Connected!")
print("FEAGI: FEAGI Socket Connected!")
feagi_connection_established.emit()
return
if state == WebSocketPeer.STATE_CLOSED or state == WebSocketPeer.STATE_CLOSING:
push_warning("FEAGI: FEAGI Socket closed, stopping FEAGI integration")
print("FEAGI: FEAGI Socket closed, stopping FEAGI integration")
set_process(false)
set_physics_process(false)
_is_socket_ready = false
feagi_connection_lost.emit()

func _get_value_type(custom_expected: EXPECTED_TYPE) -> Variant.Type:
match(custom_expected):
EXPECTED_TYPE.BOOL:
return TYPE_BOOL
EXPECTED_TYPE.INT:
return TYPE_INT
_:
return TYPE_FLOAT


#endregion
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,12 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================
"""
extends Object
extends RefCounted
class_name FEAGINetworkBootStrap
## Pings the webpage for the FEAGI connection issue, the sotres as public vars the conneciton details
## Pings the webpage for the FEAGI connection

# Static Network Configuration / defaults
const DEF_HEADERSTOUSE: PackedStringArray = ["Content-Type: application/json"]
#const DEF_FEAGI_TLD: StringName = "127.0.0.1"
#const DEF_FEAGI_SSL: StringName = "http://"
#const DEF_SOCKET_SSL: StringName = "ws://"
#const DEF_WEB_PORT: int = 8000
#const DEF_SOCKET_PORT: int = 9055
const DEF_SOCKET_MAX_QUEUED_PACKETS: int = 10000000
const DEF_SOCKET_INBOUND_BUFFER_SIZE: int = 10000000
const DEF_SOCKET_BUFFER_SIZE: int = 10000000
Expand Down
2 changes: 1 addition & 1 deletion addons/FeagiIntegration/Feagi-Interface/FeagiSocket.gd
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================
"""
extends Object
extends RefCounted
class_name FEAGISocket
## Essentially an extension of [WebSocketPeer] for FEAGI

Expand Down
2 changes: 2 additions & 0 deletions addons/FeagiIntegration/FeagiPluginInit.gd
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ func despawn_configurator_window() -> void:
return
remove_control_from_docks(_config_window)
_config_window.queue_free()
_config_window = null


func spawn_configurator_window() -> void:
if _config_window != null:
return

_config_window = DOCK_PREFAB.instantiate()
add_control_to_dock(EditorPlugin.DOCK_SLOT_RIGHT_BR, _config_window)
_config_window.setup(self)
Expand Down
Loading

0 comments on commit 4d9d945

Please sign in to comment.