diff options
Diffstat (limited to 'modules')
69 files changed, 30020 insertions, 266 deletions
diff --git a/modules/cscript/SCsub b/modules/cscript/SCsub new file mode 100644 index 0000000000..403fe68f66 --- /dev/null +++ b/modules/cscript/SCsub @@ -0,0 +1,5 @@ +Import('env') + +env.add_source_files(env.modules_sources,"*.cpp") + +Export('env') diff --git a/modules/cscript/config.py b/modules/cscript/config.py new file mode 100644 index 0000000000..ea7e83378a --- /dev/null +++ b/modules/cscript/config.py @@ -0,0 +1,11 @@ + + +def can_build(platform): + return True + + +def configure(env): + pass + + + diff --git a/modules/cscript/godot_c.cpp b/modules/cscript/godot_c.cpp new file mode 100644 index 0000000000..d5c1b53dfe --- /dev/null +++ b/modules/cscript/godot_c.cpp @@ -0,0 +1,2 @@ +#include "godot_c.h" + diff --git a/modules/cscript/godot_c.h b/modules/cscript/godot_c.h new file mode 100644 index 0000000000..b0465d8524 --- /dev/null +++ b/modules/cscript/godot_c.h @@ -0,0 +1,568 @@ +#ifndef GODOT_C_H +#define GODOT_C_H + +#ifdef __cplusplus +extern "C" { +#endif + + +#if defined(GDAPI_BUILT_IN) || !defined(WINDOWS_ENABLED) +#define GDAPI +#elif defined(GDAPI_EXPORTS) +#define GDAPI __declspec(dllexport) +#else +#define GDAPI __declspec(dllimport) +#endif + + +#define GODOT_API_VERSION 1 + + +typedef int godot_bool; + +#define GODOT_FALSE 0 +#define GODOT_TRUE 1 + +////// Image + + +#define GODOT_IMAGE_FORMAT_GRAYSCALE 0 +#define GODOT_IMAGE_FORMAT_INTENSITY 1 +#define GODOT_IMAGE_FORMAT_GRAYSCALE_ALPHA 2 +#define GODOT_IMAGE_FORMAT_RGB 3 +#define GODOT_IMAGE_FORMAT_RGBA 4 +#define GODOT_IMAGE_FORMAT_INDEXED 5 +#define GODOT_IMAGE_FORMAT_INDEXED_ALPHA 6 +#define GODOT_IMAGE_FORMAT_YUV_422 7 +#define GODOT_IMAGE_FORMAT_YUV_444 8 +#define GODOT_IMAGE_FORMAT_BC1 9 +#define GODOT_IMAGE_FORMAT_BC2 10 +#define GODOT_IMAGE_FORMAT_BC3 11 +#define GODOT_IMAGE_FORMAT_BC4 12 +#define GODOT_IMAGE_FORMAT_BC5 13 +#define GODOT_IMAGE_FORMAT_PVRTC2 14 +#define GODOT_IMAGE_FORMAT_PVRTC2_ALPHA 15 +#define GODOT_IMAGE_FORMAT_PVRTC4 16 +#define GODOT_IMAGE_FORMAT_PVRTC4_ALPHA 17 +#define GODOT_IMAGE_FORMAT_ETC 18 +#define GODOT_IMAGE_FORMAT_ATC 19 +#define GODOT_IMAGE_FORMAT_ATC_ALPHA_EXPLICIT 20 +#define GODOT_IMAGE_FORMAT_ATC_ALPHA_INTERPOLATED 21 + +typedef void* godot_image; + +godot_image GDAPI godot_image_create_empty(); +godot_image GDAPI godot_image_create(int p_width,int p_height,int p_format,int p_use_mipmaps); +godot_image GDAPI godot_image_create_with_data(int p_width,int p_height,int p_format,int p_use_mipmaps,unsigned char* p_buffer); +int GDAPI godot_image_get_width(godot_image p_image); +int GDAPI godot_image_get_height(godot_image p_image); +int GDAPI godot_image_get_format(godot_image p_image); +int GDAPI godot_image_get_mipmap_count(godot_image p_image); +godot_image GDAPI godot_image_copy(godot_image p_image); +void GDAPI godot_image_free(godot_image p_image); + + +////// RID + +typedef void* godot_rid; + +godot_rid GDAPI godot_rid_create(); +godot_rid GDAPI godot_rid_copy(godot_rid p_rid); +void GDAPI godot_rid_free(godot_rid p_rid); + +////// Variant (forward declared) + +typedef void* godot_variant; + + +////// Dictionary + +typedef void* godot_dictionary; + +godot_dictionary GDAPI godot_dictionary_create(); +void GDAPI godot_dictionary_has(godot_dictionary p_dictionary,godot_variant p_key); +godot_variant GDAPI godot_dictionary_get(godot_dictionary p_dictionary,godot_variant p_key); +void GDAPI godot_dictionary_insert(godot_dictionary p_dictionary,godot_variant p_key,godot_variant p_value); +void GDAPI godot_dictionary_remove(godot_dictionary p_dictionary,godot_variant p_key); +void GDAPI godot_dictionary_clear(godot_dictionary p_dictionary); +int GDAPI godot_dictionary_get_size(godot_dictionary p_dictionary); +void GDAPI godot_dictionary_get_keys(godot_dictionary p_dictionary,godot_variant* p_keys); +godot_dictionary GDAPI godot_dictionary_copy(godot_dictionary p_dictionary); +void GDAPI godot_dictionary_free(godot_dictionary p_dictionary); + +////// Array + +typedef void* godot_array; + +godot_array GDAPI godot_array_create(); +godot_variant GDAPI godot_array_get(godot_array p_array,int p_index); +void GDAPI godot_array_set(godot_array p_array,int p_index,godot_variant p_value); +void GDAPI godot_array_resize(godot_array p_array,int p_size); +void GDAPI godot_array_insert(godot_array p_array,int p_position,godot_variant p_value); +void GDAPI godot_array_remove(godot_array p_array,int p_position); +void GDAPI godot_array_clear(godot_array p_array); +int GDAPI godot_array_get_size(godot_array p_array); +int GDAPI godot_array_find(godot_array p_array,godot_variant p_value,int p_from_pos=-1); +godot_array GDAPI godot_array_copy(godot_array p_array); +void GDAPI godot_array_free(godot_array p_array); + +////// InputEvent + +#define INPUT_EVENT_BUTTON_LEFT 1 +#define INPUT_EVENT_BUTTON_RIGHT 2 +#define INPUT_EVENT_BUTTON_MIDDLE 3 +#define INPUT_EVENT_BUTTON_WHEEL_UP 4 +#define INPUT_EVENT_BUTTON_WHEEL_DOWN 5 +#define INPUT_EVENT_BUTTON_WHEEL_LEFT 6 +#define INPUT_EVENT_BUTTON_WHEEL_RIGHT 7 +#define INPUT_EVENT_BUTTON_MASK_LEFT (1<<(INPUT_EVENT_BUTTON_LEFT-1)) +#define INPUT_EVENT_BUTTON_MASK_RIGHT (1<<(INPUT_EVENT_BUTTON_RIGHT-1)) +#define INPUT_EVENT_BUTTON_MASK_MIDDLE (1<<(INPUT_EVENT_BUTTON_MIDDLE-1)) + +#define INPUT_EVENT_TYPE_NONE 0 +#define INPUT_EVENT_TYPE_KEY 1 +#define INPUT_EVENT_TYPE_MOUSE_MOTION 2 +#define INPUT_EVENT_TYPE_MOUSE_BUTTON 3 +#define INPUT_EVENT_TYPE_JOYSTICK_MOTION 4 +#define INPUT_EVENT_TYPE_JOYSTICK_BUTTON 5 +#define INPUT_EVENT_TYPE_SCREEN_TOUCH 6 +#define INPUT_EVENT_TYPE_SCREEN_DRAG 7 +#define INPUT_EVENT_TYPE_ACTION 8 + +typedef void* godot_input_event; + + +godot_input_event GDAPI godot_input_event_create(); +godot_input_event GDAPI godot_input_event_copy(godot_input_event p_input_event); +void GDAPI godot_input_event_free(godot_input_event p_input_event); + +int GDAPI godot_input_event_get_type(godot_input_event p_event); +int GDAPI godot_input_event_get_device(godot_input_event p_event); + +godot_bool GDAPI godot_input_event_mod_has_alt(godot_input_event p_event); +godot_bool GDAPI godot_input_event_mod_has_ctrl(godot_input_event p_event); +godot_bool GDAPI godot_input_event_mod_has_command(godot_input_event p_event); +godot_bool GDAPI godot_input_event_mod_has_shift(godot_input_event p_event); +godot_bool GDAPI godot_input_event_mod_has_meta(godot_input_event p_event); + +int GDAPI godot_input_event_key_get_scancode(godot_input_event p_event); +int GDAPI godot_input_event_key_get_unicode(godot_input_event p_event); +godot_bool GDAPI godot_input_event_key_is_pressed(godot_input_event p_event); +godot_bool GDAPI godot_input_event_key_is_echo(godot_input_event p_event); + +int GDAPI godot_input_event_mouse_get_x(godot_input_event p_event); +int GDAPI godot_input_event_mouse_get_y(godot_input_event p_event); +int GDAPI godot_input_event_mouse_get_global_x(godot_input_event p_event); +int GDAPI godot_input_event_mouse_get_global_y(godot_input_event p_event); +int GDAPI godot_input_event_mouse_get_button_mask(godot_input_event p_event); + +int GDAPI godot_input_event_mouse_button_get_button_index(godot_input_event p_event); +godot_bool GDAPI godot_input_event_mouse_button_is_pressed(godot_input_event p_event); +godot_bool GDAPI godot_input_event_mouse_button_is_doubleclick(godot_input_event p_event); + +int GDAPI godot_input_event_mouse_motion_get_relative_x(godot_input_event p_event); +int GDAPI godot_input_event_mouse_motion_get_relative_y(godot_input_event p_event); + +int GDAPI godot_input_event_mouse_motion_get_speed_x(godot_input_event p_event); +int GDAPI godot_input_event_mouse_motion_get_speed_y(godot_input_event p_event); + +int GDAPI godot_input_event_joystick_motion_get_axis(godot_input_event p_event); +float GDAPI godot_input_event_joystick_motion_get_axis_value(godot_input_event p_event); + +int GDAPI godot_input_event_joystick_button_get_button_index(godot_input_event p_event); +godot_bool GDAPI godot_input_event_joystick_button_is_pressed(godot_input_event p_event); +float GDAPI godot_input_event_joystick_button_get_pressure(godot_input_event p_event); + + +int GDAPI godot_input_event_screen_touch_get_index(godot_input_event p_event); +int GDAPI godot_input_event_screen_touch_get_x(godot_input_event p_event); +int GDAPI godot_input_event_screen_touch_get_y(godot_input_event p_event); +int GDAPI godot_input_event_screen_touch_is_pressed(godot_input_event p_event); + +int GDAPI godot_input_event_screen_drag_get_index(godot_input_event p_event); +int GDAPI godot_input_event_screen_drag_get_x(godot_input_event p_event); +int GDAPI godot_input_event_screen_drag_get_y(godot_input_event p_event); +int GDAPI godot_input_event_screen_drag_get_relative_x(godot_input_event p_event); +int GDAPI godot_input_event_screen_drag_get_relative_y(godot_input_event p_event); +int GDAPI godot_input_event_screen_drag_get_speed_x(godot_input_event p_event); +int GDAPI godot_input_event_screen_drag_get_speed_y(godot_input_event p_event); + +int GDAPI godot_input_event_is_action(godot_input_event p_event,char *p_action); +int GDAPI godot_input_event_is_action_pressed(godot_input_event p_event,char *p_action); + +////// ByteArray + +typedef void* godot_byte_array; + +godot_byte_array GDAPI godot_byte_array_create(); +godot_byte_array GDAPI godot_byte_array_copy(godot_byte_array p_byte_array); +void GDAPI godot_byte_array_free(godot_byte_array p_byte_array); + +int GDAPI godot_byte_array_get_size(godot_byte_array p_byte_array); +unsigned char GDAPI godot_byte_array_get(godot_byte_array p_byte_array,int p_index); +void GDAPI godot_byte_array_set(godot_byte_array p_byte_array,int p_index,unsigned char p_value); +void GDAPI godot_byte_array_remove(godot_byte_array p_byte_array,int p_index); +void GDAPI godot_byte_array_clear(godot_byte_array p_byte_array); + +typedef void* godot_byte_array_lock; + +godot_byte_array_lock GDAPI godot_byte_array_get_lock(godot_byte_array p_byte_array); +unsigned char GDAPI *godot_byte_array_lock_get_pointer(godot_byte_array_lock p_byte_array_lock); +void GDAPI godot_byte_array_lock_free(godot_byte_array_lock p_byte_array_lock); + + +godot_image GDAPI godot_image_create_with_array(int p_width,int p_height,int p_format,int p_use_mipmaps,godot_array p_array); +godot_byte_array GDAPI godot_image_get_data(godot_image p_image); + + +////// IntArray + +typedef void* godot_int_array; + +godot_int_array GDAPI godot_int_array_create(); +godot_int_array GDAPI godot_int_array_copy(godot_int_array p_int_array); +void GDAPI godot_int_array_free(godot_int_array p_int_array); + +int GDAPI godot_int_array_get_size(godot_int_array p_int_array); +int GDAPI godot_int_array_get(godot_int_array p_int_array,int p_index); +void GDAPI godot_int_array_set(godot_int_array p_int_array,int p_index,int p_value); +void GDAPI godot_int_array_remove(godot_int_array p_int_array,int p_index); +void GDAPI godot_int_array_clear(godot_int_array p_int_array); + +typedef void* godot_int_array_lock; + +godot_int_array_lock GDAPI godot_int_array_get_lock(godot_int_array p_int_array); +int GDAPI *godot_int_array_lock_get_pointer(godot_int_array_lock p_int_array_lock); +void GDAPI godot_int_array_lock_free(godot_int_array_lock p_int_array_lock); + +////// RealArray + +typedef void* godot_real_array; + + +godot_real_array GDAPI godot_real_array_create(); +godot_real_array GDAPI godot_real_array_copy(godot_real_array p_real_array); +void GDAPI godot_real_array_free(godot_real_array p_real_array); + +int GDAPI godot_real_array_get_size(godot_real_array p_real_array); +float GDAPI godot_real_array_get(godot_real_array p_real_array,int p_index); +void GDAPI godot_real_array_set(godot_real_array p_real_array,int p_index,float p_value); +void GDAPI godot_real_array_remove(godot_real_array p_real_array,int p_index); +void GDAPI godot_real_array_clear(godot_real_array p_real_array); + +typedef void* godot_real_array_lock; + +godot_real_array_lock GDAPI godot_real_array_get_lock(godot_real_array p_real_array); +float GDAPI *godot_real_array_lock_get_pointer(godot_real_array_lock p_real_array_lock); +void GDAPI godot_real_array_lock_free(godot_real_array_lock p_real_array_lock); + + +////// StringArray + +typedef void* godot_string_array; + + +godot_string_array GDAPI godot_string_array_create(); +godot_string_array GDAPI godot_string_array_copy(godot_string_array p_string_array); +void GDAPI godot_string_array_free(godot_string_array p_string_array); + +int GDAPI godot_string_array_get_size(godot_string_array p_string_array); +int GDAPI godot_string_array_get(godot_string_array p_string_array,int p_index,unsigned char* p_string,int p_max_len); +void GDAPI godot_string_array_set(godot_string_array p_string_array,int p_index,unsigned char *p_string); +void GDAPI godot_string_array_remove(godot_string_array p_string_array,int p_index); +void GDAPI godot_string_array_clear(godot_string_array p_string_array); + +////// Vector2Array + +typedef void* godot_vector2_array; + +godot_vector2_array GDAPI godot_vector2_array_create(); +godot_vector2_array GDAPI godot_vector2_array_copy(godot_vector2_array p_vector2_array); +void GDAPI godot_vector2_array_free(godot_vector2_array p_vector2_array); + +int GDAPI godot_vector2_array_get_size(godot_vector2_array p_vector2_array); +int GDAPI godot_vector2_array_get_stride(godot_vector2_array p_vector2_array); +void GDAPI godot_vector2_array_get(godot_vector2_array p_vector2_array,int p_index,float* p_vector2); +void GDAPI godot_vector2_array_set(godot_vector2_array p_vector2_array,int p_index,float *p_vector2); +void GDAPI godot_vector2_array_remove(godot_vector2_array p_vector2_array,int p_index); +void GDAPI godot_vector2_array_clear(godot_vector2_array p_vector2_array); + + +typedef void* godot_vector2_array_lock; + +godot_vector2_array_lock GDAPI godot_vector2_array_get_lock(godot_vector2_array p_vector2_array); +float GDAPI *godot_vector2_array_lock_get_pointer(godot_vector2_array_lock p_vector2_array_lock); +void GDAPI godot_vector2_array_lock_free(godot_vector2_array_lock p_vector2_array_lock); + +////// Vector3Array + +typedef void* godot_vector3_array; + +godot_vector3_array GDAPI godot_vector3_array_create(); +godot_vector3_array GDAPI godot_vector3_array_copy(godot_vector3_array p_vector3_array); +void GDAPI godot_vector3_array_free(godot_vector3_array p_vector3_array); + +int GDAPI godot_vector3_array_get_size(godot_vector3_array p_vector3_array); +int GDAPI godot_vector3_array_get_stride(godot_vector3_array p_vector3_array); +void GDAPI godot_vector3_array_get(godot_vector3_array p_vector3_array,int p_index,float* p_vector3); +void GDAPI godot_vector3_array_set(godot_vector3_array p_vector3_array,int p_index,float *p_vector3); +void GDAPI godot_vector3_array_remove(godot_vector3_array p_vector3_array,int p_index); +void GDAPI godot_vector3_array_clear(godot_vector3_array p_vector3_array); + + +typedef void* godot_vector3_array_lock; + +godot_vector3_array_lock GDAPI godot_vector3_array_get_lock(godot_vector3_array p_vector3_array); +float GDAPI *godot_vector3_array_lock_get_pointer(godot_vector3_array_lock p_vector3_array_lock); +void GDAPI godot_vector3_array_lock_free(godot_vector3_array_lock p_vector3_array_lock); + +////// ColorArray + +typedef void* godot_color_array; + +godot_color_array GDAPI godot_color_array_create(); +godot_color_array GDAPI godot_color_array_copy(godot_color_array p_color_array); +void GDAPI godot_color_array_free(godot_color_array p_color_array); + +int GDAPI godot_color_array_get_size(godot_color_array p_color_array); +int GDAPI godot_color_array_get_stride(godot_color_array p_color_array); +void GDAPI godot_color_array_get(godot_color_array p_color_array,int p_index,float* p_color); +void GDAPI godot_color_array_set(godot_color_array p_color_array,int p_index,float *p_color); +void GDAPI godot_color_array_remove(godot_color_array p_color_array,int p_index); +void GDAPI godot_color_array_clear(godot_color_array p_color_array); + + +typedef void* godot_color_array_lock; + +godot_color_array_lock GDAPI godot_color_array_get_lock(godot_color_array p_color_array); +float GDAPI *godot_color_array_lock_get_pointer(godot_color_array_lock p_color_array_lock); +void GDAPI godot_color_array_lock_free(godot_color_array_lock p_color_array_lock); + + +////// Instance (forward declared) + +typedef void *godot_instance; + +////// Variant + +#define GODOT_VARIANT_NIL 0 +#define GODOT_VARIANT_BOOL 1 +#define GODOT_VARIANT_INT 2 +#define GODOT_VARIANT_REAL 3 +#define GODOT_VARIANT_STRING 4 +#define GODOT_VARIANT_VECTOR2 5 +#define GODOT_VARIANT_RECT2 6 +#define GODOT_VARIANT_VECTOR3 7 +#define GODOT_VARIANT_MATRIX32 8 +#define GODOT_VARIANT_PLANE 9 +#define GODOT_VARIANT_QUAT 10 +#define GODOT_VARIANT_AABB 11 +#define GODOT_VARIANT_MATRIX3 12 +#define GODOT_VARIANT_TRANSFORM 13 +#define GODOT_VARIANT_COLOR 14 +#define GODOT_VARIANT_IMAGE 15 +#define GODOT_VARIANT_NODE_PATH 16 +#define GODOT_VARIANT_RID 17 +#define GODOT_VARIANT_OBJECT 18 +#define GODOT_VARIANT_INPUT_EVENT 19 +#define GODOT_VARIANT_DICTIONARY 20 +#define GODOT_VARIANT_ARRAY 21 +#define GODOT_VARIANT_BYTE_ARRAY 22 +#define GODOT_VARIANT_INT_ARRAY 23 +#define GODOT_VARIANT_REAL_ARRAY 24 +#define GODOT_VARIANT_STRING_ARRAY 25 +#define GODOT_VARIANT_VECTOR2_ARRAY 26 +#define GODOT_VARIANT_VECTOR3_ARRAY 27 +#define GODOT_VARIANT_COLOR_ARRAY 28 +#define GODOT_VARIANT_MAX 29 + +godot_variant *godot_variant_new(); + +int GDAPI godot_variant_get_type(godot_variant p_variant); + +void GDAPI godot_variant_set_null(godot_variant p_variant); +void GDAPI godot_variant_set_bool(godot_variant p_variant,godot_bool p_bool); +void GDAPI godot_variant_set_int(godot_variant p_variant,int p_int); +void GDAPI godot_variant_set_float(godot_variant p_variant,int p_float); +void GDAPI godot_variant_set_string(godot_variant p_variant,char *p_string); +void GDAPI godot_variant_set_vector2(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_rect2(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_vector3(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_matrix32(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_plane(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_aabb(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_matrix3(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_transform(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_color(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_image(godot_variant p_variant,godot_image *p_image); +void GDAPI godot_variant_set_node_path(godot_variant p_variant,char *p_path); +void GDAPI godot_variant_set_rid(godot_variant p_variant,char *p_path); +void GDAPI godot_variant_set_instance(godot_variant p_variant,godot_instance p_instance); +void GDAPI godot_variant_set_input_event(godot_variant p_variant,godot_input_event p_instance); +void GDAPI godot_variant_set_dictionary(godot_variant p_variant,godot_dictionary p_dictionary); +void GDAPI godot_variant_set_array(godot_variant p_variant,godot_array p_array); +void GDAPI godot_variant_set_byte_array(godot_variant p_variant,godot_byte_array p_array); +void GDAPI godot_variant_set_int_array(godot_variant p_variant,godot_byte_array p_array); +void GDAPI godot_variant_set_string_array(godot_variant p_variant,godot_string_array p_array); +void GDAPI godot_variant_set_vector2_array(godot_variant p_variant,godot_vector2_array p_array); +void GDAPI godot_variant_set_vector3_array(godot_variant p_variant,godot_vector3_array p_array); +void GDAPI godot_variant_set_color_array(godot_variant p_variant,godot_color_array p_array); + +godot_bool GDAPI godot_variant_get_bool(godot_variant p_variant); +int GDAPI godot_variant_get_int(godot_variant p_variant); +float GDAPI godot_variant_get_float(godot_variant p_variant); +int GDAPI godot_variant_get_string(godot_variant p_variant,char *p_string,int p_bufsize); +void GDAPI godot_variant_get_vector2(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_rect2(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_vector3(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_matrix32(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_plane(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_aabb(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_matrix3(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_transform(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_color(godot_variant p_variant,float *p_elems); +godot_image GDAPI *godot_variant_get_image(godot_variant p_variant); +int GDAPI godot_variant_get_node_path(godot_variant p_variant,char *p_path, int p_bufsize); +godot_rid GDAPI godot_variant_get_rid(godot_variant p_variant); +godot_instance GDAPI godot_variant_get_instance(godot_variant p_variant); +void GDAPI godot_variant_get_input_event(godot_variant p_variant,godot_input_event); +void GDAPI godot_variant_get_dictionary(godot_variant p_variant,godot_dictionary); +godot_array GDAPI godot_variant_get_array(godot_variant p_variant); +godot_byte_array GDAPI godot_variant_get_byte_array(godot_variant p_variant); +godot_byte_array GDAPI godot_variant_get_int_array(godot_variant p_variant); +godot_string_array GDAPI godot_variant_get_string_array(godot_variant p_variant); +godot_vector2_array GDAPI godot_variant_get_vector2_array(godot_variant p_variant); +godot_vector3_array GDAPI godot_variant_get_vector3_array(godot_variant p_variant); +godot_color_array GDAPI godot_variant_get_color_array(godot_variant p_variant); + + +void GDAPI godot_variant_delete(godot_variant p_variant); + +////// Class +/// + +char GDAPI **godot_class_get_list(); //get list of classes in array to array of strings, must be freed, use godot_list_free() + +int GDAPI godot_class_get_base(char* p_class,char *p_base,int p_max_len); +int GDAPI godot_class_get_name(char* p_class,char *p_base,int p_max_len); + +char GDAPI **godot_class_get_method_list(char* p_class); //free with godot_list_free() +int GDAPI godot_class_method_get_argument_count(char* p_class,char *p_method); +int GDAPI godot_class_method_get_argument_type(char* p_class,char *p_method,int p_argument); +godot_variant GDAPI godot_class_method_get_argument_default_value(char* p_class,char *p_method,int p_argument); + +char GDAPI **godot_class_get_constant_list(char* p_class); //free with godot_list_free() +int GDAPI godot_class_constant_get_value(char* p_class,char *p_constant); + + +////// Instance + +typedef int godot_call_error; + +#define GODOT_CALL_OK +#define GODOT_CALL_ERROR_WRONG_ARGUMENTS +#define GODOT_CALL_ERROR_INVALID_INSTANCE + +godot_instance GDAPI godot_instance_new(char* p_class); +int GDAPI godot_instance_get_class(godot_instance p_instance,char* p_class,int p_max_len); + +typedef struct { + char *name; + int hint; + char *hint_string; + int usage; +} godot_property_info; + +godot_call_error GDAPI godot_instance_call(godot_instance p_instance, char* p_method, ...); +godot_call_error GDAPI godot_instance_call_ret(godot_instance p_instance, godot_variant r_return, char* p_method, ...); +godot_bool GDAPI godot_instance_set(godot_instance p_instance, char* p_prop,godot_variant p_value); +godot_variant GDAPI godot_instance_get(godot_instance p_instance, char* p_prop); + + +#define GODOT_PROPERTY_HINT_NONE 0 ///< no hint provided. +#define GODOT_PROPERTY_HINT_RANGE 1///< hint_text = "min,max,step,slider; //slider is optional" +#define GODOT_PROPERTY_HINT_EXP_RANGE 2///< hint_text = "min,max,step", exponential edit +#define GODOT_PROPERTY_HINT_ENUM 3///< hint_text= "val1,val2,val3,etc" +#define GODOT_PROPERTY_HINT_EXP_EASING 4/// exponential easing funciton (Math::ease) +#define GODOT_PROPERTY_HINT_LENGTH 5///< hint_text= "length" (as integer) +#define GODOT_PROPERTY_HINT_SPRITE_FRAME 6 +#define GODOT_PROPERTY_HINT_KEY_ACCEL 7///< hint_text= "length" (as integer) +#define GODOT_PROPERTY_HINT_FLAGS 8///< hint_text= "flag1,flag2,etc" (as bit flags) +#define GODOT_PROPERTY_HINT_ALL_FLAGS 9 +#define GODOT_PROPERTY_HINT_FILE 10 ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc," +#define GODOT_PROPERTY_HINT_DIR 11 ///< a directort path must be passed +#define GODOT_PROPERTY_HINT_GLOBAL_FILE 12///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc," +#define GODOT_PROPERTY_HINT_GLOBAL_DIR 13 ///< a directort path must be passed +#define GODOT_PROPERTY_HINT_RESOURCE_TYPE 14///< a resource object type +#define GODOT_PROPERTY_HINT_MULTILINE_TEXT 15///< used for string properties that can contain multiple lines +#define GODOT_PROPERTY_HINT_COLOR_NO_ALPHA 16///< used for ignoring alpha component when editing a color +#define GODOT_PROPERTY_HINT_IMAGE_COMPRESS_LOSSY 17 +#define GODOT_PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS 18 +#define GODOT_PROPERTY_HINT_OBJECT_ID 19 + + +#define GODOT_PROPERTY_USAGE_STORAGE 1 +#define GODOT_PROPERTY_USAGE_EDITOR 2 +#define GODOT_PROPERTY_USAGE_NETWORK 4 +#define GODOT_PROPERTY_USAGE_EDITOR_HELPER 8 +#define GODOT_PROPERTY_USAGE_CHECKABLE 16 //used for editing global variables +#define GODOT_PROPERTY_USAGE_CHECKED 32 //used for editing global variables +#define GODOT_PROPERTY_USAGE_INTERNATIONALIZED 64 //hint for internationalized strings +#define GODOT_PROPERTY_USAGE_BUNDLE 128 //used for optimized bundles +#define GODOT_PROPERTY_USAGE_CATEGORY 256 +#define GODOT_PROPERTY_USAGE_STORE_IF_NONZERO 512 //only store if nonzero +#define GODOT_PROPERTY_USAGE_STORE_IF_NONONE 1024 //only store if false +#define GODOT_PROPERTY_USAGE_NO_INSTANCE_STATE 2048 +#define GODOT_PROPERTY_USAGE_RESTART_IF_CHANGED 4096 +#define GODOT_PROPERTY_USAGE_SCRIPT_VARIABLE 8192 +#define GODOT_PROPERTY_USAGE_STORE_IF_NULL 16384 +#define GODOT_PROPERTY_USAGE_ANIMATE_AS_TRIGGER 32768 + +#define GODOT_PROPERTY_USAGE_DEFAULT GODOT_PROPERTY_USAGE_STORAGE|GODOT_PROPERTY_USAGE_EDITOR|GODOT_PROPERTY_USAGE_NETWORK +#define GODOT_PROPERTY_USAGE_DEFAULT_INTL GODOT_PROPERTY_USAGE_STORAGE|GODOT_PROPERTY_USAGE_EDITOR|GODOT_PROPERTY_USAGE_NETWORK|GODOT_PROPERTY_USAGE_INTERNATIONALIZED +#define GODOT_PROPERTY_USAGE_NOEDITOR GODOT_PROPERTY_USAGE_STORAGE|GODOT_PROPERTY_USAGE_NETWORK + + +godot_property_info GDAPI **godot_instance_get_property_list(godot_instance p_instance); +void GDAPI godot_instance_free_property_list(godot_instance p_instance,godot_property_info** p_list); + + + +void GDAPI godot_list_free(char **p_name); //helper to free all the class list + + +////// Script API + +typedef void* (godot_script_instance_func)(godot_instance); //passed an instance, return a pointer to your userdata +typedef void (godot_script_free_func)(godot_instance,void*); //passed an instance, please free your userdata + +void GDAPI godot_script_register(char* p_base,char* p_name,godot_script_instance_func p_instance_func,godot_script_free_func p_free_func); +void GDAPI godot_script_unregister(char* p_name); + +typedef GDAPI godot_variant (godot_script_func)(godot_instance,void*,godot_variant*,int); //instance,userdata,arguments,argument count. Return something or NULL. Arguments must not be freed. + + +void GDAPI godot_script_add_function(char* p_name,char* p_function_name,godot_script_func p_func); +void GDAPI godot_script_add_validated_function(char* p_name,char* p_function_name,godot_script_func p_func,int* p_arg_types,int p_arg_count,godot_variant* p_default_args,int p_default_arg_count); + +typedef void (godot_set_property_func)(godot_instance,void*,godot_variant); //instance,userdata,value. Value must not be freed. +typedef godot_variant (godot_get_property_func)(godot_instance,void*); //instance,userdata. Return a value or NULL. + +void GDAPI godot_script_add_property(char* p_name,char* p_path,godot_set_property_func p_set_func,godot_get_property_func p_get_func); +void GDAPI godot_script_add_listed_property(char* p_name,char* p_path,godot_set_property_func p_set_func,godot_get_property_func p_get_func,int p_type,int p_hint,char* p_hint_string,int p_usage); + + +////// System Functions + +//using these will help Godot track how much memory is in use in debug mode +void GDAPI *godot_alloc(int p_bytes); +void GDAPI *godot_realloc(void* p_ptr,int p_bytes); +void GDAPI godot_free(void* p_ptr); + + +#ifdef __cplusplus +} +#endif + + +#endif // GODOT_C_H diff --git a/modules/gdscript/gd_pretty_print.h b/modules/cscript/register_types.cpp index 0106d873d9..267e5245ed 100644 --- a/modules/gdscript/gd_pretty_print.h +++ b/modules/cscript/register_types.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* gd_pretty_print.h */ +/* register_types.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -26,15 +26,12 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GD_PRETTY_PRINT_H -#define GD_PRETTY_PRINT_H +#include "register_types.h" +void register_cscript_types() { +} +void unregister_cscript_types() { -class GDPrettyPrint { -public: - GDPrettyPrint(); -}; - -#endif // GD_PRETTY_PRINT_H +} diff --git a/modules/gdscript/gd_pretty_print.cpp b/modules/cscript/register_types.h index cca3cd3984..a0f41eee5d 100644 --- a/modules/gdscript/gd_pretty_print.cpp +++ b/modules/cscript/register_types.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* gd_pretty_print.cpp */ +/* register_types.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -26,9 +26,5 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "gd_pretty_print.h" - -GDPrettyPrint::GDPrettyPrint() { - - -} +void register_cscript_types(); +void unregister_cscript_types(); diff --git a/modules/enet/SCsub b/modules/enet/SCsub new file mode 100644 index 0000000000..d2bc8801e4 --- /dev/null +++ b/modules/enet/SCsub @@ -0,0 +1,8 @@ +Import('env') + +env.add_source_files(env.modules_sources,"*.cpp") +env.add_source_files(env.modules_sources,"*.c") +#TODO: Make it possible to build against system enet +env.Append(CPPPATH = ["#modules/enet"]) + +Export('env') diff --git a/modules/enet/callbacks.c b/modules/enet/callbacks.c new file mode 100644 index 0000000000..b3990af1fb --- /dev/null +++ b/modules/enet/callbacks.c @@ -0,0 +1,53 @@ +/** + @file callbacks.c + @brief ENet callback functions +*/ +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +static ENetCallbacks callbacks = { malloc, free, abort }; + +int +enet_initialize_with_callbacks (ENetVersion version, const ENetCallbacks * inits) +{ + if (version < ENET_VERSION_CREATE (1, 3, 0)) + return -1; + + if (inits -> malloc != NULL || inits -> free != NULL) + { + if (inits -> malloc == NULL || inits -> free == NULL) + return -1; + + callbacks.malloc = inits -> malloc; + callbacks.free = inits -> free; + } + + if (inits -> no_memory != NULL) + callbacks.no_memory = inits -> no_memory; + + return enet_initialize (); +} + +ENetVersion +enet_linked_version (void) +{ + return ENET_VERSION; +} + +void * +enet_malloc (size_t size) +{ + void * memory = callbacks.malloc (size); + + if (memory == NULL) + callbacks.no_memory (); + + return memory; +} + +void +enet_free (void * memory) +{ + callbacks.free (memory); +} + diff --git a/modules/enet/compress.c b/modules/enet/compress.c new file mode 100644 index 0000000000..784489a787 --- /dev/null +++ b/modules/enet/compress.c @@ -0,0 +1,654 @@ +/** + @file compress.c + @brief An adaptive order-2 PPM range coder +*/ +#define ENET_BUILDING_LIB 1 +#include <string.h> +#include "enet/enet.h" + +typedef struct _ENetSymbol +{ + /* binary indexed tree of symbols */ + enet_uint8 value; + enet_uint8 count; + enet_uint16 under; + enet_uint16 left, right; + + /* context defined by this symbol */ + enet_uint16 symbols; + enet_uint16 escapes; + enet_uint16 total; + enet_uint16 parent; +} ENetSymbol; + +/* adaptation constants tuned aggressively for small packet sizes rather than large file compression */ +enum +{ + ENET_RANGE_CODER_TOP = 1<<24, + ENET_RANGE_CODER_BOTTOM = 1<<16, + + ENET_CONTEXT_SYMBOL_DELTA = 3, + ENET_CONTEXT_SYMBOL_MINIMUM = 1, + ENET_CONTEXT_ESCAPE_MINIMUM = 1, + + ENET_SUBCONTEXT_ORDER = 2, + ENET_SUBCONTEXT_SYMBOL_DELTA = 2, + ENET_SUBCONTEXT_ESCAPE_DELTA = 5 +}; + +/* context exclusion roughly halves compression speed, so disable for now */ +#undef ENET_CONTEXT_EXCLUSION + +typedef struct _ENetRangeCoder +{ + /* only allocate enough symbols for reasonable MTUs, would need to be larger for large file compression */ + ENetSymbol symbols[4096]; +} ENetRangeCoder; + +void * +enet_range_coder_create (void) +{ + ENetRangeCoder * rangeCoder = (ENetRangeCoder *) enet_malloc (sizeof (ENetRangeCoder)); + if (rangeCoder == NULL) + return NULL; + + return rangeCoder; +} + +void +enet_range_coder_destroy (void * context) +{ + ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context; + if (rangeCoder == NULL) + return; + + enet_free (rangeCoder); +} + +#define ENET_SYMBOL_CREATE(symbol, value_, count_) \ +{ \ + symbol = & rangeCoder -> symbols [nextSymbol ++]; \ + symbol -> value = value_; \ + symbol -> count = count_; \ + symbol -> under = count_; \ + symbol -> left = 0; \ + symbol -> right = 0; \ + symbol -> symbols = 0; \ + symbol -> escapes = 0; \ + symbol -> total = 0; \ + symbol -> parent = 0; \ +} + +#define ENET_CONTEXT_CREATE(context, escapes_, minimum) \ +{ \ + ENET_SYMBOL_CREATE (context, 0, 0); \ + (context) -> escapes = escapes_; \ + (context) -> total = escapes_ + 256*minimum; \ + (context) -> symbols = 0; \ +} + +static enet_uint16 +enet_symbol_rescale (ENetSymbol * symbol) +{ + enet_uint16 total = 0; + for (;;) + { + symbol -> count -= symbol->count >> 1; + symbol -> under = symbol -> count; + if (symbol -> left) + symbol -> under += enet_symbol_rescale (symbol + symbol -> left); + total += symbol -> under; + if (! symbol -> right) break; + symbol += symbol -> right; + } + return total; +} + +#define ENET_CONTEXT_RESCALE(context, minimum) \ +{ \ + (context) -> total = (context) -> symbols ? enet_symbol_rescale ((context) + (context) -> symbols) : 0; \ + (context) -> escapes -= (context) -> escapes >> 1; \ + (context) -> total += (context) -> escapes + 256*minimum; \ +} + +#define ENET_RANGE_CODER_OUTPUT(value) \ +{ \ + if (outData >= outEnd) \ + return 0; \ + * outData ++ = value; \ +} + +#define ENET_RANGE_CODER_ENCODE(under, count, total) \ +{ \ + encodeRange /= (total); \ + encodeLow += (under) * encodeRange; \ + encodeRange *= (count); \ + for (;;) \ + { \ + if((encodeLow ^ (encodeLow + encodeRange)) >= ENET_RANGE_CODER_TOP) \ + { \ + if(encodeRange >= ENET_RANGE_CODER_BOTTOM) break; \ + encodeRange = -encodeLow & (ENET_RANGE_CODER_BOTTOM - 1); \ + } \ + ENET_RANGE_CODER_OUTPUT (encodeLow >> 24); \ + encodeRange <<= 8; \ + encodeLow <<= 8; \ + } \ +} + +#define ENET_RANGE_CODER_FLUSH \ +{ \ + while (encodeLow) \ + { \ + ENET_RANGE_CODER_OUTPUT (encodeLow >> 24); \ + encodeLow <<= 8; \ + } \ +} + +#define ENET_RANGE_CODER_FREE_SYMBOLS \ +{ \ + if (nextSymbol >= sizeof (rangeCoder -> symbols) / sizeof (ENetSymbol) - ENET_SUBCONTEXT_ORDER ) \ + { \ + nextSymbol = 0; \ + ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); \ + predicted = 0; \ + order = 0; \ + } \ +} + +#define ENET_CONTEXT_ENCODE(context, symbol_, value_, under_, count_, update, minimum) \ +{ \ + under_ = value*minimum; \ + count_ = minimum; \ + if (! (context) -> symbols) \ + { \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + (context) -> symbols = symbol_ - (context); \ + } \ + else \ + { \ + ENetSymbol * node = (context) + (context) -> symbols; \ + for (;;) \ + { \ + if (value_ < node -> value) \ + { \ + node -> under += update; \ + if (node -> left) { node += node -> left; continue; } \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + node -> left = symbol_ - node; \ + } \ + else \ + if (value_ > node -> value) \ + { \ + under_ += node -> under; \ + if (node -> right) { node += node -> right; continue; } \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + node -> right = symbol_ - node; \ + } \ + else \ + { \ + count_ += node -> count; \ + under_ += node -> under - node -> count; \ + node -> under += update; \ + node -> count += update; \ + symbol_ = node; \ + } \ + break; \ + } \ + } \ +} + +#ifdef ENET_CONTEXT_EXCLUSION +static const ENetSymbol emptyContext = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +#define ENET_CONTEXT_WALK(context, body) \ +{ \ + const ENetSymbol * node = (context) + (context) -> symbols; \ + const ENetSymbol * stack [256]; \ + size_t stackSize = 0; \ + while (node -> left) \ + { \ + stack [stackSize ++] = node; \ + node += node -> left; \ + } \ + for (;;) \ + { \ + body; \ + if (node -> right) \ + { \ + node += node -> right; \ + while (node -> left) \ + { \ + stack [stackSize ++] = node; \ + node += node -> left; \ + } \ + } \ + else \ + if (stackSize <= 0) \ + break; \ + else \ + node = stack [-- stackSize]; \ + } \ +} + +#define ENET_CONTEXT_ENCODE_EXCLUDE(context, value_, under, total, minimum) \ +ENET_CONTEXT_WALK(context, { \ + if (node -> value != value_) \ + { \ + enet_uint16 parentCount = rangeCoder -> symbols [node -> parent].count + minimum; \ + if (node -> value < value_) \ + under -= parentCount; \ + total -= parentCount; \ + } \ +}) +#endif + +size_t +enet_range_coder_compress (void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit) +{ + ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context; + enet_uint8 * outStart = outData, * outEnd = & outData [outLimit]; + const enet_uint8 * inData, * inEnd; + enet_uint32 encodeLow = 0, encodeRange = ~0; + ENetSymbol * root; + enet_uint16 predicted = 0; + size_t order = 0, nextSymbol = 0; + + if (rangeCoder == NULL || inBufferCount <= 0 || inLimit <= 0) + return 0; + + inData = (const enet_uint8 *) inBuffers -> data; + inEnd = & inData [inBuffers -> dataLength]; + inBuffers ++; + inBufferCount --; + + ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); + + for (;;) + { + ENetSymbol * subcontext, * symbol; +#ifdef ENET_CONTEXT_EXCLUSION + const ENetSymbol * childContext = & emptyContext; +#endif + enet_uint8 value; + enet_uint16 count, under, * parent = & predicted, total; + if (inData >= inEnd) + { + if (inBufferCount <= 0) + break; + inData = (const enet_uint8 *) inBuffers -> data; + inEnd = & inData [inBuffers -> dataLength]; + inBuffers ++; + inBufferCount --; + } + value = * inData ++; + + for (subcontext = & rangeCoder -> symbols [predicted]; + subcontext != root; +#ifdef ENET_CONTEXT_EXCLUSION + childContext = subcontext, +#endif + subcontext = & rangeCoder -> symbols [subcontext -> parent]) + { + ENET_CONTEXT_ENCODE (subcontext, symbol, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0); + * parent = symbol - rangeCoder -> symbols; + parent = & symbol -> parent; + total = subcontext -> total; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > ENET_SUBCONTEXT_SYMBOL_DELTA + ENET_SUBCONTEXT_ESCAPE_DELTA) + ENET_CONTEXT_ENCODE_EXCLUDE (childContext, value, under, total, 0); +#endif + if (count > 0) + { + ENET_RANGE_CODER_ENCODE (subcontext -> escapes + under, count, total); + } + else + { + if (subcontext -> escapes > 0 && subcontext -> escapes < total) + ENET_RANGE_CODER_ENCODE (0, subcontext -> escapes, total); + subcontext -> escapes += ENET_SUBCONTEXT_ESCAPE_DELTA; + subcontext -> total += ENET_SUBCONTEXT_ESCAPE_DELTA; + } + subcontext -> total += ENET_SUBCONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || subcontext -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (subcontext, 0); + if (count > 0) goto nextInput; + } + + ENET_CONTEXT_ENCODE (root, symbol, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM); + * parent = symbol - rangeCoder -> symbols; + parent = & symbol -> parent; + total = root -> total; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > ENET_SUBCONTEXT_SYMBOL_DELTA + ENET_SUBCONTEXT_ESCAPE_DELTA) + ENET_CONTEXT_ENCODE_EXCLUDE (childContext, value, under, total, ENET_CONTEXT_SYMBOL_MINIMUM); +#endif + ENET_RANGE_CODER_ENCODE (root -> escapes + under, count, total); + root -> total += ENET_CONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_CONTEXT_SYMBOL_DELTA + ENET_CONTEXT_SYMBOL_MINIMUM || root -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (root, ENET_CONTEXT_SYMBOL_MINIMUM); + + nextInput: + if (order >= ENET_SUBCONTEXT_ORDER) + predicted = rangeCoder -> symbols [predicted].parent; + else + order ++; + ENET_RANGE_CODER_FREE_SYMBOLS; + } + + ENET_RANGE_CODER_FLUSH; + + return (size_t) (outData - outStart); +} + +#define ENET_RANGE_CODER_SEED \ +{ \ + if (inData < inEnd) decodeCode |= * inData ++ << 24; \ + if (inData < inEnd) decodeCode |= * inData ++ << 16; \ + if (inData < inEnd) decodeCode |= * inData ++ << 8; \ + if (inData < inEnd) decodeCode |= * inData ++; \ +} + +#define ENET_RANGE_CODER_READ(total) ((decodeCode - decodeLow) / (decodeRange /= (total))) + +#define ENET_RANGE_CODER_DECODE(under, count, total) \ +{ \ + decodeLow += (under) * decodeRange; \ + decodeRange *= (count); \ + for (;;) \ + { \ + if((decodeLow ^ (decodeLow + decodeRange)) >= ENET_RANGE_CODER_TOP) \ + { \ + if(decodeRange >= ENET_RANGE_CODER_BOTTOM) break; \ + decodeRange = -decodeLow & (ENET_RANGE_CODER_BOTTOM - 1); \ + } \ + decodeCode <<= 8; \ + if (inData < inEnd) \ + decodeCode |= * inData ++; \ + decodeRange <<= 8; \ + decodeLow <<= 8; \ + } \ +} + +#define ENET_CONTEXT_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, createRoot, visitNode, createRight, createLeft) \ +{ \ + under_ = 0; \ + count_ = minimum; \ + if (! (context) -> symbols) \ + { \ + createRoot; \ + } \ + else \ + { \ + ENetSymbol * node = (context) + (context) -> symbols; \ + for (;;) \ + { \ + enet_uint16 after = under_ + node -> under + (node -> value + 1)*minimum, before = node -> count + minimum; \ + visitNode; \ + if (code >= after) \ + { \ + under_ += node -> under; \ + if (node -> right) { node += node -> right; continue; } \ + createRight; \ + } \ + else \ + if (code < after - before) \ + { \ + node -> under += update; \ + if (node -> left) { node += node -> left; continue; } \ + createLeft; \ + } \ + else \ + { \ + value_ = node -> value; \ + count_ += node -> count; \ + under_ = after - before; \ + node -> under += update; \ + node -> count += update; \ + symbol_ = node; \ + } \ + break; \ + } \ + } \ +} + +#define ENET_CONTEXT_TRY_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, exclude) \ +ENET_CONTEXT_DECODE (context, symbol_, code, value_, under_, count_, update, minimum, return 0, exclude (node -> value, after, before), return 0, return 0) + +#define ENET_CONTEXT_ROOT_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, exclude) \ +ENET_CONTEXT_DECODE (context, symbol_, code, value_, under_, count_, update, minimum, \ + { \ + value_ = code / minimum; \ + under_ = code - code%minimum; \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + (context) -> symbols = symbol_ - (context); \ + }, \ + exclude (node -> value, after, before), \ + { \ + value_ = node->value + 1 + (code - after)/minimum; \ + under_ = code - (code - after)%minimum; \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + node -> right = symbol_ - node; \ + }, \ + { \ + value_ = node->value - 1 - (after - before - code - 1)/minimum; \ + under_ = code - (after - before - code - 1)%minimum; \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + node -> left = symbol_ - node; \ + }) \ + +#ifdef ENET_CONTEXT_EXCLUSION +typedef struct _ENetExclude +{ + enet_uint8 value; + enet_uint16 under; +} ENetExclude; + +#define ENET_CONTEXT_DECODE_EXCLUDE(context, total, minimum) \ +{ \ + enet_uint16 under = 0; \ + nextExclude = excludes; \ + ENET_CONTEXT_WALK (context, { \ + under += rangeCoder -> symbols [node -> parent].count + minimum; \ + nextExclude -> value = node -> value; \ + nextExclude -> under = under; \ + nextExclude ++; \ + }); \ + total -= under; \ +} + +#define ENET_CONTEXT_EXCLUDED(value_, after, before) \ +{ \ + size_t low = 0, high = nextExclude - excludes; \ + for(;;) \ + { \ + size_t mid = (low + high) >> 1; \ + const ENetExclude * exclude = & excludes [mid]; \ + if (value_ < exclude -> value) \ + { \ + if (low + 1 < high) \ + { \ + high = mid; \ + continue; \ + } \ + if (exclude > excludes) \ + after -= exclude [-1].under; \ + } \ + else \ + { \ + if (value_ > exclude -> value) \ + { \ + if (low + 1 < high) \ + { \ + low = mid; \ + continue; \ + } \ + } \ + else \ + before = 0; \ + after -= exclude -> under; \ + } \ + break; \ + } \ +} +#endif + +#define ENET_CONTEXT_NOT_EXCLUDED(value_, after, before) + +size_t +enet_range_coder_decompress (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit) +{ + ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context; + enet_uint8 * outStart = outData, * outEnd = & outData [outLimit]; + const enet_uint8 * inEnd = & inData [inLimit]; + enet_uint32 decodeLow = 0, decodeCode = 0, decodeRange = ~0; + ENetSymbol * root; + enet_uint16 predicted = 0; + size_t order = 0, nextSymbol = 0; +#ifdef ENET_CONTEXT_EXCLUSION + ENetExclude excludes [256]; + ENetExclude * nextExclude = excludes; +#endif + + if (rangeCoder == NULL || inLimit <= 0) + return 0; + + ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); + + ENET_RANGE_CODER_SEED; + + for (;;) + { + ENetSymbol * subcontext, * symbol, * patch; +#ifdef ENET_CONTEXT_EXCLUSION + const ENetSymbol * childContext = & emptyContext; +#endif + enet_uint8 value = 0; + enet_uint16 code, under, count, bottom, * parent = & predicted, total; + + for (subcontext = & rangeCoder -> symbols [predicted]; + subcontext != root; +#ifdef ENET_CONTEXT_EXCLUSION + childContext = subcontext, +#endif + subcontext = & rangeCoder -> symbols [subcontext -> parent]) + { + if (subcontext -> escapes <= 0) + continue; + total = subcontext -> total; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > 0) + ENET_CONTEXT_DECODE_EXCLUDE (childContext, total, 0); +#endif + if (subcontext -> escapes >= total) + continue; + code = ENET_RANGE_CODER_READ (total); + if (code < subcontext -> escapes) + { + ENET_RANGE_CODER_DECODE (0, subcontext -> escapes, total); + continue; + } + code -= subcontext -> escapes; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > 0) + { + ENET_CONTEXT_TRY_DECODE (subcontext, symbol, code, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0, ENET_CONTEXT_EXCLUDED); + } + else +#endif + { + ENET_CONTEXT_TRY_DECODE (subcontext, symbol, code, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0, ENET_CONTEXT_NOT_EXCLUDED); + } + bottom = symbol - rangeCoder -> symbols; + ENET_RANGE_CODER_DECODE (subcontext -> escapes + under, count, total); + subcontext -> total += ENET_SUBCONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || subcontext -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (subcontext, 0); + goto patchContexts; + } + + total = root -> total; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > 0) + ENET_CONTEXT_DECODE_EXCLUDE (childContext, total, ENET_CONTEXT_SYMBOL_MINIMUM); +#endif + code = ENET_RANGE_CODER_READ (total); + if (code < root -> escapes) + { + ENET_RANGE_CODER_DECODE (0, root -> escapes, total); + break; + } + code -= root -> escapes; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > 0) + { + ENET_CONTEXT_ROOT_DECODE (root, symbol, code, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM, ENET_CONTEXT_EXCLUDED); + } + else +#endif + { + ENET_CONTEXT_ROOT_DECODE (root, symbol, code, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM, ENET_CONTEXT_NOT_EXCLUDED); + } + bottom = symbol - rangeCoder -> symbols; + ENET_RANGE_CODER_DECODE (root -> escapes + under, count, total); + root -> total += ENET_CONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_CONTEXT_SYMBOL_DELTA + ENET_CONTEXT_SYMBOL_MINIMUM || root -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (root, ENET_CONTEXT_SYMBOL_MINIMUM); + + patchContexts: + for (patch = & rangeCoder -> symbols [predicted]; + patch != subcontext; + patch = & rangeCoder -> symbols [patch -> parent]) + { + ENET_CONTEXT_ENCODE (patch, symbol, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0); + * parent = symbol - rangeCoder -> symbols; + parent = & symbol -> parent; + if (count <= 0) + { + patch -> escapes += ENET_SUBCONTEXT_ESCAPE_DELTA; + patch -> total += ENET_SUBCONTEXT_ESCAPE_DELTA; + } + patch -> total += ENET_SUBCONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || patch -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (patch, 0); + } + * parent = bottom; + + ENET_RANGE_CODER_OUTPUT (value); + + if (order >= ENET_SUBCONTEXT_ORDER) + predicted = rangeCoder -> symbols [predicted].parent; + else + order ++; + ENET_RANGE_CODER_FREE_SYMBOLS; + } + + return (size_t) (outData - outStart); +} + +/** @defgroup host ENet host functions + @{ +*/ + +/** Sets the packet compressor the host should use to the default range coder. + @param host host to enable the range coder for + @returns 0 on success, < 0 on failure +*/ +int +enet_host_compress_with_range_coder (ENetHost * host) +{ + ENetCompressor compressor; + memset (& compressor, 0, sizeof (compressor)); + compressor.context = enet_range_coder_create(); + if (compressor.context == NULL) + return -1; + compressor.compress = enet_range_coder_compress; + compressor.decompress = enet_range_coder_decompress; + compressor.destroy = enet_range_coder_destroy; + enet_host_compress (host, & compressor); + return 0; +} + +/** @} */ + + diff --git a/modules/enet/config.py b/modules/enet/config.py new file mode 100644 index 0000000000..ea7e83378a --- /dev/null +++ b/modules/enet/config.py @@ -0,0 +1,11 @@ + + +def can_build(platform): + return True + + +def configure(env): + pass + + + diff --git a/modules/enet/enet/callbacks.h b/modules/enet/enet/callbacks.h new file mode 100644 index 0000000000..340a4a9896 --- /dev/null +++ b/modules/enet/enet/callbacks.h @@ -0,0 +1,27 @@ +/** + @file callbacks.h + @brief ENet callbacks +*/ +#ifndef __ENET_CALLBACKS_H__ +#define __ENET_CALLBACKS_H__ + +#include <stdlib.h> + +typedef struct _ENetCallbacks +{ + void * (ENET_CALLBACK * malloc) (size_t size); + void (ENET_CALLBACK * free) (void * memory); + void (ENET_CALLBACK * no_memory) (void); +} ENetCallbacks; + +/** @defgroup callbacks ENet internal callbacks + @{ + @ingroup private +*/ +extern void * enet_malloc (size_t); +extern void enet_free (void *); + +/** @} */ + +#endif /* __ENET_CALLBACKS_H__ */ + diff --git a/modules/enet/enet/enet.h b/modules/enet/enet/enet.h new file mode 100644 index 0000000000..650b199ee5 --- /dev/null +++ b/modules/enet/enet/enet.h @@ -0,0 +1,596 @@ +/** + @file enet.h + @brief ENet public header file +*/ +#ifndef __ENET_ENET_H__ +#define __ENET_ENET_H__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include <stdlib.h> + +#ifdef _WIN32 +#include "enet/win32.h" +#else +#include "enet/unix.h" +#endif + +#include "enet/types.h" +#include "enet/protocol.h" +#include "enet/list.h" +#include "enet/callbacks.h" + +#define ENET_VERSION_MAJOR 1 +#define ENET_VERSION_MINOR 3 +#define ENET_VERSION_PATCH 13 +#define ENET_VERSION_CREATE(major, minor, patch) (((major)<<16) | ((minor)<<8) | (patch)) +#define ENET_VERSION_GET_MAJOR(version) (((version)>>16)&0xFF) +#define ENET_VERSION_GET_MINOR(version) (((version)>>8)&0xFF) +#define ENET_VERSION_GET_PATCH(version) ((version)&0xFF) +#define ENET_VERSION ENET_VERSION_CREATE(ENET_VERSION_MAJOR, ENET_VERSION_MINOR, ENET_VERSION_PATCH) + +typedef enet_uint32 ENetVersion; + +struct _ENetHost; +struct _ENetEvent; +struct _ENetPacket; + +typedef enum _ENetSocketType +{ + ENET_SOCKET_TYPE_STREAM = 1, + ENET_SOCKET_TYPE_DATAGRAM = 2 +} ENetSocketType; + +typedef enum _ENetSocketWait +{ + ENET_SOCKET_WAIT_NONE = 0, + ENET_SOCKET_WAIT_SEND = (1 << 0), + ENET_SOCKET_WAIT_RECEIVE = (1 << 1), + ENET_SOCKET_WAIT_INTERRUPT = (1 << 2) +} ENetSocketWait; + +typedef enum _ENetSocketOption +{ + ENET_SOCKOPT_NONBLOCK = 1, + ENET_SOCKOPT_BROADCAST = 2, + ENET_SOCKOPT_RCVBUF = 3, + ENET_SOCKOPT_SNDBUF = 4, + ENET_SOCKOPT_REUSEADDR = 5, + ENET_SOCKOPT_RCVTIMEO = 6, + ENET_SOCKOPT_SNDTIMEO = 7, + ENET_SOCKOPT_ERROR = 8, + ENET_SOCKOPT_NODELAY = 9 +} ENetSocketOption; + +typedef enum _ENetSocketShutdown +{ + ENET_SOCKET_SHUTDOWN_READ = 0, + ENET_SOCKET_SHUTDOWN_WRITE = 1, + ENET_SOCKET_SHUTDOWN_READ_WRITE = 2 +} ENetSocketShutdown; + +#define ENET_HOST_ANY 0 +#define ENET_HOST_BROADCAST 0xFFFFFFFFU +#define ENET_PORT_ANY 0 + +/** + * Portable internet address structure. + * + * The host must be specified in network byte-order, and the port must be in host + * byte-order. The constant ENET_HOST_ANY may be used to specify the default + * server host. The constant ENET_HOST_BROADCAST may be used to specify the + * broadcast address (255.255.255.255). This makes sense for enet_host_connect, + * but not for enet_host_create. Once a server responds to a broadcast, the + * address is updated from ENET_HOST_BROADCAST to the server's actual IP address. + */ +typedef struct _ENetAddress +{ + enet_uint32 host; + enet_uint16 port; +} ENetAddress; + +/** + * Packet flag bit constants. + * + * The host must be specified in network byte-order, and the port must be in + * host byte-order. The constant ENET_HOST_ANY may be used to specify the + * default server host. + + @sa ENetPacket +*/ +typedef enum _ENetPacketFlag +{ + /** packet must be received by the target peer and resend attempts should be + * made until the packet is delivered */ + ENET_PACKET_FLAG_RELIABLE = (1 << 0), + /** packet will not be sequenced with other packets + * not supported for reliable packets + */ + ENET_PACKET_FLAG_UNSEQUENCED = (1 << 1), + /** packet will not allocate data, and user must supply it instead */ + ENET_PACKET_FLAG_NO_ALLOCATE = (1 << 2), + /** packet will be fragmented using unreliable (instead of reliable) sends + * if it exceeds the MTU */ + ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT = (1 << 3), + + /** whether the packet has been sent from all queues it has been entered into */ + ENET_PACKET_FLAG_SENT = (1<<8) +} ENetPacketFlag; + +typedef void (ENET_CALLBACK * ENetPacketFreeCallback) (struct _ENetPacket *); + +/** + * ENet packet structure. + * + * An ENet data packet that may be sent to or received from a peer. The shown + * fields should only be read and never modified. The data field contains the + * allocated data for the packet. The dataLength fields specifies the length + * of the allocated data. The flags field is either 0 (specifying no flags), + * or a bitwise-or of any combination of the following flags: + * + * ENET_PACKET_FLAG_RELIABLE - packet must be received by the target peer + * and resend attempts should be made until the packet is delivered + * + * ENET_PACKET_FLAG_UNSEQUENCED - packet will not be sequenced with other packets + * (not supported for reliable packets) + * + * ENET_PACKET_FLAG_NO_ALLOCATE - packet will not allocate data, and user must supply it instead + * + * ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT - packet will be fragmented using unreliable + * (instead of reliable) sends if it exceeds the MTU + * + * ENET_PACKET_FLAG_SENT - whether the packet has been sent from all queues it has been entered into + @sa ENetPacketFlag + */ +typedef struct _ENetPacket +{ + size_t referenceCount; /**< internal use only */ + enet_uint32 flags; /**< bitwise-or of ENetPacketFlag constants */ + enet_uint8 * data; /**< allocated data for packet */ + size_t dataLength; /**< length of data */ + ENetPacketFreeCallback freeCallback; /**< function to be called when the packet is no longer in use */ + void * userData; /**< application private data, may be freely modified */ +} ENetPacket; + +typedef struct _ENetAcknowledgement +{ + ENetListNode acknowledgementList; + enet_uint32 sentTime; + ENetProtocol command; +} ENetAcknowledgement; + +typedef struct _ENetOutgoingCommand +{ + ENetListNode outgoingCommandList; + enet_uint16 reliableSequenceNumber; + enet_uint16 unreliableSequenceNumber; + enet_uint32 sentTime; + enet_uint32 roundTripTimeout; + enet_uint32 roundTripTimeoutLimit; + enet_uint32 fragmentOffset; + enet_uint16 fragmentLength; + enet_uint16 sendAttempts; + ENetProtocol command; + ENetPacket * packet; +} ENetOutgoingCommand; + +typedef struct _ENetIncomingCommand +{ + ENetListNode incomingCommandList; + enet_uint16 reliableSequenceNumber; + enet_uint16 unreliableSequenceNumber; + ENetProtocol command; + enet_uint32 fragmentCount; + enet_uint32 fragmentsRemaining; + enet_uint32 * fragments; + ENetPacket * packet; +} ENetIncomingCommand; + +typedef enum _ENetPeerState +{ + ENET_PEER_STATE_DISCONNECTED = 0, + ENET_PEER_STATE_CONNECTING = 1, + ENET_PEER_STATE_ACKNOWLEDGING_CONNECT = 2, + ENET_PEER_STATE_CONNECTION_PENDING = 3, + ENET_PEER_STATE_CONNECTION_SUCCEEDED = 4, + ENET_PEER_STATE_CONNECTED = 5, + ENET_PEER_STATE_DISCONNECT_LATER = 6, + ENET_PEER_STATE_DISCONNECTING = 7, + ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT = 8, + ENET_PEER_STATE_ZOMBIE = 9 +} ENetPeerState; + +#ifndef ENET_BUFFER_MAXIMUM +#define ENET_BUFFER_MAXIMUM (1 + 2 * ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS) +#endif + +enum +{ + ENET_HOST_RECEIVE_BUFFER_SIZE = 256 * 1024, + ENET_HOST_SEND_BUFFER_SIZE = 256 * 1024, + ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL = 1000, + ENET_HOST_DEFAULT_MTU = 1400, + ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE = 32 * 1024 * 1024, + ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA = 32 * 1024 * 1024, + + ENET_PEER_DEFAULT_ROUND_TRIP_TIME = 500, + ENET_PEER_DEFAULT_PACKET_THROTTLE = 32, + ENET_PEER_PACKET_THROTTLE_SCALE = 32, + ENET_PEER_PACKET_THROTTLE_COUNTER = 7, + ENET_PEER_PACKET_THROTTLE_ACCELERATION = 2, + ENET_PEER_PACKET_THROTTLE_DECELERATION = 2, + ENET_PEER_PACKET_THROTTLE_INTERVAL = 5000, + ENET_PEER_PACKET_LOSS_SCALE = (1 << 16), + ENET_PEER_PACKET_LOSS_INTERVAL = 10000, + ENET_PEER_WINDOW_SIZE_SCALE = 64 * 1024, + ENET_PEER_TIMEOUT_LIMIT = 32, + ENET_PEER_TIMEOUT_MINIMUM = 5000, + ENET_PEER_TIMEOUT_MAXIMUM = 30000, + ENET_PEER_PING_INTERVAL = 500, + ENET_PEER_UNSEQUENCED_WINDOWS = 64, + ENET_PEER_UNSEQUENCED_WINDOW_SIZE = 1024, + ENET_PEER_FREE_UNSEQUENCED_WINDOWS = 32, + ENET_PEER_RELIABLE_WINDOWS = 16, + ENET_PEER_RELIABLE_WINDOW_SIZE = 0x1000, + ENET_PEER_FREE_RELIABLE_WINDOWS = 8 +}; + +typedef struct _ENetChannel +{ + enet_uint16 outgoingReliableSequenceNumber; + enet_uint16 outgoingUnreliableSequenceNumber; + enet_uint16 usedReliableWindows; + enet_uint16 reliableWindows [ENET_PEER_RELIABLE_WINDOWS]; + enet_uint16 incomingReliableSequenceNumber; + enet_uint16 incomingUnreliableSequenceNumber; + ENetList incomingReliableCommands; + ENetList incomingUnreliableCommands; +} ENetChannel; + +/** + * An ENet peer which data packets may be sent or received from. + * + * No fields should be modified unless otherwise specified. + */ +typedef struct _ENetPeer +{ + ENetListNode dispatchList; + struct _ENetHost * host; + enet_uint16 outgoingPeerID; + enet_uint16 incomingPeerID; + enet_uint32 connectID; + enet_uint8 outgoingSessionID; + enet_uint8 incomingSessionID; + ENetAddress address; /**< Internet address of the peer */ + void * data; /**< Application private data, may be freely modified */ + ENetPeerState state; + ENetChannel * channels; + size_t channelCount; /**< Number of channels allocated for communication with peer */ + enet_uint32 incomingBandwidth; /**< Downstream bandwidth of the client in bytes/second */ + enet_uint32 outgoingBandwidth; /**< Upstream bandwidth of the client in bytes/second */ + enet_uint32 incomingBandwidthThrottleEpoch; + enet_uint32 outgoingBandwidthThrottleEpoch; + enet_uint32 incomingDataTotal; + enet_uint32 outgoingDataTotal; + enet_uint32 lastSendTime; + enet_uint32 lastReceiveTime; + enet_uint32 nextTimeout; + enet_uint32 earliestTimeout; + enet_uint32 packetLossEpoch; + enet_uint32 packetsSent; + enet_uint32 packetsLost; + enet_uint32 packetLoss; /**< mean packet loss of reliable packets as a ratio with respect to the constant ENET_PEER_PACKET_LOSS_SCALE */ + enet_uint32 packetLossVariance; + enet_uint32 packetThrottle; + enet_uint32 packetThrottleLimit; + enet_uint32 packetThrottleCounter; + enet_uint32 packetThrottleEpoch; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; + enet_uint32 packetThrottleInterval; + enet_uint32 pingInterval; + enet_uint32 timeoutLimit; + enet_uint32 timeoutMinimum; + enet_uint32 timeoutMaximum; + enet_uint32 lastRoundTripTime; + enet_uint32 lowestRoundTripTime; + enet_uint32 lastRoundTripTimeVariance; + enet_uint32 highestRoundTripTimeVariance; + enet_uint32 roundTripTime; /**< mean round trip time (RTT), in milliseconds, between sending a reliable packet and receiving its acknowledgement */ + enet_uint32 roundTripTimeVariance; + enet_uint32 mtu; + enet_uint32 windowSize; + enet_uint32 reliableDataInTransit; + enet_uint16 outgoingReliableSequenceNumber; + ENetList acknowledgements; + ENetList sentReliableCommands; + ENetList sentUnreliableCommands; + ENetList outgoingReliableCommands; + ENetList outgoingUnreliableCommands; + ENetList dispatchedCommands; + int needsDispatch; + enet_uint16 incomingUnsequencedGroup; + enet_uint16 outgoingUnsequencedGroup; + enet_uint32 unsequencedWindow [ENET_PEER_UNSEQUENCED_WINDOW_SIZE / 32]; + enet_uint32 eventData; + size_t totalWaitingData; +} ENetPeer; + +/** An ENet packet compressor for compressing UDP packets before socket sends or receives. + */ +typedef struct _ENetCompressor +{ + /** Context data for the compressor. Must be non-NULL. */ + void * context; + /** Compresses from inBuffers[0:inBufferCount-1], containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */ + size_t (ENET_CALLBACK * compress) (void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit); + /** Decompresses from inData, containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */ + size_t (ENET_CALLBACK * decompress) (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit); + /** Destroys the context when compression is disabled or the host is destroyed. May be NULL. */ + void (ENET_CALLBACK * destroy) (void * context); +} ENetCompressor; + +/** Callback that computes the checksum of the data held in buffers[0:bufferCount-1] */ +typedef enet_uint32 (ENET_CALLBACK * ENetChecksumCallback) (const ENetBuffer * buffers, size_t bufferCount); + +/** Callback for intercepting received raw UDP packets. Should return 1 to intercept, 0 to ignore, or -1 to propagate an error. */ +typedef int (ENET_CALLBACK * ENetInterceptCallback) (struct _ENetHost * host, struct _ENetEvent * event); + +/** An ENet host for communicating with peers. + * + * No fields should be modified unless otherwise stated. + + @sa enet_host_create() + @sa enet_host_destroy() + @sa enet_host_connect() + @sa enet_host_service() + @sa enet_host_flush() + @sa enet_host_broadcast() + @sa enet_host_compress() + @sa enet_host_compress_with_range_coder() + @sa enet_host_channel_limit() + @sa enet_host_bandwidth_limit() + @sa enet_host_bandwidth_throttle() + */ +typedef struct _ENetHost +{ + ENetSocket socket; + ENetAddress address; /**< Internet address of the host */ + enet_uint32 incomingBandwidth; /**< downstream bandwidth of the host */ + enet_uint32 outgoingBandwidth; /**< upstream bandwidth of the host */ + enet_uint32 bandwidthThrottleEpoch; + enet_uint32 mtu; + enet_uint32 randomSeed; + int recalculateBandwidthLimits; + ENetPeer * peers; /**< array of peers allocated for this host */ + size_t peerCount; /**< number of peers allocated for this host */ + size_t channelLimit; /**< maximum number of channels allowed for connected peers */ + enet_uint32 serviceTime; + ENetList dispatchQueue; + int continueSending; + size_t packetSize; + enet_uint16 headerFlags; + ENetProtocol commands [ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS]; + size_t commandCount; + ENetBuffer buffers [ENET_BUFFER_MAXIMUM]; + size_t bufferCount; + ENetChecksumCallback checksum; /**< callback the user can set to enable packet checksums for this host */ + ENetCompressor compressor; + enet_uint8 packetData [2][ENET_PROTOCOL_MAXIMUM_MTU]; + ENetAddress receivedAddress; + enet_uint8 * receivedData; + size_t receivedDataLength; + enet_uint32 totalSentData; /**< total data sent, user should reset to 0 as needed to prevent overflow */ + enet_uint32 totalSentPackets; /**< total UDP packets sent, user should reset to 0 as needed to prevent overflow */ + enet_uint32 totalReceivedData; /**< total data received, user should reset to 0 as needed to prevent overflow */ + enet_uint32 totalReceivedPackets; /**< total UDP packets received, user should reset to 0 as needed to prevent overflow */ + ENetInterceptCallback intercept; /**< callback the user can set to intercept received raw UDP packets */ + size_t connectedPeers; + size_t bandwidthLimitedPeers; + size_t duplicatePeers; /**< optional number of allowed peers from duplicate IPs, defaults to ENET_PROTOCOL_MAXIMUM_PEER_ID */ + size_t maximumPacketSize; /**< the maximum allowable packet size that may be sent or received on a peer */ + size_t maximumWaitingData; /**< the maximum aggregate amount of buffer space a peer may use waiting for packets to be delivered */ +} ENetHost; + +/** + * An ENet event type, as specified in @ref ENetEvent. + */ +typedef enum _ENetEventType +{ + /** no event occurred within the specified time limit */ + ENET_EVENT_TYPE_NONE = 0, + + /** a connection request initiated by enet_host_connect has completed. + * The peer field contains the peer which successfully connected. + */ + ENET_EVENT_TYPE_CONNECT = 1, + + /** a peer has disconnected. This event is generated on a successful + * completion of a disconnect initiated by enet_peer_disconnect, if + * a peer has timed out, or if a connection request intialized by + * enet_host_connect has timed out. The peer field contains the peer + * which disconnected. The data field contains user supplied data + * describing the disconnection, or 0, if none is available. + */ + ENET_EVENT_TYPE_DISCONNECT = 2, + + /** a packet has been received from a peer. The peer field specifies the + * peer which sent the packet. The channelID field specifies the channel + * number upon which the packet was received. The packet field contains + * the packet that was received; this packet must be destroyed with + * enet_packet_destroy after use. + */ + ENET_EVENT_TYPE_RECEIVE = 3 +} ENetEventType; + +/** + * An ENet event as returned by enet_host_service(). + + @sa enet_host_service + */ +typedef struct _ENetEvent +{ + ENetEventType type; /**< type of the event */ + ENetPeer * peer; /**< peer that generated a connect, disconnect or receive event */ + enet_uint8 channelID; /**< channel on the peer that generated the event, if appropriate */ + enet_uint32 data; /**< data associated with the event, if appropriate */ + ENetPacket * packet; /**< packet associated with the event, if appropriate */ +} ENetEvent; + +/** @defgroup global ENet global functions + @{ +*/ + +/** + Initializes ENet globally. Must be called prior to using any functions in + ENet. + @returns 0 on success, < 0 on failure +*/ +ENET_API int enet_initialize (void); + +/** + Initializes ENet globally and supplies user-overridden callbacks. Must be called prior to using any functions in ENet. Do not use enet_initialize() if you use this variant. Make sure the ENetCallbacks structure is zeroed out so that any additional callbacks added in future versions will be properly ignored. + + @param version the constant ENET_VERSION should be supplied so ENet knows which version of ENetCallbacks struct to use + @param inits user-overridden callbacks where any NULL callbacks will use ENet's defaults + @returns 0 on success, < 0 on failure +*/ +ENET_API int enet_initialize_with_callbacks (ENetVersion version, const ENetCallbacks * inits); + +/** + Shuts down ENet globally. Should be called when a program that has + initialized ENet exits. +*/ +ENET_API void enet_deinitialize (void); + +/** + Gives the linked version of the ENet library. + @returns the version number +*/ +ENET_API ENetVersion enet_linked_version (void); + +/** @} */ + +/** @defgroup private ENet private implementation functions */ + +/** + Returns the wall-time in milliseconds. Its initial value is unspecified + unless otherwise set. + */ +ENET_API enet_uint32 enet_time_get (void); +/** + Sets the current wall-time in milliseconds. + */ +ENET_API void enet_time_set (enet_uint32); + +/** @defgroup socket ENet socket functions + @{ +*/ +ENET_API ENetSocket enet_socket_create (ENetSocketType); +ENET_API int enet_socket_bind (ENetSocket, const ENetAddress *); +ENET_API int enet_socket_get_address (ENetSocket, ENetAddress *); +ENET_API int enet_socket_listen (ENetSocket, int); +ENET_API ENetSocket enet_socket_accept (ENetSocket, ENetAddress *); +ENET_API int enet_socket_connect (ENetSocket, const ENetAddress *); +ENET_API int enet_socket_send (ENetSocket, const ENetAddress *, const ENetBuffer *, size_t); +ENET_API int enet_socket_receive (ENetSocket, ENetAddress *, ENetBuffer *, size_t); +ENET_API int enet_socket_wait (ENetSocket, enet_uint32 *, enet_uint32); +ENET_API int enet_socket_set_option (ENetSocket, ENetSocketOption, int); +ENET_API int enet_socket_get_option (ENetSocket, ENetSocketOption, int *); +ENET_API int enet_socket_shutdown (ENetSocket, ENetSocketShutdown); +ENET_API void enet_socket_destroy (ENetSocket); +ENET_API int enet_socketset_select (ENetSocket, ENetSocketSet *, ENetSocketSet *, enet_uint32); + +/** @} */ + +/** @defgroup Address ENet address functions + @{ +*/ +/** Attempts to resolve the host named by the parameter hostName and sets + the host field in the address parameter if successful. + @param address destination to store resolved address + @param hostName host name to lookup + @retval 0 on success + @retval < 0 on failure + @returns the address of the given hostName in address on success +*/ +ENET_API int enet_address_set_host (ENetAddress * address, const char * hostName); + +/** Gives the printable form of the IP address specified in the address parameter. + @param address address printed + @param hostName destination for name, must not be NULL + @param nameLength maximum length of hostName. + @returns the null-terminated name of the host in hostName on success + @retval 0 on success + @retval < 0 on failure +*/ +ENET_API int enet_address_get_host_ip (const ENetAddress * address, char * hostName, size_t nameLength); + +/** Attempts to do a reverse lookup of the host field in the address parameter. + @param address address used for reverse lookup + @param hostName destination for name, must not be NULL + @param nameLength maximum length of hostName. + @returns the null-terminated name of the host in hostName on success + @retval 0 on success + @retval < 0 on failure +*/ +ENET_API int enet_address_get_host (const ENetAddress * address, char * hostName, size_t nameLength); + +/** @} */ + +ENET_API ENetPacket * enet_packet_create (const void *, size_t, enet_uint32); +ENET_API void enet_packet_destroy (ENetPacket *); +ENET_API int enet_packet_resize (ENetPacket *, size_t); +ENET_API enet_uint32 enet_crc32 (const ENetBuffer *, size_t); + +ENET_API ENetHost * enet_host_create (const ENetAddress *, size_t, size_t, enet_uint32, enet_uint32); +ENET_API void enet_host_destroy (ENetHost *); +ENET_API ENetPeer * enet_host_connect (ENetHost *, const ENetAddress *, size_t, enet_uint32); +ENET_API int enet_host_check_events (ENetHost *, ENetEvent *); +ENET_API int enet_host_service (ENetHost *, ENetEvent *, enet_uint32); +ENET_API void enet_host_flush (ENetHost *); +ENET_API void enet_host_broadcast (ENetHost *, enet_uint8, ENetPacket *); +ENET_API void enet_host_compress (ENetHost *, const ENetCompressor *); +ENET_API int enet_host_compress_with_range_coder (ENetHost * host); +ENET_API void enet_host_channel_limit (ENetHost *, size_t); +ENET_API void enet_host_bandwidth_limit (ENetHost *, enet_uint32, enet_uint32); +extern void enet_host_bandwidth_throttle (ENetHost *); +extern enet_uint32 enet_host_random_seed (void); + +ENET_API int enet_peer_send (ENetPeer *, enet_uint8, ENetPacket *); +ENET_API ENetPacket * enet_peer_receive (ENetPeer *, enet_uint8 * channelID); +ENET_API void enet_peer_ping (ENetPeer *); +ENET_API void enet_peer_ping_interval (ENetPeer *, enet_uint32); +ENET_API void enet_peer_timeout (ENetPeer *, enet_uint32, enet_uint32, enet_uint32); +ENET_API void enet_peer_reset (ENetPeer *); +ENET_API void enet_peer_disconnect (ENetPeer *, enet_uint32); +ENET_API void enet_peer_disconnect_now (ENetPeer *, enet_uint32); +ENET_API void enet_peer_disconnect_later (ENetPeer *, enet_uint32); +ENET_API void enet_peer_throttle_configure (ENetPeer *, enet_uint32, enet_uint32, enet_uint32); +extern int enet_peer_throttle (ENetPeer *, enet_uint32); +extern void enet_peer_reset_queues (ENetPeer *); +extern void enet_peer_setup_outgoing_command (ENetPeer *, ENetOutgoingCommand *); +extern ENetOutgoingCommand * enet_peer_queue_outgoing_command (ENetPeer *, const ENetProtocol *, ENetPacket *, enet_uint32, enet_uint16); +extern ENetIncomingCommand * enet_peer_queue_incoming_command (ENetPeer *, const ENetProtocol *, const void *, size_t, enet_uint32, enet_uint32); +extern ENetAcknowledgement * enet_peer_queue_acknowledgement (ENetPeer *, const ENetProtocol *, enet_uint16); +extern void enet_peer_dispatch_incoming_unreliable_commands (ENetPeer *, ENetChannel *); +extern void enet_peer_dispatch_incoming_reliable_commands (ENetPeer *, ENetChannel *); +extern void enet_peer_on_connect (ENetPeer *); +extern void enet_peer_on_disconnect (ENetPeer *); + +ENET_API void * enet_range_coder_create (void); +ENET_API void enet_range_coder_destroy (void *); +ENET_API size_t enet_range_coder_compress (void *, const ENetBuffer *, size_t, size_t, enet_uint8 *, size_t); +ENET_API size_t enet_range_coder_decompress (void *, const enet_uint8 *, size_t, enet_uint8 *, size_t); + +extern size_t enet_protocol_command_size (enet_uint8); + +#ifdef __cplusplus +} +#endif + +#endif /* __ENET_ENET_H__ */ + diff --git a/modules/enet/enet/list.h b/modules/enet/enet/list.h new file mode 100644 index 0000000000..d7b2600848 --- /dev/null +++ b/modules/enet/enet/list.h @@ -0,0 +1,43 @@ +/** + @file list.h + @brief ENet list management +*/ +#ifndef __ENET_LIST_H__ +#define __ENET_LIST_H__ + +#include <stdlib.h> + +typedef struct _ENetListNode +{ + struct _ENetListNode * next; + struct _ENetListNode * previous; +} ENetListNode; + +typedef ENetListNode * ENetListIterator; + +typedef struct _ENetList +{ + ENetListNode sentinel; +} ENetList; + +extern void enet_list_clear (ENetList *); + +extern ENetListIterator enet_list_insert (ENetListIterator, void *); +extern void * enet_list_remove (ENetListIterator); +extern ENetListIterator enet_list_move (ENetListIterator, void *, void *); + +extern size_t enet_list_size (ENetList *); + +#define enet_list_begin(list) ((list) -> sentinel.next) +#define enet_list_end(list) (& (list) -> sentinel) + +#define enet_list_empty(list) (enet_list_begin (list) == enet_list_end (list)) + +#define enet_list_next(iterator) ((iterator) -> next) +#define enet_list_previous(iterator) ((iterator) -> previous) + +#define enet_list_front(list) ((void *) (list) -> sentinel.next) +#define enet_list_back(list) ((void *) (list) -> sentinel.previous) + +#endif /* __ENET_LIST_H__ */ + diff --git a/modules/enet/enet/protocol.h b/modules/enet/enet/protocol.h new file mode 100644 index 0000000000..f8c73d8a66 --- /dev/null +++ b/modules/enet/enet/protocol.h @@ -0,0 +1,198 @@ +/** + @file protocol.h + @brief ENet protocol +*/ +#ifndef __ENET_PROTOCOL_H__ +#define __ENET_PROTOCOL_H__ + +#include "enet/types.h" + +enum +{ + ENET_PROTOCOL_MINIMUM_MTU = 576, + ENET_PROTOCOL_MAXIMUM_MTU = 4096, + ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS = 32, + ENET_PROTOCOL_MINIMUM_WINDOW_SIZE = 4096, + ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE = 65536, + ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT = 1, + ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT = 255, + ENET_PROTOCOL_MAXIMUM_PEER_ID = 0xFFF, + ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT = 1024 * 1024 +}; + +typedef enum _ENetProtocolCommand +{ + ENET_PROTOCOL_COMMAND_NONE = 0, + ENET_PROTOCOL_COMMAND_ACKNOWLEDGE = 1, + ENET_PROTOCOL_COMMAND_CONNECT = 2, + ENET_PROTOCOL_COMMAND_VERIFY_CONNECT = 3, + ENET_PROTOCOL_COMMAND_DISCONNECT = 4, + ENET_PROTOCOL_COMMAND_PING = 5, + ENET_PROTOCOL_COMMAND_SEND_RELIABLE = 6, + ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE = 7, + ENET_PROTOCOL_COMMAND_SEND_FRAGMENT = 8, + ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED = 9, + ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT = 10, + ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE = 11, + ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT = 12, + ENET_PROTOCOL_COMMAND_COUNT = 13, + + ENET_PROTOCOL_COMMAND_MASK = 0x0F +} ENetProtocolCommand; + +typedef enum _ENetProtocolFlag +{ + ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE = (1 << 7), + ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED = (1 << 6), + + ENET_PROTOCOL_HEADER_FLAG_COMPRESSED = (1 << 14), + ENET_PROTOCOL_HEADER_FLAG_SENT_TIME = (1 << 15), + ENET_PROTOCOL_HEADER_FLAG_MASK = ENET_PROTOCOL_HEADER_FLAG_COMPRESSED | ENET_PROTOCOL_HEADER_FLAG_SENT_TIME, + + ENET_PROTOCOL_HEADER_SESSION_MASK = (3 << 12), + ENET_PROTOCOL_HEADER_SESSION_SHIFT = 12 +} ENetProtocolFlag; + +#ifdef _MSC_VER +#pragma pack(push, 1) +#define ENET_PACKED +#elif defined(__GNUC__) || defined(__clang__) +#define ENET_PACKED __attribute__ ((packed)) +#else +#define ENET_PACKED +#endif + +typedef struct _ENetProtocolHeader +{ + enet_uint16 peerID; + enet_uint16 sentTime; +} ENET_PACKED ENetProtocolHeader; + +typedef struct _ENetProtocolCommandHeader +{ + enet_uint8 command; + enet_uint8 channelID; + enet_uint16 reliableSequenceNumber; +} ENET_PACKED ENetProtocolCommandHeader; + +typedef struct _ENetProtocolAcknowledge +{ + ENetProtocolCommandHeader header; + enet_uint16 receivedReliableSequenceNumber; + enet_uint16 receivedSentTime; +} ENET_PACKED ENetProtocolAcknowledge; + +typedef struct _ENetProtocolConnect +{ + ENetProtocolCommandHeader header; + enet_uint16 outgoingPeerID; + enet_uint8 incomingSessionID; + enet_uint8 outgoingSessionID; + enet_uint32 mtu; + enet_uint32 windowSize; + enet_uint32 channelCount; + enet_uint32 incomingBandwidth; + enet_uint32 outgoingBandwidth; + enet_uint32 packetThrottleInterval; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; + enet_uint32 connectID; + enet_uint32 data; +} ENET_PACKED ENetProtocolConnect; + +typedef struct _ENetProtocolVerifyConnect +{ + ENetProtocolCommandHeader header; + enet_uint16 outgoingPeerID; + enet_uint8 incomingSessionID; + enet_uint8 outgoingSessionID; + enet_uint32 mtu; + enet_uint32 windowSize; + enet_uint32 channelCount; + enet_uint32 incomingBandwidth; + enet_uint32 outgoingBandwidth; + enet_uint32 packetThrottleInterval; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; + enet_uint32 connectID; +} ENET_PACKED ENetProtocolVerifyConnect; + +typedef struct _ENetProtocolBandwidthLimit +{ + ENetProtocolCommandHeader header; + enet_uint32 incomingBandwidth; + enet_uint32 outgoingBandwidth; +} ENET_PACKED ENetProtocolBandwidthLimit; + +typedef struct _ENetProtocolThrottleConfigure +{ + ENetProtocolCommandHeader header; + enet_uint32 packetThrottleInterval; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; +} ENET_PACKED ENetProtocolThrottleConfigure; + +typedef struct _ENetProtocolDisconnect +{ + ENetProtocolCommandHeader header; + enet_uint32 data; +} ENET_PACKED ENetProtocolDisconnect; + +typedef struct _ENetProtocolPing +{ + ENetProtocolCommandHeader header; +} ENET_PACKED ENetProtocolPing; + +typedef struct _ENetProtocolSendReliable +{ + ENetProtocolCommandHeader header; + enet_uint16 dataLength; +} ENET_PACKED ENetProtocolSendReliable; + +typedef struct _ENetProtocolSendUnreliable +{ + ENetProtocolCommandHeader header; + enet_uint16 unreliableSequenceNumber; + enet_uint16 dataLength; +} ENET_PACKED ENetProtocolSendUnreliable; + +typedef struct _ENetProtocolSendUnsequenced +{ + ENetProtocolCommandHeader header; + enet_uint16 unsequencedGroup; + enet_uint16 dataLength; +} ENET_PACKED ENetProtocolSendUnsequenced; + +typedef struct _ENetProtocolSendFragment +{ + ENetProtocolCommandHeader header; + enet_uint16 startSequenceNumber; + enet_uint16 dataLength; + enet_uint32 fragmentCount; + enet_uint32 fragmentNumber; + enet_uint32 totalLength; + enet_uint32 fragmentOffset; +} ENET_PACKED ENetProtocolSendFragment; + +typedef union _ENetProtocol +{ + ENetProtocolCommandHeader header; + ENetProtocolAcknowledge acknowledge; + ENetProtocolConnect connect; + ENetProtocolVerifyConnect verifyConnect; + ENetProtocolDisconnect disconnect; + ENetProtocolPing ping; + ENetProtocolSendReliable sendReliable; + ENetProtocolSendUnreliable sendUnreliable; + ENetProtocolSendUnsequenced sendUnsequenced; + ENetProtocolSendFragment sendFragment; + ENetProtocolBandwidthLimit bandwidthLimit; + ENetProtocolThrottleConfigure throttleConfigure; +} ENET_PACKED ENetProtocol; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif + +#endif /* __ENET_PROTOCOL_H__ */ + diff --git a/modules/enet/enet/time.h b/modules/enet/enet/time.h new file mode 100644 index 0000000000..c82a546035 --- /dev/null +++ b/modules/enet/enet/time.h @@ -0,0 +1,18 @@ +/** + @file time.h + @brief ENet time constants and macros +*/ +#ifndef __ENET_TIME_H__ +#define __ENET_TIME_H__ + +#define ENET_TIME_OVERFLOW 86400000 + +#define ENET_TIME_LESS(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW) +#define ENET_TIME_GREATER(a, b) ((b) - (a) >= ENET_TIME_OVERFLOW) +#define ENET_TIME_LESS_EQUAL(a, b) (! ENET_TIME_GREATER (a, b)) +#define ENET_TIME_GREATER_EQUAL(a, b) (! ENET_TIME_LESS (a, b)) + +#define ENET_TIME_DIFFERENCE(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW ? (b) - (a) : (a) - (b)) + +#endif /* __ENET_TIME_H__ */ + diff --git a/modules/enet/enet/types.h b/modules/enet/enet/types.h new file mode 100644 index 0000000000..ab010a4b13 --- /dev/null +++ b/modules/enet/enet/types.h @@ -0,0 +1,13 @@ +/** + @file types.h + @brief type definitions for ENet +*/ +#ifndef __ENET_TYPES_H__ +#define __ENET_TYPES_H__ + +typedef unsigned char enet_uint8; /**< unsigned 8-bit type */ +typedef unsigned short enet_uint16; /**< unsigned 16-bit type */ +typedef unsigned int enet_uint32; /**< unsigned 32-bit type */ + +#endif /* __ENET_TYPES_H__ */ + diff --git a/modules/enet/enet/unix.h b/modules/enet/enet/unix.h new file mode 100644 index 0000000000..a59e340606 --- /dev/null +++ b/modules/enet/enet/unix.h @@ -0,0 +1,47 @@ +/** + @file unix.h + @brief ENet Unix header +*/ +#ifndef __ENET_UNIX_H__ +#define __ENET_UNIX_H__ + +#include <stdlib.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <unistd.h> + +#ifdef MSG_MAXIOVLEN +#define ENET_BUFFER_MAXIMUM MSG_MAXIOVLEN +#endif + +typedef int ENetSocket; + +#define ENET_SOCKET_NULL -1 + +#define ENET_HOST_TO_NET_16(value) (htons (value)) /**< macro that converts host to net byte-order of a 16-bit value */ +#define ENET_HOST_TO_NET_32(value) (htonl (value)) /**< macro that converts host to net byte-order of a 32-bit value */ + +#define ENET_NET_TO_HOST_16(value) (ntohs (value)) /**< macro that converts net to host byte-order of a 16-bit value */ +#define ENET_NET_TO_HOST_32(value) (ntohl (value)) /**< macro that converts net to host byte-order of a 32-bit value */ + +typedef struct +{ + void * data; + size_t dataLength; +} ENetBuffer; + +#define ENET_CALLBACK + +#define ENET_API extern + +typedef fd_set ENetSocketSet; + +#define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO (& (sockset)) +#define ENET_SOCKETSET_ADD(sockset, socket) FD_SET (socket, & (sockset)) +#define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLR (socket, & (sockset)) +#define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET (socket, & (sockset)) + +#endif /* __ENET_UNIX_H__ */ + diff --git a/modules/enet/enet/utility.h b/modules/enet/enet/utility.h new file mode 100644 index 0000000000..e48a476be3 --- /dev/null +++ b/modules/enet/enet/utility.h @@ -0,0 +1,12 @@ +/** + @file utility.h + @brief ENet utility header +*/ +#ifndef __ENET_UTILITY_H__ +#define __ENET_UTILITY_H__ + +#define ENET_MAX(x, y) ((x) > (y) ? (x) : (y)) +#define ENET_MIN(x, y) ((x) < (y) ? (x) : (y)) + +#endif /* __ENET_UTILITY_H__ */ + diff --git a/modules/enet/enet/win32.h b/modules/enet/enet/win32.h new file mode 100644 index 0000000000..e73ca9d052 --- /dev/null +++ b/modules/enet/enet/win32.h @@ -0,0 +1,57 @@ +/** + @file win32.h + @brief ENet Win32 header +*/ +#ifndef __ENET_WIN32_H__ +#define __ENET_WIN32_H__ + +#ifdef _MSC_VER +#ifdef ENET_BUILDING_LIB +#pragma warning (disable: 4267) // size_t to int conversion +#pragma warning (disable: 4244) // 64bit to 32bit int +#pragma warning (disable: 4018) // signed/unsigned mismatch +#pragma warning (disable: 4146) // unary minus operator applied to unsigned type +#endif +#endif + +#include <stdlib.h> +#include <winsock2.h> + +typedef SOCKET ENetSocket; + +#define ENET_SOCKET_NULL INVALID_SOCKET + +#define ENET_HOST_TO_NET_16(value) (htons (value)) +#define ENET_HOST_TO_NET_32(value) (htonl (value)) + +#define ENET_NET_TO_HOST_16(value) (ntohs (value)) +#define ENET_NET_TO_HOST_32(value) (ntohl (value)) + +typedef struct +{ + size_t dataLength; + void * data; +} ENetBuffer; + +#define ENET_CALLBACK __cdecl + +#ifdef ENET_DLL +#ifdef ENET_BUILDING_LIB +#define ENET_API __declspec( dllexport ) +#else +#define ENET_API __declspec( dllimport ) +#endif /* ENET_BUILDING_LIB */ +#else /* !ENET_DLL */ +#define ENET_API extern +#endif /* ENET_DLL */ + +typedef fd_set ENetSocketSet; + +#define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO (& (sockset)) +#define ENET_SOCKETSET_ADD(sockset, socket) FD_SET (socket, & (sockset)) +#define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLR (socket, & (sockset)) +#define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET (socket, & (sockset)) + +#endif /* __ENET_WIN32_H__ */ + + diff --git a/modules/enet/host.c b/modules/enet/host.c new file mode 100644 index 0000000000..3be6c0922c --- /dev/null +++ b/modules/enet/host.c @@ -0,0 +1,492 @@ +/** + @file host.c + @brief ENet host management functions +*/ +#define ENET_BUILDING_LIB 1 +#include <string.h> +#include "enet/enet.h" + +/** @defgroup host ENet host functions + @{ +*/ + +/** Creates a host for communicating to peers. + + @param address the address at which other peers may connect to this host. If NULL, then no peers may connect to the host. + @param peerCount the maximum number of peers that should be allocated for the host. + @param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT + @param incomingBandwidth downstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth. + @param outgoingBandwidth upstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth. + + @returns the host on success and NULL on failure + + @remarks ENet will strategically drop packets on specific sides of a connection between hosts + to ensure the host's bandwidth is not overwhelmed. The bandwidth parameters also determine + the window size of a connection which limits the amount of reliable packets that may be in transit + at any given time. +*/ +ENetHost * +enet_host_create (const ENetAddress * address, size_t peerCount, size_t channelLimit, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth) +{ + ENetHost * host; + ENetPeer * currentPeer; + + if (peerCount > ENET_PROTOCOL_MAXIMUM_PEER_ID) + return NULL; + + host = (ENetHost *) enet_malloc (sizeof (ENetHost)); + if (host == NULL) + return NULL; + memset (host, 0, sizeof (ENetHost)); + + host -> peers = (ENetPeer *) enet_malloc (peerCount * sizeof (ENetPeer)); + if (host -> peers == NULL) + { + enet_free (host); + + return NULL; + } + memset (host -> peers, 0, peerCount * sizeof (ENetPeer)); + + host -> socket = enet_socket_create (ENET_SOCKET_TYPE_DATAGRAM); + if (host -> socket == ENET_SOCKET_NULL || (address != NULL && enet_socket_bind (host -> socket, address) < 0)) + { + if (host -> socket != ENET_SOCKET_NULL) + enet_socket_destroy (host -> socket); + + enet_free (host -> peers); + enet_free (host); + + return NULL; + } + + enet_socket_set_option (host -> socket, ENET_SOCKOPT_NONBLOCK, 1); + enet_socket_set_option (host -> socket, ENET_SOCKOPT_BROADCAST, 1); + enet_socket_set_option (host -> socket, ENET_SOCKOPT_RCVBUF, ENET_HOST_RECEIVE_BUFFER_SIZE); + enet_socket_set_option (host -> socket, ENET_SOCKOPT_SNDBUF, ENET_HOST_SEND_BUFFER_SIZE); + + if (address != NULL && enet_socket_get_address (host -> socket, & host -> address) < 0) + host -> address = * address; + + if (! channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) + channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; + else + if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT) + channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT; + + host -> randomSeed = (enet_uint32) (size_t) host; + host -> randomSeed += enet_host_random_seed (); + host -> randomSeed = (host -> randomSeed << 16) | (host -> randomSeed >> 16); + host -> channelLimit = channelLimit; + host -> incomingBandwidth = incomingBandwidth; + host -> outgoingBandwidth = outgoingBandwidth; + host -> bandwidthThrottleEpoch = 0; + host -> recalculateBandwidthLimits = 0; + host -> mtu = ENET_HOST_DEFAULT_MTU; + host -> peerCount = peerCount; + host -> commandCount = 0; + host -> bufferCount = 0; + host -> checksum = NULL; + host -> receivedAddress.host = ENET_HOST_ANY; + host -> receivedAddress.port = 0; + host -> receivedData = NULL; + host -> receivedDataLength = 0; + + host -> totalSentData = 0; + host -> totalSentPackets = 0; + host -> totalReceivedData = 0; + host -> totalReceivedPackets = 0; + + host -> connectedPeers = 0; + host -> bandwidthLimitedPeers = 0; + host -> duplicatePeers = ENET_PROTOCOL_MAXIMUM_PEER_ID; + host -> maximumPacketSize = ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE; + host -> maximumWaitingData = ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA; + + host -> compressor.context = NULL; + host -> compressor.compress = NULL; + host -> compressor.decompress = NULL; + host -> compressor.destroy = NULL; + + host -> intercept = NULL; + + enet_list_clear (& host -> dispatchQueue); + + for (currentPeer = host -> peers; + currentPeer < & host -> peers [host -> peerCount]; + ++ currentPeer) + { + currentPeer -> host = host; + currentPeer -> incomingPeerID = currentPeer - host -> peers; + currentPeer -> outgoingSessionID = currentPeer -> incomingSessionID = 0xFF; + currentPeer -> data = NULL; + + enet_list_clear (& currentPeer -> acknowledgements); + enet_list_clear (& currentPeer -> sentReliableCommands); + enet_list_clear (& currentPeer -> sentUnreliableCommands); + enet_list_clear (& currentPeer -> outgoingReliableCommands); + enet_list_clear (& currentPeer -> outgoingUnreliableCommands); + enet_list_clear (& currentPeer -> dispatchedCommands); + + enet_peer_reset (currentPeer); + } + + return host; +} + +/** Destroys the host and all resources associated with it. + @param host pointer to the host to destroy +*/ +void +enet_host_destroy (ENetHost * host) +{ + ENetPeer * currentPeer; + + if (host == NULL) + return; + + enet_socket_destroy (host -> socket); + + for (currentPeer = host -> peers; + currentPeer < & host -> peers [host -> peerCount]; + ++ currentPeer) + { + enet_peer_reset (currentPeer); + } + + if (host -> compressor.context != NULL && host -> compressor.destroy) + (* host -> compressor.destroy) (host -> compressor.context); + + enet_free (host -> peers); + enet_free (host); +} + +/** Initiates a connection to a foreign host. + @param host host seeking the connection + @param address destination for the connection + @param channelCount number of channels to allocate + @param data user data supplied to the receiving host + @returns a peer representing the foreign host on success, NULL on failure + @remarks The peer returned will have not completed the connection until enet_host_service() + notifies of an ENET_EVENT_TYPE_CONNECT event for the peer. +*/ +ENetPeer * +enet_host_connect (ENetHost * host, const ENetAddress * address, size_t channelCount, enet_uint32 data) +{ + ENetPeer * currentPeer; + ENetChannel * channel; + ENetProtocol command; + + if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT) + channelCount = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT; + else + if (channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) + channelCount = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; + + for (currentPeer = host -> peers; + currentPeer < & host -> peers [host -> peerCount]; + ++ currentPeer) + { + if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED) + break; + } + + if (currentPeer >= & host -> peers [host -> peerCount]) + return NULL; + + currentPeer -> channels = (ENetChannel *) enet_malloc (channelCount * sizeof (ENetChannel)); + if (currentPeer -> channels == NULL) + return NULL; + currentPeer -> channelCount = channelCount; + currentPeer -> state = ENET_PEER_STATE_CONNECTING; + currentPeer -> address = * address; + currentPeer -> connectID = ++ host -> randomSeed; + + if (host -> outgoingBandwidth == 0) + currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + else + currentPeer -> windowSize = (host -> outgoingBandwidth / + ENET_PEER_WINDOW_SIZE_SCALE) * + ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (currentPeer -> windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + currentPeer -> windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else + if (currentPeer -> windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + for (channel = currentPeer -> channels; + channel < & currentPeer -> channels [channelCount]; + ++ channel) + { + channel -> outgoingReliableSequenceNumber = 0; + channel -> outgoingUnreliableSequenceNumber = 0; + channel -> incomingReliableSequenceNumber = 0; + channel -> incomingUnreliableSequenceNumber = 0; + + enet_list_clear (& channel -> incomingReliableCommands); + enet_list_clear (& channel -> incomingUnreliableCommands); + + channel -> usedReliableWindows = 0; + memset (channel -> reliableWindows, 0, sizeof (channel -> reliableWindows)); + } + + command.header.command = ENET_PROTOCOL_COMMAND_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + command.connect.outgoingPeerID = ENET_HOST_TO_NET_16 (currentPeer -> incomingPeerID); + command.connect.incomingSessionID = currentPeer -> incomingSessionID; + command.connect.outgoingSessionID = currentPeer -> outgoingSessionID; + command.connect.mtu = ENET_HOST_TO_NET_32 (currentPeer -> mtu); + command.connect.windowSize = ENET_HOST_TO_NET_32 (currentPeer -> windowSize); + command.connect.channelCount = ENET_HOST_TO_NET_32 (channelCount); + command.connect.incomingBandwidth = ENET_HOST_TO_NET_32 (host -> incomingBandwidth); + command.connect.outgoingBandwidth = ENET_HOST_TO_NET_32 (host -> outgoingBandwidth); + command.connect.packetThrottleInterval = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleInterval); + command.connect.packetThrottleAcceleration = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleAcceleration); + command.connect.packetThrottleDeceleration = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleDeceleration); + command.connect.connectID = currentPeer -> connectID; + command.connect.data = ENET_HOST_TO_NET_32 (data); + + enet_peer_queue_outgoing_command (currentPeer, & command, NULL, 0, 0); + + return currentPeer; +} + +/** Queues a packet to be sent to all peers associated with the host. + @param host host on which to broadcast the packet + @param channelID channel on which to broadcast + @param packet packet to broadcast +*/ +void +enet_host_broadcast (ENetHost * host, enet_uint8 channelID, ENetPacket * packet) +{ + ENetPeer * currentPeer; + + for (currentPeer = host -> peers; + currentPeer < & host -> peers [host -> peerCount]; + ++ currentPeer) + { + if (currentPeer -> state != ENET_PEER_STATE_CONNECTED) + continue; + + enet_peer_send (currentPeer, channelID, packet); + } + + if (packet -> referenceCount == 0) + enet_packet_destroy (packet); +} + +/** Sets the packet compressor the host should use to compress and decompress packets. + @param host host to enable or disable compression for + @param compressor callbacks for for the packet compressor; if NULL, then compression is disabled +*/ +void +enet_host_compress (ENetHost * host, const ENetCompressor * compressor) +{ + if (host -> compressor.context != NULL && host -> compressor.destroy) + (* host -> compressor.destroy) (host -> compressor.context); + + if (compressor) + host -> compressor = * compressor; + else + host -> compressor.context = NULL; +} + +/** Limits the maximum allowed channels of future incoming connections. + @param host host to limit + @param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT +*/ +void +enet_host_channel_limit (ENetHost * host, size_t channelLimit) +{ + if (! channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) + channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; + else + if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT) + channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT; + + host -> channelLimit = channelLimit; +} + + +/** Adjusts the bandwidth limits of a host. + @param host host to adjust + @param incomingBandwidth new incoming bandwidth + @param outgoingBandwidth new outgoing bandwidth + @remarks the incoming and outgoing bandwidth parameters are identical in function to those + specified in enet_host_create(). +*/ +void +enet_host_bandwidth_limit (ENetHost * host, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth) +{ + host -> incomingBandwidth = incomingBandwidth; + host -> outgoingBandwidth = outgoingBandwidth; + host -> recalculateBandwidthLimits = 1; +} + +void +enet_host_bandwidth_throttle (ENetHost * host) +{ + enet_uint32 timeCurrent = enet_time_get (), + elapsedTime = timeCurrent - host -> bandwidthThrottleEpoch, + peersRemaining = (enet_uint32) host -> connectedPeers, + dataTotal = ~0, + bandwidth = ~0, + throttle = 0, + bandwidthLimit = 0; + int needsAdjustment = host -> bandwidthLimitedPeers > 0 ? 1 : 0; + ENetPeer * peer; + ENetProtocol command; + + if (elapsedTime < ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL) + return; + + host -> bandwidthThrottleEpoch = timeCurrent; + + if (peersRemaining == 0) + return; + + if (host -> outgoingBandwidth != 0) + { + dataTotal = 0; + bandwidth = (host -> outgoingBandwidth * elapsedTime) / 1000; + + for (peer = host -> peers; + peer < & host -> peers [host -> peerCount]; + ++ peer) + { + if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) + continue; + + dataTotal += peer -> outgoingDataTotal; + } + } + + while (peersRemaining > 0 && needsAdjustment != 0) + { + needsAdjustment = 0; + + if (dataTotal <= bandwidth) + throttle = ENET_PEER_PACKET_THROTTLE_SCALE; + else + throttle = (bandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / dataTotal; + + for (peer = host -> peers; + peer < & host -> peers [host -> peerCount]; + ++ peer) + { + enet_uint32 peerBandwidth; + + if ((peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) || + peer -> incomingBandwidth == 0 || + peer -> outgoingBandwidthThrottleEpoch == timeCurrent) + continue; + + peerBandwidth = (peer -> incomingBandwidth * elapsedTime) / 1000; + if ((throttle * peer -> outgoingDataTotal) / ENET_PEER_PACKET_THROTTLE_SCALE <= peerBandwidth) + continue; + + peer -> packetThrottleLimit = (peerBandwidth * + ENET_PEER_PACKET_THROTTLE_SCALE) / peer -> outgoingDataTotal; + + if (peer -> packetThrottleLimit == 0) + peer -> packetThrottleLimit = 1; + + if (peer -> packetThrottle > peer -> packetThrottleLimit) + peer -> packetThrottle = peer -> packetThrottleLimit; + + peer -> outgoingBandwidthThrottleEpoch = timeCurrent; + + peer -> incomingDataTotal = 0; + peer -> outgoingDataTotal = 0; + + needsAdjustment = 1; + -- peersRemaining; + bandwidth -= peerBandwidth; + dataTotal -= peerBandwidth; + } + } + + if (peersRemaining > 0) + { + if (dataTotal <= bandwidth) + throttle = ENET_PEER_PACKET_THROTTLE_SCALE; + else + throttle = (bandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / dataTotal; + + for (peer = host -> peers; + peer < & host -> peers [host -> peerCount]; + ++ peer) + { + if ((peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) || + peer -> outgoingBandwidthThrottleEpoch == timeCurrent) + continue; + + peer -> packetThrottleLimit = throttle; + + if (peer -> packetThrottle > peer -> packetThrottleLimit) + peer -> packetThrottle = peer -> packetThrottleLimit; + + peer -> incomingDataTotal = 0; + peer -> outgoingDataTotal = 0; + } + } + + if (host -> recalculateBandwidthLimits) + { + host -> recalculateBandwidthLimits = 0; + + peersRemaining = (enet_uint32) host -> connectedPeers; + bandwidth = host -> incomingBandwidth; + needsAdjustment = 1; + + if (bandwidth == 0) + bandwidthLimit = 0; + else + while (peersRemaining > 0 && needsAdjustment != 0) + { + needsAdjustment = 0; + bandwidthLimit = bandwidth / peersRemaining; + + for (peer = host -> peers; + peer < & host -> peers [host -> peerCount]; + ++ peer) + { + if ((peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) || + peer -> incomingBandwidthThrottleEpoch == timeCurrent) + continue; + + if (peer -> outgoingBandwidth > 0 && + peer -> outgoingBandwidth >= bandwidthLimit) + continue; + + peer -> incomingBandwidthThrottleEpoch = timeCurrent; + + needsAdjustment = 1; + -- peersRemaining; + bandwidth -= peer -> outgoingBandwidth; + } + } + + for (peer = host -> peers; + peer < & host -> peers [host -> peerCount]; + ++ peer) + { + if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) + continue; + + command.header.command = ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + command.bandwidthLimit.outgoingBandwidth = ENET_HOST_TO_NET_32 (host -> outgoingBandwidth); + + if (peer -> incomingBandwidthThrottleEpoch == timeCurrent) + command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32 (peer -> outgoingBandwidth); + else + command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32 (bandwidthLimit); + + enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0); + } + } +} + +/** @} */ diff --git a/modules/enet/list.c b/modules/enet/list.c new file mode 100644 index 0000000000..1c1a8dfaaf --- /dev/null +++ b/modules/enet/list.c @@ -0,0 +1,75 @@ +/** + @file list.c + @brief ENet linked list functions +*/ +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +/** + @defgroup list ENet linked list utility functions + @ingroup private + @{ +*/ +void +enet_list_clear (ENetList * list) +{ + list -> sentinel.next = & list -> sentinel; + list -> sentinel.previous = & list -> sentinel; +} + +ENetListIterator +enet_list_insert (ENetListIterator position, void * data) +{ + ENetListIterator result = (ENetListIterator) data; + + result -> previous = position -> previous; + result -> next = position; + + result -> previous -> next = result; + position -> previous = result; + + return result; +} + +void * +enet_list_remove (ENetListIterator position) +{ + position -> previous -> next = position -> next; + position -> next -> previous = position -> previous; + + return position; +} + +ENetListIterator +enet_list_move (ENetListIterator position, void * dataFirst, void * dataLast) +{ + ENetListIterator first = (ENetListIterator) dataFirst, + last = (ENetListIterator) dataLast; + + first -> previous -> next = last -> next; + last -> next -> previous = first -> previous; + + first -> previous = position -> previous; + last -> next = position; + + first -> previous -> next = first; + position -> previous = last; + + return first; +} + +size_t +enet_list_size (ENetList * list) +{ + size_t size = 0; + ENetListIterator position; + + for (position = enet_list_begin (list); + position != enet_list_end (list); + position = enet_list_next (position)) + ++ size; + + return size; +} + +/** @} */ diff --git a/modules/enet/networked_multiplayer_enet.cpp b/modules/enet/networked_multiplayer_enet.cpp new file mode 100644 index 0000000000..18a4347edf --- /dev/null +++ b/modules/enet/networked_multiplayer_enet.cpp @@ -0,0 +1,650 @@ +#include "os/os.h" +#include "io/marshalls.h" +#include "networked_multiplayer_enet.h" + +void NetworkedMultiplayerENet::set_transfer_mode(TransferMode p_mode) { + + transfer_mode=p_mode; +} + +void NetworkedMultiplayerENet::set_target_peer(int p_peer){ + + target_peer=p_peer; +} + +int NetworkedMultiplayerENet::get_packet_peer() const{ + + ERR_FAIL_COND_V(!active,1); + ERR_FAIL_COND_V(incoming_packets.size()==0,1); + + return incoming_packets.front()->get().from; + +} + +Error NetworkedMultiplayerENet::create_server(int p_port, int p_max_clients, int p_in_bandwidth, int p_out_bandwidth){ + + ERR_FAIL_COND_V(active,ERR_ALREADY_IN_USE); + + ENetAddress address; + address.host = bind_ip; + + address.port = p_port; + + host = enet_host_create (& address /* the address to bind the server host to */, + p_max_clients /* allow up to 32 clients and/or outgoing connections */, + 2 /* allow up to 2 channels to be used, 0 and 1 */, + p_in_bandwidth /* assume any amount of incoming bandwidth */, + p_out_bandwidth /* assume any amount of outgoing bandwidth */); + + ERR_FAIL_COND_V(!host,ERR_CANT_CREATE); + + _setup_compressor(); + active=true; + server=true; + refuse_connections=false; + unique_id=1; + connection_status=CONNECTION_CONNECTED; + return OK; +} +Error NetworkedMultiplayerENet::create_client(const IP_Address& p_ip, int p_port, int p_in_bandwidth, int p_out_bandwidth){ + + ERR_FAIL_COND_V(active,ERR_ALREADY_IN_USE); + + host = enet_host_create (NULL /* create a client host */, + 1 /* only allow 1 outgoing connection */, + 2 /* allow up 2 channels to be used, 0 and 1 */, + p_in_bandwidth /* 56K modem with 56 Kbps downstream bandwidth */, + p_out_bandwidth /* 56K modem with 14 Kbps upstream bandwidth */); + + ERR_FAIL_COND_V(!host,ERR_CANT_CREATE); + + + _setup_compressor(); + + ENetAddress address; + address.host=p_ip.host; + address.port=p_port; + + //enet_address_set_host (& address, "localhost"); + //address.port = p_port; + + unique_id=_gen_unique_id(); + + /* Initiate the connection, allocating the two channels 0 and 1. */ + ENetPeer *peer = enet_host_connect (host, & address, 2, unique_id); + + if (peer == NULL) { + enet_host_destroy(host); + ERR_FAIL_COND_V(!peer,ERR_CANT_CREATE); + } + + //technically safe to ignore the peer or anything else. + + connection_status=CONNECTION_CONNECTING; + active=true; + server=false; + refuse_connections=false; + + return OK; +} + +void NetworkedMultiplayerENet::poll(){ + + ERR_FAIL_COND(!active); + + _pop_current_packet(); + + ENetEvent event; + /* Wait up to 1000 milliseconds for an event. */ + while (true) { + + if (!host || !active) //might have been disconnected while emitting a notification + return; + + int ret = enet_host_service (host, & event, 1); + + if (ret<0) { + //error, do something? + break; + } else if (ret==0) { + break; + } + + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: { + /* Store any relevant client information here. */ + + if (server && refuse_connections) { + enet_peer_reset(event.peer); + break; + } + + IP_Address ip; + ip.host=event.peer -> address.host; + + int *new_id = memnew( int ); + *new_id = event.data; + + if (*new_id==0) { //data zero is sent by server (enet won't let you configure this). Server is always 1 + *new_id=1; + } + + event.peer->data=new_id; + + peer_map[*new_id]=event.peer; + + connection_status=CONNECTION_CONNECTED; //if connecting, this means it connected t something! + + emit_signal("peer_connected",*new_id); + + if (server) { + //someone connected, let it know of all the peers available + for (Map<int,ENetPeer*>::Element *E=peer_map.front();E;E=E->next()) { + + if (E->key()==*new_id) + continue; + //send existing peers to new peer + ENetPacket * packet = enet_packet_create (NULL,8,ENET_PACKET_FLAG_RELIABLE); + encode_uint32(SYSMSG_ADD_PEER,&packet->data[0]); + encode_uint32(E->key(),&packet->data[4]); + enet_peer_send(event.peer,1,packet); + //send the new peer to existing peers + packet = enet_packet_create (NULL,8,ENET_PACKET_FLAG_RELIABLE); + encode_uint32(SYSMSG_ADD_PEER,&packet->data[0]); + encode_uint32(*new_id,&packet->data[4]); + enet_peer_send(E->get(),1,packet); + } + } else { + + emit_signal("connection_succeeded"); + } + + } break; + case ENET_EVENT_TYPE_DISCONNECT: { + + /* Reset the peer's client information. */ + + int *id = (int*)event.peer -> data; + + + + if (!id) { + if (!server) { + emit_signal("connection_failed"); + } + } else { + + if (server) { + //someone disconnected, let it know to everyone else + for (Map<int,ENetPeer*>::Element *E=peer_map.front();E;E=E->next()) { + + if (E->key()==*id) + continue; + //send the new peer to existing peers + ENetPacket* packet = enet_packet_create (NULL,8,ENET_PACKET_FLAG_RELIABLE); + encode_uint32(SYSMSG_REMOVE_PEER,&packet->data[0]); + encode_uint32(*id,&packet->data[4]); + enet_peer_send(E->get(),1,packet); + } + } else if (!server) { + emit_signal("server_disconnected"); + close_connection(); + return; + } + + emit_signal("peer_disconnected",*id); + peer_map.erase(*id); + memdelete( id ); + + } + + + } break; + case ENET_EVENT_TYPE_RECEIVE: { + + + if (event.channelID==1) { + //some config message + ERR_CONTINUE( event.packet->dataLength < 8); + + int msg = decode_uint32(&event.packet->data[0]); + int id = decode_uint32(&event.packet->data[4]); + + switch(msg) { + case SYSMSG_ADD_PEER: { + + peer_map[id]=NULL; + emit_signal("peer_connected",id); + + } break; + case SYSMSG_REMOVE_PEER: { + + peer_map.erase(id); + emit_signal("peer_disconnected",id); + } break; + } + + enet_packet_destroy(event.packet); + } else if (event.channelID==0){ + + Packet packet; + packet.packet = event.packet; + + int *id = (int*)event.peer -> data; + + ERR_CONTINUE(event.packet->dataLength<12) + + + uint32_t source = decode_uint32(&event.packet->data[0]); + int target = decode_uint32(&event.packet->data[4]); + uint32_t flags = decode_uint32(&event.packet->data[8]); + + packet.from=source; + + if (server) { + + packet.from=*id; + + if (target==0) { + //re-send the everyone but sender :| + + incoming_packets.push_back(packet); + //and make copies for sending + for (Map<int,ENetPeer*>::Element *E=peer_map.front();E;E=E->next()) { + + if (uint32_t(E->key())==source) //do not resend to self + continue; + + ENetPacket* packet2 = enet_packet_create (packet.packet->data,packet.packet->dataLength,flags); + + enet_peer_send(E->get(),0,packet2); + } + + } else if (target<0) { + //to all but one + + //and make copies for sending + for (Map<int,ENetPeer*>::Element *E=peer_map.front();E;E=E->next()) { + + if (uint32_t(E->key())==source || E->key()==-target) //do not resend to self, also do not send to excluded + continue; + + ENetPacket* packet2 = enet_packet_create (packet.packet->data,packet.packet->dataLength,flags); + + enet_peer_send(E->get(),0,packet2); + } + + if (-target != 1) { + //server is not excluded + incoming_packets.push_back(packet); + } else { + //server is excluded, erase packet + enet_packet_destroy(packet.packet); + } + + } else if (target==1) { + //to myself and only myself + incoming_packets.push_back(packet); + } else { + //to someone else, specifically + ERR_CONTINUE(!peer_map.has(target)); + enet_peer_send(peer_map[target],0,packet.packet); + } + } else { + + incoming_packets.push_back(packet); + } + + + //destroy packet later.. + } else { + ERR_CONTINUE(true); + } + + + }break; + case ENET_EVENT_TYPE_NONE: { + //do nothing + } break; + } + } +} + +bool NetworkedMultiplayerENet::is_server() const { + ERR_FAIL_COND_V(!active,false); + + return server; +} + +void NetworkedMultiplayerENet::close_connection() { + + if (!active) + return; + + _pop_current_packet(); + + bool peers_disconnected=false; + for (Map<int,ENetPeer*>::Element *E=peer_map.front();E;E=E->next()) { + if (E->get()) { + enet_peer_disconnect_now(E->get(),unique_id); + peers_disconnected=true; + } + } + + if (peers_disconnected) { + enet_host_flush(host); + OS::get_singleton()->delay_usec(100); //wait 100ms for disconnection packets to send + + } + + enet_host_destroy(host); + active=false; + incoming_packets.clear(); + unique_id=1; //server is 1 + connection_status=CONNECTION_DISCONNECTED; +} + +int NetworkedMultiplayerENet::get_available_packet_count() const { + + return incoming_packets.size(); +} +Error NetworkedMultiplayerENet::get_packet(const uint8_t **r_buffer,int &r_buffer_size) const{ + + ERR_FAIL_COND_V(incoming_packets.size()==0,ERR_UNAVAILABLE); + + _pop_current_packet(); + + current_packet = incoming_packets.front()->get(); + incoming_packets.pop_front(); + + *r_buffer=(const uint8_t*)(¤t_packet.packet->data[12]); + r_buffer_size=current_packet.packet->dataLength; + + return OK; +} +Error NetworkedMultiplayerENet::put_packet(const uint8_t *p_buffer,int p_buffer_size){ + + ERR_FAIL_COND_V(!active,ERR_UNCONFIGURED); + ERR_FAIL_COND_V(connection_status!=CONNECTION_CONNECTED,ERR_UNCONFIGURED); + + int packet_flags=0; + + switch(transfer_mode) { + case TRANSFER_MODE_UNRELIABLE: { + packet_flags=ENET_PACKET_FLAG_UNSEQUENCED; + } break; + case TRANSFER_MODE_UNRELIABLE_ORDERED: { + packet_flags=0; + } break; + case TRANSFER_MODE_RELIABLE: { + packet_flags=ENET_PACKET_FLAG_RELIABLE; + } break; + } + + Map<int,ENetPeer*>::Element *E=NULL; + + if (target_peer!=0) { + + E = peer_map.find(ABS(target_peer)); + if (!E) { + ERR_EXPLAIN("Invalid Target Peer: "+itos(target_peer)); + ERR_FAIL_V(ERR_INVALID_PARAMETER); + } + } + + ENetPacket * packet = enet_packet_create (NULL,p_buffer_size+12,packet_flags); + encode_uint32(unique_id,&packet->data[0]); //source ID + encode_uint32(target_peer,&packet->data[4]); //dest ID + encode_uint32(packet_flags,&packet->data[8]); //dest ID + copymem(&packet->data[12],p_buffer,p_buffer_size); + + if (server) { + + if (target_peer==0) { + enet_host_broadcast(host,0,packet); + } else if (target_peer<0) { + //send to all but one + //and make copies for sending + + int exclude=-target_peer; + + for (Map<int,ENetPeer*>::Element *F=peer_map.front();F;F=F->next()) { + + if (F->key()==exclude) // exclude packet + continue; + + ENetPacket* packet2 = enet_packet_create (packet->data,packet->dataLength,packet_flags); + + enet_peer_send(F->get(),0,packet2); + } + + enet_packet_destroy(packet); //original packet no longer needed + } else { + enet_peer_send (E->get(), 0, packet); + + } + } else { + + ERR_FAIL_COND_V(!peer_map.has(1),ERR_BUG); + enet_peer_send (peer_map[1], 0, packet); //send to server for broadcast.. + + } + + enet_host_flush(host); + + return OK; +} + +int NetworkedMultiplayerENet::get_max_packet_size() const { + + return 1<<24; //anything is good +} + +void NetworkedMultiplayerENet::_pop_current_packet() const { + + if (current_packet.packet) { + enet_packet_destroy(current_packet.packet); + current_packet.packet=NULL; + current_packet.from=0; + } + +} + +NetworkedMultiplayerPeer::ConnectionStatus NetworkedMultiplayerENet::get_connection_status() const { + + return connection_status; +} + +uint32_t NetworkedMultiplayerENet::_gen_unique_id() const { + + uint32_t hash = 0; + + while (hash==0 || hash==1) { + + hash = hash_djb2_one_32( + (uint32_t)OS::get_singleton()->get_ticks_usec() ); + hash = hash_djb2_one_32( + (uint32_t)OS::get_singleton()->get_unix_time(), hash ); + hash = hash_djb2_one_32( + (uint32_t)OS::get_singleton()->get_data_dir().hash64(), hash ); + //hash = hash_djb2_one_32( + // (uint32_t)OS::get_singleton()->get_unique_ID().hash64(), hash ); + hash = hash_djb2_one_32( + (uint32_t)((uint64_t)this), hash ); //rely on aslr heap + hash = hash_djb2_one_32( + (uint32_t)((uint64_t)&hash), hash ); //rely on aslr stack + + hash=hash&0x7FFFFFFF; // make it compatible with unsigned, since negatie id is used for exclusion + } + + return hash; +} + +int NetworkedMultiplayerENet::get_unique_id() const { + + ERR_FAIL_COND_V(!active,0); + return unique_id; +} + +void NetworkedMultiplayerENet::set_refuse_new_connections(bool p_enable) { + + refuse_connections=p_enable; +} + +bool NetworkedMultiplayerENet::is_refusing_new_connections() const { + + return refuse_connections; +} + +void NetworkedMultiplayerENet::set_compression_mode(CompressionMode p_mode) { + + compression_mode=p_mode; +} + +NetworkedMultiplayerENet::CompressionMode NetworkedMultiplayerENet::get_compression_mode() const{ + + return compression_mode; +} + +size_t NetworkedMultiplayerENet::enet_compress(void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit) { + + NetworkedMultiplayerENet *enet = (NetworkedMultiplayerENet*)(context); + + if (size_t(enet->src_compressor_mem.size())<inLimit) { + enet->src_compressor_mem.resize( inLimit ); + } + + int total = inLimit; + int ofs=0; + while(total) { + for(size_t i=0;i<inBufferCount;i++) { + int to_copy = MIN(total,int(inBuffers[i].dataLength)); + copymem(&enet->src_compressor_mem[ofs],inBuffers[i].data,to_copy); + ofs+=to_copy; + total-=to_copy; + } + } + + Compression::Mode mode; + + switch(enet->compression_mode) { + case COMPRESS_FASTLZ: { + mode=Compression::MODE_FASTLZ; + } break; + case COMPRESS_ZLIB: { + mode=Compression::MODE_DEFLATE; + } break; + default: { ERR_FAIL_V(0); } + } + + int req_size = Compression::get_max_compressed_buffer_size(ofs,mode); + if (enet->dst_compressor_mem.size()<req_size) { + enet->dst_compressor_mem.resize(req_size); + } + int ret=Compression::compress(enet->dst_compressor_mem.ptr(),enet->src_compressor_mem.ptr(),ofs,mode); + + if (ret<0) + return 0; + + + if (ret>int(outLimit)) + return 0; //do not bother + + copymem(outData,enet->dst_compressor_mem.ptr(),ret); + + return ret; +} + +size_t NetworkedMultiplayerENet::enet_decompress (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit){ + + NetworkedMultiplayerENet *enet = (NetworkedMultiplayerENet*)(context); + int ret = -1; + switch(enet->compression_mode) { + case COMPRESS_FASTLZ: { + + ret=Compression::decompress(outData,outLimit,inData,inLimit,Compression::MODE_FASTLZ); + } break; + case COMPRESS_ZLIB: { + + ret=Compression::decompress(outData,outLimit,inData,inLimit,Compression::MODE_DEFLATE); + } break; + default: {} + } + if (ret<0) { + return 0; + } else { + return ret; + } +} + +void NetworkedMultiplayerENet::_setup_compressor() { + + switch(compression_mode) { + + case COMPRESS_NONE: { + + enet_host_compress(host,NULL); + } break; + case COMPRESS_RANGE_CODER: { + enet_host_compress_with_range_coder(host); + } break; + case COMPRESS_FASTLZ: + case COMPRESS_ZLIB: { + + enet_host_compress(host,&enet_compressor); + } break; + } +} + +void NetworkedMultiplayerENet::enet_compressor_destroy(void * context){ + + //do none +} + + +void NetworkedMultiplayerENet::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("create_server","port","max_clients","in_bandwidth","out_bandwidth"),&NetworkedMultiplayerENet::create_server,DEFVAL(32),DEFVAL(0),DEFVAL(0)); + ObjectTypeDB::bind_method(_MD("create_client","ip","port","in_bandwidth","out_bandwidth"),&NetworkedMultiplayerENet::create_client,DEFVAL(0),DEFVAL(0)); + ObjectTypeDB::bind_method(_MD("close_connection"),&NetworkedMultiplayerENet::close_connection); + ObjectTypeDB::bind_method(_MD("set_compression_mode","mode"),&NetworkedMultiplayerENet::set_compression_mode); + ObjectTypeDB::bind_method(_MD("get_compression_mode"),&NetworkedMultiplayerENet::get_compression_mode); + ObjectTypeDB::bind_method(_MD("set_bind_ip", "ip"),&NetworkedMultiplayerENet::set_bind_ip); + + BIND_CONSTANT( COMPRESS_NONE ); + BIND_CONSTANT( COMPRESS_RANGE_CODER ); + BIND_CONSTANT( COMPRESS_FASTLZ ); + BIND_CONSTANT( COMPRESS_ZLIB ); +} + + +NetworkedMultiplayerENet::NetworkedMultiplayerENet(){ + + active=false; + server=false; + refuse_connections=false; + unique_id=0; + target_peer=0; + current_packet.packet=NULL; + transfer_mode=TRANSFER_MODE_RELIABLE; + connection_status=CONNECTION_DISCONNECTED; + compression_mode=COMPRESS_NONE; + enet_compressor.context=this; + enet_compressor.compress=enet_compress; + enet_compressor.decompress=enet_decompress; + enet_compressor.destroy=enet_compressor_destroy; + + bind_ip=ENET_HOST_ANY; +} + +NetworkedMultiplayerENet::~NetworkedMultiplayerENet(){ + + close_connection(); +} + +// sets IP for ENet to bind when using create_server +// if no IP is set, then ENet bind to ENET_HOST_ANY +void NetworkedMultiplayerENet::set_bind_ip(const IP_Address& p_ip){ + bind_ip=p_ip.host; +} diff --git a/modules/enet/networked_multiplayer_enet.h b/modules/enet/networked_multiplayer_enet.h new file mode 100644 index 0000000000..59863c1f78 --- /dev/null +++ b/modules/enet/networked_multiplayer_enet.h @@ -0,0 +1,114 @@ +#ifndef NETWORKED_MULTIPLAYER_ENET_H +#define NETWORKED_MULTIPLAYER_ENET_H + +#include "io/networked_multiplayer_peer.h" +#include "enet/enet.h" +#include "io/compression.h" + +class NetworkedMultiplayerENet : public NetworkedMultiplayerPeer { + + OBJ_TYPE(NetworkedMultiplayerENet,NetworkedMultiplayerPeer) +public: + enum CompressionMode { + COMPRESS_NONE, + COMPRESS_RANGE_CODER, + COMPRESS_FASTLZ, + COMPRESS_ZLIB + }; +private: + + + enum { + SYSMSG_ADD_PEER, + SYSMSG_REMOVE_PEER + }; + + bool active; + bool server; + + uint32_t unique_id; + + int target_peer; + TransferMode transfer_mode; + + ENetEvent event; + ENetPeer *peer; + ENetHost *host; + + bool refuse_connections; + + ConnectionStatus connection_status; + + Map<int,ENetPeer*> peer_map; + + struct Packet { + + ENetPacket *packet; + int from; + }; + + CompressionMode compression_mode; + + mutable List<Packet> incoming_packets; + + mutable Packet current_packet; + + uint32_t _gen_unique_id() const; + void _pop_current_packet() const; + + Vector<uint8_t> src_compressor_mem; + Vector<uint8_t> dst_compressor_mem; + + ENetCompressor enet_compressor; + static size_t enet_compress(void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit); + static size_t enet_decompress (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit); + static void enet_compressor_destroy(void * context); + void _setup_compressor(); + + enet_uint32 bind_ip; +protected: + static void _bind_methods(); +public: + + virtual void set_transfer_mode(TransferMode p_mode); + virtual void set_target_peer(int p_peer); + + + virtual int get_packet_peer() const; + + + Error create_server(int p_port, int p_max_peers=32, int p_in_bandwidth=0, int p_out_bandwidth=0); + Error create_client(const IP_Address& p_ip, int p_port, int p_in_bandwidth=0, int p_out_bandwidth=0); + + void close_connection(); + + virtual void poll(); + + virtual bool is_server() const; + + virtual int get_available_packet_count() const; + virtual Error get_packet(const uint8_t **r_buffer,int &r_buffer_size) const; ///< buffer is GONE after next get_packet + virtual Error put_packet(const uint8_t *p_buffer,int p_buffer_size); + + virtual int get_max_packet_size() const; + + virtual ConnectionStatus get_connection_status() const; + + virtual void set_refuse_new_connections(bool p_enable); + virtual bool is_refusing_new_connections() const; + + virtual int get_unique_id() const; + + void set_compression_mode(CompressionMode p_mode); + CompressionMode get_compression_mode() const; + + NetworkedMultiplayerENet(); + ~NetworkedMultiplayerENet(); + + void set_bind_ip(const IP_Address& p_ip); +}; + +VARIANT_ENUM_CAST(NetworkedMultiplayerENet::CompressionMode); + + +#endif // NETWORKED_MULTIPLAYER_ENET_H diff --git a/modules/enet/packet.c b/modules/enet/packet.c new file mode 100644 index 0000000000..5fa78b28ae --- /dev/null +++ b/modules/enet/packet.c @@ -0,0 +1,165 @@ +/** + @file packet.c + @brief ENet packet management functions +*/ +#include <string.h> +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +/** @defgroup Packet ENet packet functions + @{ +*/ + +/** Creates a packet that may be sent to a peer. + @param data initial contents of the packet's data; the packet's data will remain uninitialized if data is NULL. + @param dataLength size of the data allocated for this packet + @param flags flags for this packet as described for the ENetPacket structure. + @returns the packet on success, NULL on failure +*/ +ENetPacket * +enet_packet_create (const void * data, size_t dataLength, enet_uint32 flags) +{ + ENetPacket * packet = (ENetPacket *) enet_malloc (sizeof (ENetPacket)); + if (packet == NULL) + return NULL; + + if (flags & ENET_PACKET_FLAG_NO_ALLOCATE) + packet -> data = (enet_uint8 *) data; + else + if (dataLength <= 0) + packet -> data = NULL; + else + { + packet -> data = (enet_uint8 *) enet_malloc (dataLength); + if (packet -> data == NULL) + { + enet_free (packet); + return NULL; + } + + if (data != NULL) + memcpy (packet -> data, data, dataLength); + } + + packet -> referenceCount = 0; + packet -> flags = flags; + packet -> dataLength = dataLength; + packet -> freeCallback = NULL; + packet -> userData = NULL; + + return packet; +} + +/** Destroys the packet and deallocates its data. + @param packet packet to be destroyed +*/ +void +enet_packet_destroy (ENetPacket * packet) +{ + if (packet == NULL) + return; + + if (packet -> freeCallback != NULL) + (* packet -> freeCallback) (packet); + if (! (packet -> flags & ENET_PACKET_FLAG_NO_ALLOCATE) && + packet -> data != NULL) + enet_free (packet -> data); + enet_free (packet); +} + +/** Attempts to resize the data in the packet to length specified in the + dataLength parameter + @param packet packet to resize + @param dataLength new size for the packet data + @returns 0 on success, < 0 on failure +*/ +int +enet_packet_resize (ENetPacket * packet, size_t dataLength) +{ + enet_uint8 * newData; + + if (dataLength <= packet -> dataLength || (packet -> flags & ENET_PACKET_FLAG_NO_ALLOCATE)) + { + packet -> dataLength = dataLength; + + return 0; + } + + newData = (enet_uint8 *) enet_malloc (dataLength); + if (newData == NULL) + return -1; + + memcpy (newData, packet -> data, packet -> dataLength); + enet_free (packet -> data); + + packet -> data = newData; + packet -> dataLength = dataLength; + + return 0; +} + +static int initializedCRC32 = 0; +static enet_uint32 crcTable [256]; + +static enet_uint32 +reflect_crc (int val, int bits) +{ + int result = 0, bit; + + for (bit = 0; bit < bits; bit ++) + { + if(val & 1) result |= 1 << (bits - 1 - bit); + val >>= 1; + } + + return result; +} + +static void +initialize_crc32 (void) +{ + int byte; + + for (byte = 0; byte < 256; ++ byte) + { + enet_uint32 crc = reflect_crc (byte, 8) << 24; + int offset; + + for(offset = 0; offset < 8; ++ offset) + { + if (crc & 0x80000000) + crc = (crc << 1) ^ 0x04c11db7; + else + crc <<= 1; + } + + crcTable [byte] = reflect_crc (crc, 32); + } + + initializedCRC32 = 1; +} + +enet_uint32 +enet_crc32 (const ENetBuffer * buffers, size_t bufferCount) +{ + enet_uint32 crc = 0xFFFFFFFF; + + if (! initializedCRC32) initialize_crc32 (); + + while (bufferCount -- > 0) + { + const enet_uint8 * data = (const enet_uint8 *) buffers -> data, + * dataEnd = & data [buffers -> dataLength]; + + while (data < dataEnd) + { + crc = (crc >> 8) ^ crcTable [(crc & 0xFF) ^ *data++]; + } + + ++ buffers; + } + + return ENET_HOST_TO_NET_32 (~ crc); +} + +/** @} */ diff --git a/modules/enet/peer.c b/modules/enet/peer.c new file mode 100644 index 0000000000..e2d0872bd3 --- /dev/null +++ b/modules/enet/peer.c @@ -0,0 +1,1004 @@ +/** + @file peer.c + @brief ENet peer management functions +*/ +#include <string.h> +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +/** @defgroup peer ENet peer functions + @{ +*/ + +/** Configures throttle parameter for a peer. + + Unreliable packets are dropped by ENet in response to the varying conditions + of the Internet connection to the peer. The throttle represents a probability + that an unreliable packet should not be dropped and thus sent by ENet to the peer. + The lowest mean round trip time from the sending of a reliable packet to the + receipt of its acknowledgement is measured over an amount of time specified by + the interval parameter in milliseconds. If a measured round trip time happens to + be significantly less than the mean round trip time measured over the interval, + then the throttle probability is increased to allow more traffic by an amount + specified in the acceleration parameter, which is a ratio to the ENET_PEER_PACKET_THROTTLE_SCALE + constant. If a measured round trip time happens to be significantly greater than + the mean round trip time measured over the interval, then the throttle probability + is decreased to limit traffic by an amount specified in the deceleration parameter, which + is a ratio to the ENET_PEER_PACKET_THROTTLE_SCALE constant. When the throttle has + a value of ENET_PEER_PACKET_THROTTLE_SCALE, no unreliable packets are dropped by + ENet, and so 100% of all unreliable packets will be sent. When the throttle has a + value of 0, all unreliable packets are dropped by ENet, and so 0% of all unreliable + packets will be sent. Intermediate values for the throttle represent intermediate + probabilities between 0% and 100% of unreliable packets being sent. The bandwidth + limits of the local and foreign hosts are taken into account to determine a + sensible limit for the throttle probability above which it should not raise even in + the best of conditions. + + @param peer peer to configure + @param interval interval, in milliseconds, over which to measure lowest mean RTT; the default value is ENET_PEER_PACKET_THROTTLE_INTERVAL. + @param acceleration rate at which to increase the throttle probability as mean RTT declines + @param deceleration rate at which to decrease the throttle probability as mean RTT increases +*/ +void +enet_peer_throttle_configure (ENetPeer * peer, enet_uint32 interval, enet_uint32 acceleration, enet_uint32 deceleration) +{ + ENetProtocol command; + + peer -> packetThrottleInterval = interval; + peer -> packetThrottleAcceleration = acceleration; + peer -> packetThrottleDeceleration = deceleration; + + command.header.command = ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + + command.throttleConfigure.packetThrottleInterval = ENET_HOST_TO_NET_32 (interval); + command.throttleConfigure.packetThrottleAcceleration = ENET_HOST_TO_NET_32 (acceleration); + command.throttleConfigure.packetThrottleDeceleration = ENET_HOST_TO_NET_32 (deceleration); + + enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0); +} + +int +enet_peer_throttle (ENetPeer * peer, enet_uint32 rtt) +{ + if (peer -> lastRoundTripTime <= peer -> lastRoundTripTimeVariance) + { + peer -> packetThrottle = peer -> packetThrottleLimit; + } + else + if (rtt < peer -> lastRoundTripTime) + { + peer -> packetThrottle += peer -> packetThrottleAcceleration; + + if (peer -> packetThrottle > peer -> packetThrottleLimit) + peer -> packetThrottle = peer -> packetThrottleLimit; + + return 1; + } + else + if (rtt > peer -> lastRoundTripTime + 2 * peer -> lastRoundTripTimeVariance) + { + if (peer -> packetThrottle > peer -> packetThrottleDeceleration) + peer -> packetThrottle -= peer -> packetThrottleDeceleration; + else + peer -> packetThrottle = 0; + + return -1; + } + + return 0; +} + +/** Queues a packet to be sent. + @param peer destination for the packet + @param channelID channel on which to send + @param packet packet to send + @retval 0 on success + @retval < 0 on failure +*/ +int +enet_peer_send (ENetPeer * peer, enet_uint8 channelID, ENetPacket * packet) +{ + ENetChannel * channel = & peer -> channels [channelID]; + ENetProtocol command; + size_t fragmentLength; + + if (peer -> state != ENET_PEER_STATE_CONNECTED || + channelID >= peer -> channelCount || + packet -> dataLength > peer -> host -> maximumPacketSize) + return -1; + + fragmentLength = peer -> mtu - sizeof (ENetProtocolHeader) - sizeof (ENetProtocolSendFragment); + if (peer -> host -> checksum != NULL) + fragmentLength -= sizeof(enet_uint32); + + if (packet -> dataLength > fragmentLength) + { + enet_uint32 fragmentCount = (packet -> dataLength + fragmentLength - 1) / fragmentLength, + fragmentNumber, + fragmentOffset; + enet_uint8 commandNumber; + enet_uint16 startSequenceNumber; + ENetList fragments; + ENetOutgoingCommand * fragment; + + if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT) + return -1; + + if ((packet -> flags & (ENET_PACKET_FLAG_RELIABLE | ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT)) == ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT && + channel -> outgoingUnreliableSequenceNumber < 0xFFFF) + { + commandNumber = ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT; + startSequenceNumber = ENET_HOST_TO_NET_16 (channel -> outgoingUnreliableSequenceNumber + 1); + } + else + { + commandNumber = ENET_PROTOCOL_COMMAND_SEND_FRAGMENT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + startSequenceNumber = ENET_HOST_TO_NET_16 (channel -> outgoingReliableSequenceNumber + 1); + } + + enet_list_clear (& fragments); + + for (fragmentNumber = 0, + fragmentOffset = 0; + fragmentOffset < packet -> dataLength; + ++ fragmentNumber, + fragmentOffset += fragmentLength) + { + if (packet -> dataLength - fragmentOffset < fragmentLength) + fragmentLength = packet -> dataLength - fragmentOffset; + + fragment = (ENetOutgoingCommand *) enet_malloc (sizeof (ENetOutgoingCommand)); + if (fragment == NULL) + { + while (! enet_list_empty (& fragments)) + { + fragment = (ENetOutgoingCommand *) enet_list_remove (enet_list_begin (& fragments)); + + enet_free (fragment); + } + + return -1; + } + + fragment -> fragmentOffset = fragmentOffset; + fragment -> fragmentLength = fragmentLength; + fragment -> packet = packet; + fragment -> command.header.command = commandNumber; + fragment -> command.header.channelID = channelID; + fragment -> command.sendFragment.startSequenceNumber = startSequenceNumber; + fragment -> command.sendFragment.dataLength = ENET_HOST_TO_NET_16 (fragmentLength); + fragment -> command.sendFragment.fragmentCount = ENET_HOST_TO_NET_32 (fragmentCount); + fragment -> command.sendFragment.fragmentNumber = ENET_HOST_TO_NET_32 (fragmentNumber); + fragment -> command.sendFragment.totalLength = ENET_HOST_TO_NET_32 (packet -> dataLength); + fragment -> command.sendFragment.fragmentOffset = ENET_NET_TO_HOST_32 (fragmentOffset); + + enet_list_insert (enet_list_end (& fragments), fragment); + } + + packet -> referenceCount += fragmentNumber; + + while (! enet_list_empty (& fragments)) + { + fragment = (ENetOutgoingCommand *) enet_list_remove (enet_list_begin (& fragments)); + + enet_peer_setup_outgoing_command (peer, fragment); + } + + return 0; + } + + command.header.channelID = channelID; + + if ((packet -> flags & (ENET_PACKET_FLAG_RELIABLE | ENET_PACKET_FLAG_UNSEQUENCED)) == ENET_PACKET_FLAG_UNSEQUENCED) + { + command.header.command = ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED | ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; + command.sendUnsequenced.dataLength = ENET_HOST_TO_NET_16 (packet -> dataLength); + } + else + if (packet -> flags & ENET_PACKET_FLAG_RELIABLE || channel -> outgoingUnreliableSequenceNumber >= 0xFFFF) + { + command.header.command = ENET_PROTOCOL_COMMAND_SEND_RELIABLE | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.sendReliable.dataLength = ENET_HOST_TO_NET_16 (packet -> dataLength); + } + else + { + command.header.command = ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE; + command.sendUnreliable.dataLength = ENET_HOST_TO_NET_16 (packet -> dataLength); + } + + if (enet_peer_queue_outgoing_command (peer, & command, packet, 0, packet -> dataLength) == NULL) + return -1; + + return 0; +} + +/** Attempts to dequeue any incoming queued packet. + @param peer peer to dequeue packets from + @param channelID holds the channel ID of the channel the packet was received on success + @returns a pointer to the packet, or NULL if there are no available incoming queued packets +*/ +ENetPacket * +enet_peer_receive (ENetPeer * peer, enet_uint8 * channelID) +{ + ENetIncomingCommand * incomingCommand; + ENetPacket * packet; + + if (enet_list_empty (& peer -> dispatchedCommands)) + return NULL; + + incomingCommand = (ENetIncomingCommand *) enet_list_remove (enet_list_begin (& peer -> dispatchedCommands)); + + if (channelID != NULL) + * channelID = incomingCommand -> command.header.channelID; + + packet = incomingCommand -> packet; + + -- packet -> referenceCount; + + if (incomingCommand -> fragments != NULL) + enet_free (incomingCommand -> fragments); + + enet_free (incomingCommand); + + peer -> totalWaitingData -= packet -> dataLength; + + return packet; +} + +static void +enet_peer_reset_outgoing_commands (ENetList * queue) +{ + ENetOutgoingCommand * outgoingCommand; + + while (! enet_list_empty (queue)) + { + outgoingCommand = (ENetOutgoingCommand *) enet_list_remove (enet_list_begin (queue)); + + if (outgoingCommand -> packet != NULL) + { + -- outgoingCommand -> packet -> referenceCount; + + if (outgoingCommand -> packet -> referenceCount == 0) + enet_packet_destroy (outgoingCommand -> packet); + } + + enet_free (outgoingCommand); + } +} + +static void +enet_peer_remove_incoming_commands (ENetList * queue, ENetListIterator startCommand, ENetListIterator endCommand) +{ + ENetListIterator currentCommand; + + for (currentCommand = startCommand; currentCommand != endCommand; ) + { + ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand; + + currentCommand = enet_list_next (currentCommand); + + enet_list_remove (& incomingCommand -> incomingCommandList); + + if (incomingCommand -> packet != NULL) + { + -- incomingCommand -> packet -> referenceCount; + + if (incomingCommand -> packet -> referenceCount == 0) + enet_packet_destroy (incomingCommand -> packet); + } + + if (incomingCommand -> fragments != NULL) + enet_free (incomingCommand -> fragments); + + enet_free (incomingCommand); + } +} + +static void +enet_peer_reset_incoming_commands (ENetList * queue) +{ + enet_peer_remove_incoming_commands(queue, enet_list_begin (queue), enet_list_end (queue)); +} + +void +enet_peer_reset_queues (ENetPeer * peer) +{ + ENetChannel * channel; + + if (peer -> needsDispatch) + { + enet_list_remove (& peer -> dispatchList); + + peer -> needsDispatch = 0; + } + + while (! enet_list_empty (& peer -> acknowledgements)) + enet_free (enet_list_remove (enet_list_begin (& peer -> acknowledgements))); + + enet_peer_reset_outgoing_commands (& peer -> sentReliableCommands); + enet_peer_reset_outgoing_commands (& peer -> sentUnreliableCommands); + enet_peer_reset_outgoing_commands (& peer -> outgoingReliableCommands); + enet_peer_reset_outgoing_commands (& peer -> outgoingUnreliableCommands); + enet_peer_reset_incoming_commands (& peer -> dispatchedCommands); + + if (peer -> channels != NULL && peer -> channelCount > 0) + { + for (channel = peer -> channels; + channel < & peer -> channels [peer -> channelCount]; + ++ channel) + { + enet_peer_reset_incoming_commands (& channel -> incomingReliableCommands); + enet_peer_reset_incoming_commands (& channel -> incomingUnreliableCommands); + } + + enet_free (peer -> channels); + } + + peer -> channels = NULL; + peer -> channelCount = 0; +} + +void +enet_peer_on_connect (ENetPeer * peer) +{ + if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) + { + if (peer -> incomingBandwidth != 0) + ++ peer -> host -> bandwidthLimitedPeers; + + ++ peer -> host -> connectedPeers; + } +} + +void +enet_peer_on_disconnect (ENetPeer * peer) +{ + if (peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) + { + if (peer -> incomingBandwidth != 0) + -- peer -> host -> bandwidthLimitedPeers; + + -- peer -> host -> connectedPeers; + } +} + +/** Forcefully disconnects a peer. + @param peer peer to forcefully disconnect + @remarks The foreign host represented by the peer is not notified of the disconnection and will timeout + on its connection to the local host. +*/ +void +enet_peer_reset (ENetPeer * peer) +{ + enet_peer_on_disconnect (peer); + + peer -> outgoingPeerID = ENET_PROTOCOL_MAXIMUM_PEER_ID; + peer -> connectID = 0; + + peer -> state = ENET_PEER_STATE_DISCONNECTED; + + peer -> incomingBandwidth = 0; + peer -> outgoingBandwidth = 0; + peer -> incomingBandwidthThrottleEpoch = 0; + peer -> outgoingBandwidthThrottleEpoch = 0; + peer -> incomingDataTotal = 0; + peer -> outgoingDataTotal = 0; + peer -> lastSendTime = 0; + peer -> lastReceiveTime = 0; + peer -> nextTimeout = 0; + peer -> earliestTimeout = 0; + peer -> packetLossEpoch = 0; + peer -> packetsSent = 0; + peer -> packetsLost = 0; + peer -> packetLoss = 0; + peer -> packetLossVariance = 0; + peer -> packetThrottle = ENET_PEER_DEFAULT_PACKET_THROTTLE; + peer -> packetThrottleLimit = ENET_PEER_PACKET_THROTTLE_SCALE; + peer -> packetThrottleCounter = 0; + peer -> packetThrottleEpoch = 0; + peer -> packetThrottleAcceleration = ENET_PEER_PACKET_THROTTLE_ACCELERATION; + peer -> packetThrottleDeceleration = ENET_PEER_PACKET_THROTTLE_DECELERATION; + peer -> packetThrottleInterval = ENET_PEER_PACKET_THROTTLE_INTERVAL; + peer -> pingInterval = ENET_PEER_PING_INTERVAL; + peer -> timeoutLimit = ENET_PEER_TIMEOUT_LIMIT; + peer -> timeoutMinimum = ENET_PEER_TIMEOUT_MINIMUM; + peer -> timeoutMaximum = ENET_PEER_TIMEOUT_MAXIMUM; + peer -> lastRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; + peer -> lowestRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; + peer -> lastRoundTripTimeVariance = 0; + peer -> highestRoundTripTimeVariance = 0; + peer -> roundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; + peer -> roundTripTimeVariance = 0; + peer -> mtu = peer -> host -> mtu; + peer -> reliableDataInTransit = 0; + peer -> outgoingReliableSequenceNumber = 0; + peer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + peer -> incomingUnsequencedGroup = 0; + peer -> outgoingUnsequencedGroup = 0; + peer -> eventData = 0; + peer -> totalWaitingData = 0; + + memset (peer -> unsequencedWindow, 0, sizeof (peer -> unsequencedWindow)); + + enet_peer_reset_queues (peer); +} + +/** Sends a ping request to a peer. + @param peer destination for the ping request + @remarks ping requests factor into the mean round trip time as designated by the + roundTripTime field in the ENetPeer structure. ENet automatically pings all connected + peers at regular intervals, however, this function may be called to ensure more + frequent ping requests. +*/ +void +enet_peer_ping (ENetPeer * peer) +{ + ENetProtocol command; + + if (peer -> state != ENET_PEER_STATE_CONNECTED) + return; + + command.header.command = ENET_PROTOCOL_COMMAND_PING | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + + enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0); +} + +/** Sets the interval at which pings will be sent to a peer. + + Pings are used both to monitor the liveness of the connection and also to dynamically + adjust the throttle during periods of low traffic so that the throttle has reasonable + responsiveness during traffic spikes. + + @param peer the peer to adjust + @param pingInterval the interval at which to send pings; defaults to ENET_PEER_PING_INTERVAL if 0 +*/ +void +enet_peer_ping_interval (ENetPeer * peer, enet_uint32 pingInterval) +{ + peer -> pingInterval = pingInterval ? pingInterval : ENET_PEER_PING_INTERVAL; +} + +/** Sets the timeout parameters for a peer. + + The timeout parameter control how and when a peer will timeout from a failure to acknowledge + reliable traffic. Timeout values use an exponential backoff mechanism, where if a reliable + packet is not acknowledge within some multiple of the average RTT plus a variance tolerance, + the timeout will be doubled until it reaches a set limit. If the timeout is thus at this + limit and reliable packets have been sent but not acknowledged within a certain minimum time + period, the peer will be disconnected. Alternatively, if reliable packets have been sent + but not acknowledged for a certain maximum time period, the peer will be disconnected regardless + of the current timeout limit value. + + @param peer the peer to adjust + @param timeoutLimit the timeout limit; defaults to ENET_PEER_TIMEOUT_LIMIT if 0 + @param timeoutMinimum the timeout minimum; defaults to ENET_PEER_TIMEOUT_MINIMUM if 0 + @param timeoutMaximum the timeout maximum; defaults to ENET_PEER_TIMEOUT_MAXIMUM if 0 +*/ + +void +enet_peer_timeout (ENetPeer * peer, enet_uint32 timeoutLimit, enet_uint32 timeoutMinimum, enet_uint32 timeoutMaximum) +{ + peer -> timeoutLimit = timeoutLimit ? timeoutLimit : ENET_PEER_TIMEOUT_LIMIT; + peer -> timeoutMinimum = timeoutMinimum ? timeoutMinimum : ENET_PEER_TIMEOUT_MINIMUM; + peer -> timeoutMaximum = timeoutMaximum ? timeoutMaximum : ENET_PEER_TIMEOUT_MAXIMUM; +} + +/** Force an immediate disconnection from a peer. + @param peer peer to disconnect + @param data data describing the disconnection + @remarks No ENET_EVENT_DISCONNECT event will be generated. The foreign peer is not + guaranteed to receive the disconnect notification, and is reset immediately upon + return from this function. +*/ +void +enet_peer_disconnect_now (ENetPeer * peer, enet_uint32 data) +{ + ENetProtocol command; + + if (peer -> state == ENET_PEER_STATE_DISCONNECTED) + return; + + if (peer -> state != ENET_PEER_STATE_ZOMBIE && + peer -> state != ENET_PEER_STATE_DISCONNECTING) + { + enet_peer_reset_queues (peer); + + command.header.command = ENET_PROTOCOL_COMMAND_DISCONNECT | ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; + command.header.channelID = 0xFF; + command.disconnect.data = ENET_HOST_TO_NET_32 (data); + + enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0); + + enet_host_flush (peer -> host); + } + + enet_peer_reset (peer); +} + +/** Request a disconnection from a peer. + @param peer peer to request a disconnection + @param data data describing the disconnection + @remarks An ENET_EVENT_DISCONNECT event will be generated by enet_host_service() + once the disconnection is complete. +*/ +void +enet_peer_disconnect (ENetPeer * peer, enet_uint32 data) +{ + ENetProtocol command; + + if (peer -> state == ENET_PEER_STATE_DISCONNECTING || + peer -> state == ENET_PEER_STATE_DISCONNECTED || + peer -> state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT || + peer -> state == ENET_PEER_STATE_ZOMBIE) + return; + + enet_peer_reset_queues (peer); + + command.header.command = ENET_PROTOCOL_COMMAND_DISCONNECT; + command.header.channelID = 0xFF; + command.disconnect.data = ENET_HOST_TO_NET_32 (data); + + if (peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) + command.header.command |= ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + else + command.header.command |= ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; + + enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0); + + if (peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) + { + enet_peer_on_disconnect (peer); + + peer -> state = ENET_PEER_STATE_DISCONNECTING; + } + else + { + enet_host_flush (peer -> host); + enet_peer_reset (peer); + } +} + +/** Request a disconnection from a peer, but only after all queued outgoing packets are sent. + @param peer peer to request a disconnection + @param data data describing the disconnection + @remarks An ENET_EVENT_DISCONNECT event will be generated by enet_host_service() + once the disconnection is complete. +*/ +void +enet_peer_disconnect_later (ENetPeer * peer, enet_uint32 data) +{ + if ((peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) && + ! (enet_list_empty (& peer -> outgoingReliableCommands) && + enet_list_empty (& peer -> outgoingUnreliableCommands) && + enet_list_empty (& peer -> sentReliableCommands))) + { + peer -> state = ENET_PEER_STATE_DISCONNECT_LATER; + peer -> eventData = data; + } + else + enet_peer_disconnect (peer, data); +} + +ENetAcknowledgement * +enet_peer_queue_acknowledgement (ENetPeer * peer, const ENetProtocol * command, enet_uint16 sentTime) +{ + ENetAcknowledgement * acknowledgement; + + if (command -> header.channelID < peer -> channelCount) + { + ENetChannel * channel = & peer -> channels [command -> header.channelID]; + enet_uint16 reliableWindow = command -> header.reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE, + currentWindow = channel -> incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (command -> header.reliableSequenceNumber < channel -> incomingReliableSequenceNumber) + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + + if (reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1 && reliableWindow <= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS) + return NULL; + } + + acknowledgement = (ENetAcknowledgement *) enet_malloc (sizeof (ENetAcknowledgement)); + if (acknowledgement == NULL) + return NULL; + + peer -> outgoingDataTotal += sizeof (ENetProtocolAcknowledge); + + acknowledgement -> sentTime = sentTime; + acknowledgement -> command = * command; + + enet_list_insert (enet_list_end (& peer -> acknowledgements), acknowledgement); + + return acknowledgement; +} + +void +enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoingCommand) +{ + ENetChannel * channel = & peer -> channels [outgoingCommand -> command.header.channelID]; + + peer -> outgoingDataTotal += enet_protocol_command_size (outgoingCommand -> command.header.command) + outgoingCommand -> fragmentLength; + + if (outgoingCommand -> command.header.channelID == 0xFF) + { + ++ peer -> outgoingReliableSequenceNumber; + + outgoingCommand -> reliableSequenceNumber = peer -> outgoingReliableSequenceNumber; + outgoingCommand -> unreliableSequenceNumber = 0; + } + else + if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) + { + ++ channel -> outgoingReliableSequenceNumber; + channel -> outgoingUnreliableSequenceNumber = 0; + + outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber; + outgoingCommand -> unreliableSequenceNumber = 0; + } + else + if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED) + { + ++ peer -> outgoingUnsequencedGroup; + + outgoingCommand -> reliableSequenceNumber = 0; + outgoingCommand -> unreliableSequenceNumber = 0; + } + else + { + if (outgoingCommand -> fragmentOffset == 0) + ++ channel -> outgoingUnreliableSequenceNumber; + + outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber; + outgoingCommand -> unreliableSequenceNumber = channel -> outgoingUnreliableSequenceNumber; + } + + outgoingCommand -> sendAttempts = 0; + outgoingCommand -> sentTime = 0; + outgoingCommand -> roundTripTimeout = 0; + outgoingCommand -> roundTripTimeoutLimit = 0; + outgoingCommand -> command.header.reliableSequenceNumber = ENET_HOST_TO_NET_16 (outgoingCommand -> reliableSequenceNumber); + + switch (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) + { + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: + outgoingCommand -> command.sendUnreliable.unreliableSequenceNumber = ENET_HOST_TO_NET_16 (outgoingCommand -> unreliableSequenceNumber); + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: + outgoingCommand -> command.sendUnsequenced.unsequencedGroup = ENET_HOST_TO_NET_16 (peer -> outgoingUnsequencedGroup); + break; + + default: + break; + } + + if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) + enet_list_insert (enet_list_end (& peer -> outgoingReliableCommands), outgoingCommand); + else + enet_list_insert (enet_list_end (& peer -> outgoingUnreliableCommands), outgoingCommand); +} + +ENetOutgoingCommand * +enet_peer_queue_outgoing_command (ENetPeer * peer, const ENetProtocol * command, ENetPacket * packet, enet_uint32 offset, enet_uint16 length) +{ + ENetOutgoingCommand * outgoingCommand = (ENetOutgoingCommand *) enet_malloc (sizeof (ENetOutgoingCommand)); + if (outgoingCommand == NULL) + return NULL; + + outgoingCommand -> command = * command; + outgoingCommand -> fragmentOffset = offset; + outgoingCommand -> fragmentLength = length; + outgoingCommand -> packet = packet; + if (packet != NULL) + ++ packet -> referenceCount; + + enet_peer_setup_outgoing_command (peer, outgoingCommand); + + return outgoingCommand; +} + +void +enet_peer_dispatch_incoming_unreliable_commands (ENetPeer * peer, ENetChannel * channel) +{ + ENetListIterator droppedCommand, startCommand, currentCommand; + + for (droppedCommand = startCommand = currentCommand = enet_list_begin (& channel -> incomingUnreliableCommands); + currentCommand != enet_list_end (& channel -> incomingUnreliableCommands); + currentCommand = enet_list_next (currentCommand)) + { + ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand; + + if ((incomingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED) + continue; + + if (incomingCommand -> reliableSequenceNumber == channel -> incomingReliableSequenceNumber) + { + if (incomingCommand -> fragmentsRemaining <= 0) + { + channel -> incomingUnreliableSequenceNumber = incomingCommand -> unreliableSequenceNumber; + continue; + } + + if (startCommand != currentCommand) + { + enet_list_move (enet_list_end (& peer -> dispatchedCommands), startCommand, enet_list_previous (currentCommand)); + + if (! peer -> needsDispatch) + { + enet_list_insert (enet_list_end (& peer -> host -> dispatchQueue), & peer -> dispatchList); + + peer -> needsDispatch = 1; + } + + droppedCommand = currentCommand; + } + else + if (droppedCommand != currentCommand) + droppedCommand = enet_list_previous (currentCommand); + } + else + { + enet_uint16 reliableWindow = incomingCommand -> reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE, + currentWindow = channel -> incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + if (incomingCommand -> reliableSequenceNumber < channel -> incomingReliableSequenceNumber) + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + if (reliableWindow >= currentWindow && reliableWindow < currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) + break; + + droppedCommand = enet_list_next (currentCommand); + + if (startCommand != currentCommand) + { + enet_list_move (enet_list_end (& peer -> dispatchedCommands), startCommand, enet_list_previous (currentCommand)); + + if (! peer -> needsDispatch) + { + enet_list_insert (enet_list_end (& peer -> host -> dispatchQueue), & peer -> dispatchList); + + peer -> needsDispatch = 1; + } + } + } + + startCommand = enet_list_next (currentCommand); + } + + if (startCommand != currentCommand) + { + enet_list_move (enet_list_end (& peer -> dispatchedCommands), startCommand, enet_list_previous (currentCommand)); + + if (! peer -> needsDispatch) + { + enet_list_insert (enet_list_end (& peer -> host -> dispatchQueue), & peer -> dispatchList); + + peer -> needsDispatch = 1; + } + + droppedCommand = currentCommand; + } + + enet_peer_remove_incoming_commands (& channel -> incomingUnreliableCommands, enet_list_begin (& channel -> incomingUnreliableCommands), droppedCommand); +} + +void +enet_peer_dispatch_incoming_reliable_commands (ENetPeer * peer, ENetChannel * channel) +{ + ENetListIterator currentCommand; + + for (currentCommand = enet_list_begin (& channel -> incomingReliableCommands); + currentCommand != enet_list_end (& channel -> incomingReliableCommands); + currentCommand = enet_list_next (currentCommand)) + { + ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand; + + if (incomingCommand -> fragmentsRemaining > 0 || + incomingCommand -> reliableSequenceNumber != (enet_uint16) (channel -> incomingReliableSequenceNumber + 1)) + break; + + channel -> incomingReliableSequenceNumber = incomingCommand -> reliableSequenceNumber; + + if (incomingCommand -> fragmentCount > 0) + channel -> incomingReliableSequenceNumber += incomingCommand -> fragmentCount - 1; + } + + if (currentCommand == enet_list_begin (& channel -> incomingReliableCommands)) + return; + + channel -> incomingUnreliableSequenceNumber = 0; + + enet_list_move (enet_list_end (& peer -> dispatchedCommands), enet_list_begin (& channel -> incomingReliableCommands), enet_list_previous (currentCommand)); + + if (! peer -> needsDispatch) + { + enet_list_insert (enet_list_end (& peer -> host -> dispatchQueue), & peer -> dispatchList); + + peer -> needsDispatch = 1; + } + + if (! enet_list_empty (& channel -> incomingUnreliableCommands)) + enet_peer_dispatch_incoming_unreliable_commands (peer, channel); +} + +ENetIncomingCommand * +enet_peer_queue_incoming_command (ENetPeer * peer, const ENetProtocol * command, const void * data, size_t dataLength, enet_uint32 flags, enet_uint32 fragmentCount) +{ + static ENetIncomingCommand dummyCommand; + + ENetChannel * channel = & peer -> channels [command -> header.channelID]; + enet_uint32 unreliableSequenceNumber = 0, reliableSequenceNumber = 0; + enet_uint16 reliableWindow, currentWindow; + ENetIncomingCommand * incomingCommand; + ENetListIterator currentCommand; + ENetPacket * packet = NULL; + + if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) + goto discardCommand; + + if ((command -> header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED) + { + reliableSequenceNumber = command -> header.reliableSequenceNumber; + reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + currentWindow = channel -> incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (reliableSequenceNumber < channel -> incomingReliableSequenceNumber) + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + + if (reliableWindow < currentWindow || reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) + goto discardCommand; + } + + switch (command -> header.command & ENET_PROTOCOL_COMMAND_MASK) + { + case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: + case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: + if (reliableSequenceNumber == channel -> incomingReliableSequenceNumber) + goto discardCommand; + + for (currentCommand = enet_list_previous (enet_list_end (& channel -> incomingReliableCommands)); + currentCommand != enet_list_end (& channel -> incomingReliableCommands); + currentCommand = enet_list_previous (currentCommand)) + { + incomingCommand = (ENetIncomingCommand *) currentCommand; + + if (reliableSequenceNumber >= channel -> incomingReliableSequenceNumber) + { + if (incomingCommand -> reliableSequenceNumber < channel -> incomingReliableSequenceNumber) + continue; + } + else + if (incomingCommand -> reliableSequenceNumber >= channel -> incomingReliableSequenceNumber) + break; + + if (incomingCommand -> reliableSequenceNumber <= reliableSequenceNumber) + { + if (incomingCommand -> reliableSequenceNumber < reliableSequenceNumber) + break; + + goto discardCommand; + } + } + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT: + unreliableSequenceNumber = ENET_NET_TO_HOST_16 (command -> sendUnreliable.unreliableSequenceNumber); + + if (reliableSequenceNumber == channel -> incomingReliableSequenceNumber && + unreliableSequenceNumber <= channel -> incomingUnreliableSequenceNumber) + goto discardCommand; + + for (currentCommand = enet_list_previous (enet_list_end (& channel -> incomingUnreliableCommands)); + currentCommand != enet_list_end (& channel -> incomingUnreliableCommands); + currentCommand = enet_list_previous (currentCommand)) + { + incomingCommand = (ENetIncomingCommand *) currentCommand; + + if ((command -> header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED) + continue; + + if (reliableSequenceNumber >= channel -> incomingReliableSequenceNumber) + { + if (incomingCommand -> reliableSequenceNumber < channel -> incomingReliableSequenceNumber) + continue; + } + else + if (incomingCommand -> reliableSequenceNumber >= channel -> incomingReliableSequenceNumber) + break; + + if (incomingCommand -> reliableSequenceNumber < reliableSequenceNumber) + break; + + if (incomingCommand -> reliableSequenceNumber > reliableSequenceNumber) + continue; + + if (incomingCommand -> unreliableSequenceNumber <= unreliableSequenceNumber) + { + if (incomingCommand -> unreliableSequenceNumber < unreliableSequenceNumber) + break; + + goto discardCommand; + } + } + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: + currentCommand = enet_list_end (& channel -> incomingUnreliableCommands); + break; + + default: + goto discardCommand; + } + + if (peer -> totalWaitingData >= peer -> host -> maximumWaitingData) + goto notifyError; + + packet = enet_packet_create (data, dataLength, flags); + if (packet == NULL) + goto notifyError; + + incomingCommand = (ENetIncomingCommand *) enet_malloc (sizeof (ENetIncomingCommand)); + if (incomingCommand == NULL) + goto notifyError; + + incomingCommand -> reliableSequenceNumber = command -> header.reliableSequenceNumber; + incomingCommand -> unreliableSequenceNumber = unreliableSequenceNumber & 0xFFFF; + incomingCommand -> command = * command; + incomingCommand -> fragmentCount = fragmentCount; + incomingCommand -> fragmentsRemaining = fragmentCount; + incomingCommand -> packet = packet; + incomingCommand -> fragments = NULL; + + if (fragmentCount > 0) + { + if (fragmentCount <= ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT) + incomingCommand -> fragments = (enet_uint32 *) enet_malloc ((fragmentCount + 31) / 32 * sizeof (enet_uint32)); + if (incomingCommand -> fragments == NULL) + { + enet_free (incomingCommand); + + goto notifyError; + } + memset (incomingCommand -> fragments, 0, (fragmentCount + 31) / 32 * sizeof (enet_uint32)); + } + + if (packet != NULL) + { + ++ packet -> referenceCount; + + peer -> totalWaitingData += packet -> dataLength; + } + + enet_list_insert (enet_list_next (currentCommand), incomingCommand); + + switch (command -> header.command & ENET_PROTOCOL_COMMAND_MASK) + { + case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: + case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: + enet_peer_dispatch_incoming_reliable_commands (peer, channel); + break; + + default: + enet_peer_dispatch_incoming_unreliable_commands (peer, channel); + break; + } + + return incomingCommand; + +discardCommand: + if (fragmentCount > 0) + goto notifyError; + + if (packet != NULL && packet -> referenceCount == 0) + enet_packet_destroy (packet); + + return & dummyCommand; + +notifyError: + if (packet != NULL && packet -> referenceCount == 0) + enet_packet_destroy (packet); + + return NULL; +} + +/** @} */ diff --git a/modules/enet/protocol.c b/modules/enet/protocol.c new file mode 100644 index 0000000000..4a2a4ed185 --- /dev/null +++ b/modules/enet/protocol.c @@ -0,0 +1,1914 @@ +/** + @file protocol.c + @brief ENet protocol functions +*/ +#include <stdio.h> +#include <string.h> +#define ENET_BUILDING_LIB 1 +#include "enet/utility.h" +#include "enet/time.h" +#include "enet/enet.h" + + +static size_t commandSizes [ENET_PROTOCOL_COMMAND_COUNT] = +{ + 0, + sizeof (ENetProtocolAcknowledge), + sizeof (ENetProtocolConnect), + sizeof (ENetProtocolVerifyConnect), + sizeof (ENetProtocolDisconnect), + sizeof (ENetProtocolPing), + sizeof (ENetProtocolSendReliable), + sizeof (ENetProtocolSendUnreliable), + sizeof (ENetProtocolSendFragment), + sizeof (ENetProtocolSendUnsequenced), + sizeof (ENetProtocolBandwidthLimit), + sizeof (ENetProtocolThrottleConfigure), + sizeof (ENetProtocolSendFragment) +}; + +size_t +enet_protocol_command_size (enet_uint8 commandNumber) +{ + return commandSizes [commandNumber & ENET_PROTOCOL_COMMAND_MASK]; +} + +static void +enet_protocol_change_state (ENetHost * host, ENetPeer * peer, ENetPeerState state) +{ + if (state == ENET_PEER_STATE_CONNECTED || state == ENET_PEER_STATE_DISCONNECT_LATER) + enet_peer_on_connect (peer); + else + enet_peer_on_disconnect (peer); + + peer -> state = state; +} + +static void +enet_protocol_dispatch_state (ENetHost * host, ENetPeer * peer, ENetPeerState state) +{ + enet_protocol_change_state (host, peer, state); + + if (! peer -> needsDispatch) + { + enet_list_insert (enet_list_end (& host -> dispatchQueue), & peer -> dispatchList); + + peer -> needsDispatch = 1; + } +} + +static int +enet_protocol_dispatch_incoming_commands (ENetHost * host, ENetEvent * event) +{ + while (! enet_list_empty (& host -> dispatchQueue)) + { + ENetPeer * peer = (ENetPeer *) enet_list_remove (enet_list_begin (& host -> dispatchQueue)); + + peer -> needsDispatch = 0; + + switch (peer -> state) + { + case ENET_PEER_STATE_CONNECTION_PENDING: + case ENET_PEER_STATE_CONNECTION_SUCCEEDED: + enet_protocol_change_state (host, peer, ENET_PEER_STATE_CONNECTED); + + event -> type = ENET_EVENT_TYPE_CONNECT; + event -> peer = peer; + event -> data = peer -> eventData; + + return 1; + + case ENET_PEER_STATE_ZOMBIE: + host -> recalculateBandwidthLimits = 1; + + event -> type = ENET_EVENT_TYPE_DISCONNECT; + event -> peer = peer; + event -> data = peer -> eventData; + + enet_peer_reset (peer); + + return 1; + + case ENET_PEER_STATE_CONNECTED: + if (enet_list_empty (& peer -> dispatchedCommands)) + continue; + + event -> packet = enet_peer_receive (peer, & event -> channelID); + if (event -> packet == NULL) + continue; + + event -> type = ENET_EVENT_TYPE_RECEIVE; + event -> peer = peer; + + if (! enet_list_empty (& peer -> dispatchedCommands)) + { + peer -> needsDispatch = 1; + + enet_list_insert (enet_list_end (& host -> dispatchQueue), & peer -> dispatchList); + } + + return 1; + + default: + break; + } + } + + return 0; +} + +static void +enet_protocol_notify_connect (ENetHost * host, ENetPeer * peer, ENetEvent * event) +{ + host -> recalculateBandwidthLimits = 1; + + if (event != NULL) + { + enet_protocol_change_state (host, peer, ENET_PEER_STATE_CONNECTED); + + event -> type = ENET_EVENT_TYPE_CONNECT; + event -> peer = peer; + event -> data = peer -> eventData; + } + else + enet_protocol_dispatch_state (host, peer, peer -> state == ENET_PEER_STATE_CONNECTING ? ENET_PEER_STATE_CONNECTION_SUCCEEDED : ENET_PEER_STATE_CONNECTION_PENDING); +} + +static void +enet_protocol_notify_disconnect (ENetHost * host, ENetPeer * peer, ENetEvent * event) +{ + if (peer -> state >= ENET_PEER_STATE_CONNECTION_PENDING) + host -> recalculateBandwidthLimits = 1; + + if (peer -> state != ENET_PEER_STATE_CONNECTING && peer -> state < ENET_PEER_STATE_CONNECTION_SUCCEEDED) + enet_peer_reset (peer); + else + if (event != NULL) + { + event -> type = ENET_EVENT_TYPE_DISCONNECT; + event -> peer = peer; + event -> data = 0; + + enet_peer_reset (peer); + } + else + { + peer -> eventData = 0; + + enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE); + } +} + +static void +enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer) +{ + ENetOutgoingCommand * outgoingCommand; + + while (! enet_list_empty (& peer -> sentUnreliableCommands)) + { + outgoingCommand = (ENetOutgoingCommand *) enet_list_front (& peer -> sentUnreliableCommands); + + enet_list_remove (& outgoingCommand -> outgoingCommandList); + + if (outgoingCommand -> packet != NULL) + { + -- outgoingCommand -> packet -> referenceCount; + + if (outgoingCommand -> packet -> referenceCount == 0) + { + outgoingCommand -> packet -> flags |= ENET_PACKET_FLAG_SENT; + + enet_packet_destroy (outgoingCommand -> packet); + } + } + + enet_free (outgoingCommand); + } +} + +static ENetProtocolCommand +enet_protocol_remove_sent_reliable_command (ENetPeer * peer, enet_uint16 reliableSequenceNumber, enet_uint8 channelID) +{ + ENetOutgoingCommand * outgoingCommand = NULL; + ENetListIterator currentCommand; + ENetProtocolCommand commandNumber; + int wasSent = 1; + + for (currentCommand = enet_list_begin (& peer -> sentReliableCommands); + currentCommand != enet_list_end (& peer -> sentReliableCommands); + currentCommand = enet_list_next (currentCommand)) + { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + if (outgoingCommand -> reliableSequenceNumber == reliableSequenceNumber && + outgoingCommand -> command.header.channelID == channelID) + break; + } + + if (currentCommand == enet_list_end (& peer -> sentReliableCommands)) + { + for (currentCommand = enet_list_begin (& peer -> outgoingReliableCommands); + currentCommand != enet_list_end (& peer -> outgoingReliableCommands); + currentCommand = enet_list_next (currentCommand)) + { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + if (outgoingCommand -> sendAttempts < 1) return ENET_PROTOCOL_COMMAND_NONE; + + if (outgoingCommand -> reliableSequenceNumber == reliableSequenceNumber && + outgoingCommand -> command.header.channelID == channelID) + break; + } + + if (currentCommand == enet_list_end (& peer -> outgoingReliableCommands)) + return ENET_PROTOCOL_COMMAND_NONE; + + wasSent = 0; + } + + if (outgoingCommand == NULL) + return ENET_PROTOCOL_COMMAND_NONE; + + if (channelID < peer -> channelCount) + { + ENetChannel * channel = & peer -> channels [channelID]; + enet_uint16 reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + if (channel -> reliableWindows [reliableWindow] > 0) + { + -- channel -> reliableWindows [reliableWindow]; + if (! channel -> reliableWindows [reliableWindow]) + channel -> usedReliableWindows &= ~ (1 << reliableWindow); + } + } + + commandNumber = (ENetProtocolCommand) (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK); + + enet_list_remove (& outgoingCommand -> outgoingCommandList); + + if (outgoingCommand -> packet != NULL) + { + if (wasSent) + peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength; + + -- outgoingCommand -> packet -> referenceCount; + + if (outgoingCommand -> packet -> referenceCount == 0) + { + outgoingCommand -> packet -> flags |= ENET_PACKET_FLAG_SENT; + + enet_packet_destroy (outgoingCommand -> packet); + } + } + + enet_free (outgoingCommand); + + if (enet_list_empty (& peer -> sentReliableCommands)) + return commandNumber; + + outgoingCommand = (ENetOutgoingCommand *) enet_list_front (& peer -> sentReliableCommands); + + peer -> nextTimeout = outgoingCommand -> sentTime + outgoingCommand -> roundTripTimeout; + + return commandNumber; +} + +static ENetPeer * +enet_protocol_handle_connect (ENetHost * host, ENetProtocolHeader * header, ENetProtocol * command) +{ + enet_uint8 incomingSessionID, outgoingSessionID; + enet_uint32 mtu, windowSize; + ENetChannel * channel; + size_t channelCount, duplicatePeers = 0; + ENetPeer * currentPeer, * peer = NULL; + ENetProtocol verifyCommand; + + channelCount = ENET_NET_TO_HOST_32 (command -> connect.channelCount); + + if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT || + channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) + return NULL; + + for (currentPeer = host -> peers; + currentPeer < & host -> peers [host -> peerCount]; + ++ currentPeer) + { + if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED) + { + if (peer == NULL) + peer = currentPeer; + } + else + if (currentPeer -> state != ENET_PEER_STATE_CONNECTING && + currentPeer -> address.host == host -> receivedAddress.host) + { + if (currentPeer -> address.port == host -> receivedAddress.port && + currentPeer -> connectID == command -> connect.connectID) + return NULL; + + ++ duplicatePeers; + } + } + + if (peer == NULL || duplicatePeers >= host -> duplicatePeers) + return NULL; + + if (channelCount > host -> channelLimit) + channelCount = host -> channelLimit; + peer -> channels = (ENetChannel *) enet_malloc (channelCount * sizeof (ENetChannel)); + if (peer -> channels == NULL) + return NULL; + peer -> channelCount = channelCount; + peer -> state = ENET_PEER_STATE_ACKNOWLEDGING_CONNECT; + peer -> connectID = command -> connect.connectID; + peer -> address = host -> receivedAddress; + peer -> outgoingPeerID = ENET_NET_TO_HOST_16 (command -> connect.outgoingPeerID); + peer -> incomingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.incomingBandwidth); + peer -> outgoingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.outgoingBandwidth); + peer -> packetThrottleInterval = ENET_NET_TO_HOST_32 (command -> connect.packetThrottleInterval); + peer -> packetThrottleAcceleration = ENET_NET_TO_HOST_32 (command -> connect.packetThrottleAcceleration); + peer -> packetThrottleDeceleration = ENET_NET_TO_HOST_32 (command -> connect.packetThrottleDeceleration); + peer -> eventData = ENET_NET_TO_HOST_32 (command -> connect.data); + + incomingSessionID = command -> connect.incomingSessionID == 0xFF ? peer -> outgoingSessionID : command -> connect.incomingSessionID; + incomingSessionID = (incomingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + if (incomingSessionID == peer -> outgoingSessionID) + incomingSessionID = (incomingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + peer -> outgoingSessionID = incomingSessionID; + + outgoingSessionID = command -> connect.outgoingSessionID == 0xFF ? peer -> incomingSessionID : command -> connect.outgoingSessionID; + outgoingSessionID = (outgoingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + if (outgoingSessionID == peer -> incomingSessionID) + outgoingSessionID = (outgoingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + peer -> incomingSessionID = outgoingSessionID; + + for (channel = peer -> channels; + channel < & peer -> channels [channelCount]; + ++ channel) + { + channel -> outgoingReliableSequenceNumber = 0; + channel -> outgoingUnreliableSequenceNumber = 0; + channel -> incomingReliableSequenceNumber = 0; + channel -> incomingUnreliableSequenceNumber = 0; + + enet_list_clear (& channel -> incomingReliableCommands); + enet_list_clear (& channel -> incomingUnreliableCommands); + + channel -> usedReliableWindows = 0; + memset (channel -> reliableWindows, 0, sizeof (channel -> reliableWindows)); + } + + mtu = ENET_NET_TO_HOST_32 (command -> connect.mtu); + + if (mtu < ENET_PROTOCOL_MINIMUM_MTU) + mtu = ENET_PROTOCOL_MINIMUM_MTU; + else + if (mtu > ENET_PROTOCOL_MAXIMUM_MTU) + mtu = ENET_PROTOCOL_MAXIMUM_MTU; + + peer -> mtu = mtu; + + if (host -> outgoingBandwidth == 0 && + peer -> incomingBandwidth == 0) + peer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + else + if (host -> outgoingBandwidth == 0 || + peer -> incomingBandwidth == 0) + peer -> windowSize = (ENET_MAX (host -> outgoingBandwidth, peer -> incomingBandwidth) / + ENET_PEER_WINDOW_SIZE_SCALE) * + ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else + peer -> windowSize = (ENET_MIN (host -> outgoingBandwidth, peer -> incomingBandwidth) / + ENET_PEER_WINDOW_SIZE_SCALE) * + ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (peer -> windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + peer -> windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else + if (peer -> windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + peer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + if (host -> incomingBandwidth == 0) + windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + else + windowSize = (host -> incomingBandwidth / ENET_PEER_WINDOW_SIZE_SCALE) * + ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (windowSize > ENET_NET_TO_HOST_32 (command -> connect.windowSize)) + windowSize = ENET_NET_TO_HOST_32 (command -> connect.windowSize); + + if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else + if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + verifyCommand.header.command = ENET_PROTOCOL_COMMAND_VERIFY_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + verifyCommand.header.channelID = 0xFF; + verifyCommand.verifyConnect.outgoingPeerID = ENET_HOST_TO_NET_16 (peer -> incomingPeerID); + verifyCommand.verifyConnect.incomingSessionID = incomingSessionID; + verifyCommand.verifyConnect.outgoingSessionID = outgoingSessionID; + verifyCommand.verifyConnect.mtu = ENET_HOST_TO_NET_32 (peer -> mtu); + verifyCommand.verifyConnect.windowSize = ENET_HOST_TO_NET_32 (windowSize); + verifyCommand.verifyConnect.channelCount = ENET_HOST_TO_NET_32 (channelCount); + verifyCommand.verifyConnect.incomingBandwidth = ENET_HOST_TO_NET_32 (host -> incomingBandwidth); + verifyCommand.verifyConnect.outgoingBandwidth = ENET_HOST_TO_NET_32 (host -> outgoingBandwidth); + verifyCommand.verifyConnect.packetThrottleInterval = ENET_HOST_TO_NET_32 (peer -> packetThrottleInterval); + verifyCommand.verifyConnect.packetThrottleAcceleration = ENET_HOST_TO_NET_32 (peer -> packetThrottleAcceleration); + verifyCommand.verifyConnect.packetThrottleDeceleration = ENET_HOST_TO_NET_32 (peer -> packetThrottleDeceleration); + verifyCommand.verifyConnect.connectID = peer -> connectID; + + enet_peer_queue_outgoing_command (peer, & verifyCommand, NULL, 0, 0); + + return peer; +} + +static int +enet_protocol_handle_send_reliable (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData) +{ + size_t dataLength; + + if (command -> header.channelID >= peer -> channelCount || + (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + dataLength = ENET_NET_TO_HOST_16 (command -> sendReliable.dataLength); + * currentData += dataLength; + if (dataLength > host -> maximumPacketSize || + * currentData < host -> receivedData || + * currentData > & host -> receivedData [host -> receivedDataLength]) + return -1; + + if (enet_peer_queue_incoming_command (peer, command, (const enet_uint8 *) command + sizeof (ENetProtocolSendReliable), dataLength, ENET_PACKET_FLAG_RELIABLE, 0) == NULL) + return -1; + + return 0; +} + +static int +enet_protocol_handle_send_unsequenced (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData) +{ + enet_uint32 unsequencedGroup, index; + size_t dataLength; + + if (command -> header.channelID >= peer -> channelCount || + (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + dataLength = ENET_NET_TO_HOST_16 (command -> sendUnsequenced.dataLength); + * currentData += dataLength; + if (dataLength > host -> maximumPacketSize || + * currentData < host -> receivedData || + * currentData > & host -> receivedData [host -> receivedDataLength]) + return -1; + + unsequencedGroup = ENET_NET_TO_HOST_16 (command -> sendUnsequenced.unsequencedGroup); + index = unsequencedGroup % ENET_PEER_UNSEQUENCED_WINDOW_SIZE; + + if (unsequencedGroup < peer -> incomingUnsequencedGroup) + unsequencedGroup += 0x10000; + + if (unsequencedGroup >= (enet_uint32) peer -> incomingUnsequencedGroup + ENET_PEER_FREE_UNSEQUENCED_WINDOWS * ENET_PEER_UNSEQUENCED_WINDOW_SIZE) + return 0; + + unsequencedGroup &= 0xFFFF; + + if (unsequencedGroup - index != peer -> incomingUnsequencedGroup) + { + peer -> incomingUnsequencedGroup = unsequencedGroup - index; + + memset (peer -> unsequencedWindow, 0, sizeof (peer -> unsequencedWindow)); + } + else + if (peer -> unsequencedWindow [index / 32] & (1 << (index % 32))) + return 0; + + if (enet_peer_queue_incoming_command (peer, command, (const enet_uint8 *) command + sizeof (ENetProtocolSendUnsequenced), dataLength, ENET_PACKET_FLAG_UNSEQUENCED, 0) == NULL) + return -1; + + peer -> unsequencedWindow [index / 32] |= 1 << (index % 32); + + return 0; +} + +static int +enet_protocol_handle_send_unreliable (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData) +{ + size_t dataLength; + + if (command -> header.channelID >= peer -> channelCount || + (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + dataLength = ENET_NET_TO_HOST_16 (command -> sendUnreliable.dataLength); + * currentData += dataLength; + if (dataLength > host -> maximumPacketSize || + * currentData < host -> receivedData || + * currentData > & host -> receivedData [host -> receivedDataLength]) + return -1; + + if (enet_peer_queue_incoming_command (peer, command, (const enet_uint8 *) command + sizeof (ENetProtocolSendUnreliable), dataLength, 0, 0) == NULL) + return -1; + + return 0; +} + +static int +enet_protocol_handle_send_fragment (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData) +{ + enet_uint32 fragmentNumber, + fragmentCount, + fragmentOffset, + fragmentLength, + startSequenceNumber, + totalLength; + ENetChannel * channel; + enet_uint16 startWindow, currentWindow; + ENetListIterator currentCommand; + ENetIncomingCommand * startCommand = NULL; + + if (command -> header.channelID >= peer -> channelCount || + (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + fragmentLength = ENET_NET_TO_HOST_16 (command -> sendFragment.dataLength); + * currentData += fragmentLength; + if (fragmentLength > host -> maximumPacketSize || + * currentData < host -> receivedData || + * currentData > & host -> receivedData [host -> receivedDataLength]) + return -1; + + channel = & peer -> channels [command -> header.channelID]; + startSequenceNumber = ENET_NET_TO_HOST_16 (command -> sendFragment.startSequenceNumber); + startWindow = startSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + currentWindow = channel -> incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (startSequenceNumber < channel -> incomingReliableSequenceNumber) + startWindow += ENET_PEER_RELIABLE_WINDOWS; + + if (startWindow < currentWindow || startWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) + return 0; + + fragmentNumber = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentNumber); + fragmentCount = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentCount); + fragmentOffset = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentOffset); + totalLength = ENET_NET_TO_HOST_32 (command -> sendFragment.totalLength); + + if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT || + fragmentNumber >= fragmentCount || + totalLength > host -> maximumPacketSize || + fragmentOffset >= totalLength || + fragmentLength > totalLength - fragmentOffset) + return -1; + + for (currentCommand = enet_list_previous (enet_list_end (& channel -> incomingReliableCommands)); + currentCommand != enet_list_end (& channel -> incomingReliableCommands); + currentCommand = enet_list_previous (currentCommand)) + { + ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand; + + if (startSequenceNumber >= channel -> incomingReliableSequenceNumber) + { + if (incomingCommand -> reliableSequenceNumber < channel -> incomingReliableSequenceNumber) + continue; + } + else + if (incomingCommand -> reliableSequenceNumber >= channel -> incomingReliableSequenceNumber) + break; + + if (incomingCommand -> reliableSequenceNumber <= startSequenceNumber) + { + if (incomingCommand -> reliableSequenceNumber < startSequenceNumber) + break; + + if ((incomingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_FRAGMENT || + totalLength != incomingCommand -> packet -> dataLength || + fragmentCount != incomingCommand -> fragmentCount) + return -1; + + startCommand = incomingCommand; + break; + } + } + + if (startCommand == NULL) + { + ENetProtocol hostCommand = * command; + + hostCommand.header.reliableSequenceNumber = startSequenceNumber; + + startCommand = enet_peer_queue_incoming_command (peer, & hostCommand, NULL, totalLength, ENET_PACKET_FLAG_RELIABLE, fragmentCount); + if (startCommand == NULL) + return -1; + } + + if ((startCommand -> fragments [fragmentNumber / 32] & (1 << (fragmentNumber % 32))) == 0) + { + -- startCommand -> fragmentsRemaining; + + startCommand -> fragments [fragmentNumber / 32] |= (1 << (fragmentNumber % 32)); + + if (fragmentOffset + fragmentLength > startCommand -> packet -> dataLength) + fragmentLength = startCommand -> packet -> dataLength - fragmentOffset; + + memcpy (startCommand -> packet -> data + fragmentOffset, + (enet_uint8 *) command + sizeof (ENetProtocolSendFragment), + fragmentLength); + + if (startCommand -> fragmentsRemaining <= 0) + enet_peer_dispatch_incoming_reliable_commands (peer, channel); + } + + return 0; +} + +static int +enet_protocol_handle_send_unreliable_fragment (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData) +{ + enet_uint32 fragmentNumber, + fragmentCount, + fragmentOffset, + fragmentLength, + reliableSequenceNumber, + startSequenceNumber, + totalLength; + enet_uint16 reliableWindow, currentWindow; + ENetChannel * channel; + ENetListIterator currentCommand; + ENetIncomingCommand * startCommand = NULL; + + if (command -> header.channelID >= peer -> channelCount || + (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + fragmentLength = ENET_NET_TO_HOST_16 (command -> sendFragment.dataLength); + * currentData += fragmentLength; + if (fragmentLength > host -> maximumPacketSize || + * currentData < host -> receivedData || + * currentData > & host -> receivedData [host -> receivedDataLength]) + return -1; + + channel = & peer -> channels [command -> header.channelID]; + reliableSequenceNumber = command -> header.reliableSequenceNumber; + startSequenceNumber = ENET_NET_TO_HOST_16 (command -> sendFragment.startSequenceNumber); + + reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + currentWindow = channel -> incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (reliableSequenceNumber < channel -> incomingReliableSequenceNumber) + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + + if (reliableWindow < currentWindow || reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) + return 0; + + if (reliableSequenceNumber == channel -> incomingReliableSequenceNumber && + startSequenceNumber <= channel -> incomingUnreliableSequenceNumber) + return 0; + + fragmentNumber = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentNumber); + fragmentCount = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentCount); + fragmentOffset = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentOffset); + totalLength = ENET_NET_TO_HOST_32 (command -> sendFragment.totalLength); + + if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT || + fragmentNumber >= fragmentCount || + totalLength > host -> maximumPacketSize || + fragmentOffset >= totalLength || + fragmentLength > totalLength - fragmentOffset) + return -1; + + for (currentCommand = enet_list_previous (enet_list_end (& channel -> incomingUnreliableCommands)); + currentCommand != enet_list_end (& channel -> incomingUnreliableCommands); + currentCommand = enet_list_previous (currentCommand)) + { + ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand; + + if (reliableSequenceNumber >= channel -> incomingReliableSequenceNumber) + { + if (incomingCommand -> reliableSequenceNumber < channel -> incomingReliableSequenceNumber) + continue; + } + else + if (incomingCommand -> reliableSequenceNumber >= channel -> incomingReliableSequenceNumber) + break; + + if (incomingCommand -> reliableSequenceNumber < reliableSequenceNumber) + break; + + if (incomingCommand -> reliableSequenceNumber > reliableSequenceNumber) + continue; + + if (incomingCommand -> unreliableSequenceNumber <= startSequenceNumber) + { + if (incomingCommand -> unreliableSequenceNumber < startSequenceNumber) + break; + + if ((incomingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT || + totalLength != incomingCommand -> packet -> dataLength || + fragmentCount != incomingCommand -> fragmentCount) + return -1; + + startCommand = incomingCommand; + break; + } + } + + if (startCommand == NULL) + { + startCommand = enet_peer_queue_incoming_command (peer, command, NULL, totalLength, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT, fragmentCount); + if (startCommand == NULL) + return -1; + } + + if ((startCommand -> fragments [fragmentNumber / 32] & (1 << (fragmentNumber % 32))) == 0) + { + -- startCommand -> fragmentsRemaining; + + startCommand -> fragments [fragmentNumber / 32] |= (1 << (fragmentNumber % 32)); + + if (fragmentOffset + fragmentLength > startCommand -> packet -> dataLength) + fragmentLength = startCommand -> packet -> dataLength - fragmentOffset; + + memcpy (startCommand -> packet -> data + fragmentOffset, + (enet_uint8 *) command + sizeof (ENetProtocolSendFragment), + fragmentLength); + + if (startCommand -> fragmentsRemaining <= 0) + enet_peer_dispatch_incoming_unreliable_commands (peer, channel); + } + + return 0; +} + +static int +enet_protocol_handle_ping (ENetHost * host, ENetPeer * peer, const ENetProtocol * command) +{ + if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) + return -1; + + return 0; +} + +static int +enet_protocol_handle_bandwidth_limit (ENetHost * host, ENetPeer * peer, const ENetProtocol * command) +{ + if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) + return -1; + + if (peer -> incomingBandwidth != 0) + -- host -> bandwidthLimitedPeers; + + peer -> incomingBandwidth = ENET_NET_TO_HOST_32 (command -> bandwidthLimit.incomingBandwidth); + peer -> outgoingBandwidth = ENET_NET_TO_HOST_32 (command -> bandwidthLimit.outgoingBandwidth); + + if (peer -> incomingBandwidth != 0) + ++ host -> bandwidthLimitedPeers; + + if (peer -> incomingBandwidth == 0 && host -> outgoingBandwidth == 0) + peer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + else + if (peer -> incomingBandwidth == 0 || host -> outgoingBandwidth == 0) + peer -> windowSize = (ENET_MAX (peer -> incomingBandwidth, host -> outgoingBandwidth) / + ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else + peer -> windowSize = (ENET_MIN (peer -> incomingBandwidth, host -> outgoingBandwidth) / + ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (peer -> windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + peer -> windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else + if (peer -> windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + peer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + return 0; +} + +static int +enet_protocol_handle_throttle_configure (ENetHost * host, ENetPeer * peer, const ENetProtocol * command) +{ + if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) + return -1; + + peer -> packetThrottleInterval = ENET_NET_TO_HOST_32 (command -> throttleConfigure.packetThrottleInterval); + peer -> packetThrottleAcceleration = ENET_NET_TO_HOST_32 (command -> throttleConfigure.packetThrottleAcceleration); + peer -> packetThrottleDeceleration = ENET_NET_TO_HOST_32 (command -> throttleConfigure.packetThrottleDeceleration); + + return 0; +} + +static int +enet_protocol_handle_disconnect (ENetHost * host, ENetPeer * peer, const ENetProtocol * command) +{ + if (peer -> state == ENET_PEER_STATE_DISCONNECTED || peer -> state == ENET_PEER_STATE_ZOMBIE || peer -> state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT) + return 0; + + enet_peer_reset_queues (peer); + + if (peer -> state == ENET_PEER_STATE_CONNECTION_SUCCEEDED || peer -> state == ENET_PEER_STATE_DISCONNECTING || peer -> state == ENET_PEER_STATE_CONNECTING) + enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE); + else + if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) + { + if (peer -> state == ENET_PEER_STATE_CONNECTION_PENDING) host -> recalculateBandwidthLimits = 1; + + enet_peer_reset (peer); + } + else + if (command -> header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) + enet_protocol_change_state (host, peer, ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT); + else + enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE); + + if (peer -> state != ENET_PEER_STATE_DISCONNECTED) + peer -> eventData = ENET_NET_TO_HOST_32 (command -> disconnect.data); + + return 0; +} + +static int +enet_protocol_handle_acknowledge (ENetHost * host, ENetEvent * event, ENetPeer * peer, const ENetProtocol * command) +{ + enet_uint32 roundTripTime, + receivedSentTime, + receivedReliableSequenceNumber; + ENetProtocolCommand commandNumber; + + if (peer -> state == ENET_PEER_STATE_DISCONNECTED || peer -> state == ENET_PEER_STATE_ZOMBIE) + return 0; + + receivedSentTime = ENET_NET_TO_HOST_16 (command -> acknowledge.receivedSentTime); + receivedSentTime |= host -> serviceTime & 0xFFFF0000; + if ((receivedSentTime & 0x8000) > (host -> serviceTime & 0x8000)) + receivedSentTime -= 0x10000; + + if (ENET_TIME_LESS (host -> serviceTime, receivedSentTime)) + return 0; + + peer -> lastReceiveTime = host -> serviceTime; + peer -> earliestTimeout = 0; + + roundTripTime = ENET_TIME_DIFFERENCE (host -> serviceTime, receivedSentTime); + + enet_peer_throttle (peer, roundTripTime); + + peer -> roundTripTimeVariance -= peer -> roundTripTimeVariance / 4; + + if (roundTripTime >= peer -> roundTripTime) + { + peer -> roundTripTime += (roundTripTime - peer -> roundTripTime) / 8; + peer -> roundTripTimeVariance += (roundTripTime - peer -> roundTripTime) / 4; + } + else + { + peer -> roundTripTime -= (peer -> roundTripTime - roundTripTime) / 8; + peer -> roundTripTimeVariance += (peer -> roundTripTime - roundTripTime) / 4; + } + + if (peer -> roundTripTime < peer -> lowestRoundTripTime) + peer -> lowestRoundTripTime = peer -> roundTripTime; + + if (peer -> roundTripTimeVariance > peer -> highestRoundTripTimeVariance) + peer -> highestRoundTripTimeVariance = peer -> roundTripTimeVariance; + + if (peer -> packetThrottleEpoch == 0 || + ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> packetThrottleEpoch) >= peer -> packetThrottleInterval) + { + peer -> lastRoundTripTime = peer -> lowestRoundTripTime; + peer -> lastRoundTripTimeVariance = peer -> highestRoundTripTimeVariance; + peer -> lowestRoundTripTime = peer -> roundTripTime; + peer -> highestRoundTripTimeVariance = peer -> roundTripTimeVariance; + peer -> packetThrottleEpoch = host -> serviceTime; + } + + receivedReliableSequenceNumber = ENET_NET_TO_HOST_16 (command -> acknowledge.receivedReliableSequenceNumber); + + commandNumber = enet_protocol_remove_sent_reliable_command (peer, receivedReliableSequenceNumber, command -> header.channelID); + + switch (peer -> state) + { + case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT: + if (commandNumber != ENET_PROTOCOL_COMMAND_VERIFY_CONNECT) + return -1; + + enet_protocol_notify_connect (host, peer, event); + break; + + case ENET_PEER_STATE_DISCONNECTING: + if (commandNumber != ENET_PROTOCOL_COMMAND_DISCONNECT) + return -1; + + enet_protocol_notify_disconnect (host, peer, event); + break; + + case ENET_PEER_STATE_DISCONNECT_LATER: + if (enet_list_empty (& peer -> outgoingReliableCommands) && + enet_list_empty (& peer -> outgoingUnreliableCommands) && + enet_list_empty (& peer -> sentReliableCommands)) + enet_peer_disconnect (peer, peer -> eventData); + break; + + default: + break; + } + + return 0; +} + +static int +enet_protocol_handle_verify_connect (ENetHost * host, ENetEvent * event, ENetPeer * peer, const ENetProtocol * command) +{ + enet_uint32 mtu, windowSize; + size_t channelCount; + + if (peer -> state != ENET_PEER_STATE_CONNECTING) + return 0; + + channelCount = ENET_NET_TO_HOST_32 (command -> verifyConnect.channelCount); + + if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT || channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT || + ENET_NET_TO_HOST_32 (command -> verifyConnect.packetThrottleInterval) != peer -> packetThrottleInterval || + ENET_NET_TO_HOST_32 (command -> verifyConnect.packetThrottleAcceleration) != peer -> packetThrottleAcceleration || + ENET_NET_TO_HOST_32 (command -> verifyConnect.packetThrottleDeceleration) != peer -> packetThrottleDeceleration || + command -> verifyConnect.connectID != peer -> connectID) + { + peer -> eventData = 0; + + enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE); + + return -1; + } + + enet_protocol_remove_sent_reliable_command (peer, 1, 0xFF); + + if (channelCount < peer -> channelCount) + peer -> channelCount = channelCount; + + peer -> outgoingPeerID = ENET_NET_TO_HOST_16 (command -> verifyConnect.outgoingPeerID); + peer -> incomingSessionID = command -> verifyConnect.incomingSessionID; + peer -> outgoingSessionID = command -> verifyConnect.outgoingSessionID; + + mtu = ENET_NET_TO_HOST_32 (command -> verifyConnect.mtu); + + if (mtu < ENET_PROTOCOL_MINIMUM_MTU) + mtu = ENET_PROTOCOL_MINIMUM_MTU; + else + if (mtu > ENET_PROTOCOL_MAXIMUM_MTU) + mtu = ENET_PROTOCOL_MAXIMUM_MTU; + + if (mtu < peer -> mtu) + peer -> mtu = mtu; + + windowSize = ENET_NET_TO_HOST_32 (command -> verifyConnect.windowSize); + + if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + if (windowSize < peer -> windowSize) + peer -> windowSize = windowSize; + + peer -> incomingBandwidth = ENET_NET_TO_HOST_32 (command -> verifyConnect.incomingBandwidth); + peer -> outgoingBandwidth = ENET_NET_TO_HOST_32 (command -> verifyConnect.outgoingBandwidth); + + enet_protocol_notify_connect (host, peer, event); + return 0; +} + +static int +enet_protocol_handle_incoming_commands (ENetHost * host, ENetEvent * event) +{ + ENetProtocolHeader * header; + ENetProtocol * command; + ENetPeer * peer; + enet_uint8 * currentData; + size_t headerSize; + enet_uint16 peerID, flags; + enet_uint8 sessionID; + + if (host -> receivedDataLength < (size_t) & ((ENetProtocolHeader *) 0) -> sentTime) + return 0; + + header = (ENetProtocolHeader *) host -> receivedData; + + peerID = ENET_NET_TO_HOST_16 (header -> peerID); + sessionID = (peerID & ENET_PROTOCOL_HEADER_SESSION_MASK) >> ENET_PROTOCOL_HEADER_SESSION_SHIFT; + flags = peerID & ENET_PROTOCOL_HEADER_FLAG_MASK; + peerID &= ~ (ENET_PROTOCOL_HEADER_FLAG_MASK | ENET_PROTOCOL_HEADER_SESSION_MASK); + + headerSize = (flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME ? sizeof (ENetProtocolHeader) : (size_t) & ((ENetProtocolHeader *) 0) -> sentTime); + if (host -> checksum != NULL) + headerSize += sizeof (enet_uint32); + + if (peerID == ENET_PROTOCOL_MAXIMUM_PEER_ID) + peer = NULL; + else + if (peerID >= host -> peerCount) + return 0; + else + { + peer = & host -> peers [peerID]; + + if (peer -> state == ENET_PEER_STATE_DISCONNECTED || + peer -> state == ENET_PEER_STATE_ZOMBIE || + ((host -> receivedAddress.host != peer -> address.host || + host -> receivedAddress.port != peer -> address.port) && + peer -> address.host != ENET_HOST_BROADCAST) || + (peer -> outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID && + sessionID != peer -> incomingSessionID)) + return 0; + } + + if (flags & ENET_PROTOCOL_HEADER_FLAG_COMPRESSED) + { + size_t originalSize; + if (host -> compressor.context == NULL || host -> compressor.decompress == NULL) + return 0; + + originalSize = host -> compressor.decompress (host -> compressor.context, + host -> receivedData + headerSize, + host -> receivedDataLength - headerSize, + host -> packetData [1] + headerSize, + sizeof (host -> packetData [1]) - headerSize); + if (originalSize <= 0 || originalSize > sizeof (host -> packetData [1]) - headerSize) + return 0; + + memcpy (host -> packetData [1], header, headerSize); + host -> receivedData = host -> packetData [1]; + host -> receivedDataLength = headerSize + originalSize; + } + + if (host -> checksum != NULL) + { + enet_uint32 * checksum = (enet_uint32 *) & host -> receivedData [headerSize - sizeof (enet_uint32)], + desiredChecksum = * checksum; + ENetBuffer buffer; + + * checksum = peer != NULL ? peer -> connectID : 0; + + buffer.data = host -> receivedData; + buffer.dataLength = host -> receivedDataLength; + + if (host -> checksum (& buffer, 1) != desiredChecksum) + return 0; + } + + if (peer != NULL) + { + peer -> address.host = host -> receivedAddress.host; + peer -> address.port = host -> receivedAddress.port; + peer -> incomingDataTotal += host -> receivedDataLength; + } + + currentData = host -> receivedData + headerSize; + + while (currentData < & host -> receivedData [host -> receivedDataLength]) + { + enet_uint8 commandNumber; + size_t commandSize; + + command = (ENetProtocol *) currentData; + + if (currentData + sizeof (ENetProtocolCommandHeader) > & host -> receivedData [host -> receivedDataLength]) + break; + + commandNumber = command -> header.command & ENET_PROTOCOL_COMMAND_MASK; + if (commandNumber >= ENET_PROTOCOL_COMMAND_COUNT) + break; + + commandSize = commandSizes [commandNumber]; + if (commandSize == 0 || currentData + commandSize > & host -> receivedData [host -> receivedDataLength]) + break; + + currentData += commandSize; + + if (peer == NULL && commandNumber != ENET_PROTOCOL_COMMAND_CONNECT) + break; + + command -> header.reliableSequenceNumber = ENET_NET_TO_HOST_16 (command -> header.reliableSequenceNumber); + + switch (commandNumber) + { + case ENET_PROTOCOL_COMMAND_ACKNOWLEDGE: + if (enet_protocol_handle_acknowledge (host, event, peer, command)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_CONNECT: + if (peer != NULL) + goto commandError; + peer = enet_protocol_handle_connect (host, header, command); + if (peer == NULL) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_VERIFY_CONNECT: + if (enet_protocol_handle_verify_connect (host, event, peer, command)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_DISCONNECT: + if (enet_protocol_handle_disconnect (host, peer, command)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_PING: + if (enet_protocol_handle_ping (host, peer, command)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: + if (enet_protocol_handle_send_reliable (host, peer, command, & currentData)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: + if (enet_protocol_handle_send_unreliable (host, peer, command, & currentData)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: + if (enet_protocol_handle_send_unsequenced (host, peer, command, & currentData)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: + if (enet_protocol_handle_send_fragment (host, peer, command, & currentData)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT: + if (enet_protocol_handle_bandwidth_limit (host, peer, command)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE: + if (enet_protocol_handle_throttle_configure (host, peer, command)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT: + if (enet_protocol_handle_send_unreliable_fragment (host, peer, command, & currentData)) + goto commandError; + break; + + default: + goto commandError; + } + + if (peer != NULL && + (command -> header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) != 0) + { + enet_uint16 sentTime; + + if (! (flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME)) + break; + + sentTime = ENET_NET_TO_HOST_16 (header -> sentTime); + + switch (peer -> state) + { + case ENET_PEER_STATE_DISCONNECTING: + case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT: + case ENET_PEER_STATE_DISCONNECTED: + case ENET_PEER_STATE_ZOMBIE: + break; + + case ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT: + if ((command -> header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_DISCONNECT) + enet_peer_queue_acknowledgement (peer, command, sentTime); + break; + + default: + enet_peer_queue_acknowledgement (peer, command, sentTime); + break; + } + } + } + +commandError: + if (event != NULL && event -> type != ENET_EVENT_TYPE_NONE) + return 1; + + return 0; +} + +static int +enet_protocol_receive_incoming_commands (ENetHost * host, ENetEvent * event) +{ + int packets; + + for (packets = 0; packets < 256; ++ packets) + { + int receivedLength; + ENetBuffer buffer; + + buffer.data = host -> packetData [0]; + buffer.dataLength = sizeof (host -> packetData [0]); + + receivedLength = enet_socket_receive (host -> socket, + & host -> receivedAddress, + & buffer, + 1); + + if (receivedLength < 0) + return -1; + + if (receivedLength == 0) + return 0; + + host -> receivedData = host -> packetData [0]; + host -> receivedDataLength = receivedLength; + + host -> totalReceivedData += receivedLength; + host -> totalReceivedPackets ++; + + if (host -> intercept != NULL) + { + switch (host -> intercept (host, event)) + { + case 1: + if (event != NULL && event -> type != ENET_EVENT_TYPE_NONE) + return 1; + + continue; + + case -1: + return -1; + + default: + break; + } + } + + switch (enet_protocol_handle_incoming_commands (host, event)) + { + case 1: + return 1; + + case -1: + return -1; + + default: + break; + } + } + + return -1; +} + +static void +enet_protocol_send_acknowledgements (ENetHost * host, ENetPeer * peer) +{ + ENetProtocol * command = & host -> commands [host -> commandCount]; + ENetBuffer * buffer = & host -> buffers [host -> bufferCount]; + ENetAcknowledgement * acknowledgement; + ENetListIterator currentAcknowledgement; + enet_uint16 reliableSequenceNumber; + + currentAcknowledgement = enet_list_begin (& peer -> acknowledgements); + + while (currentAcknowledgement != enet_list_end (& peer -> acknowledgements)) + { + if (command >= & host -> commands [sizeof (host -> commands) / sizeof (ENetProtocol)] || + buffer >= & host -> buffers [sizeof (host -> buffers) / sizeof (ENetBuffer)] || + peer -> mtu - host -> packetSize < sizeof (ENetProtocolAcknowledge)) + { + host -> continueSending = 1; + + break; + } + + acknowledgement = (ENetAcknowledgement *) currentAcknowledgement; + + currentAcknowledgement = enet_list_next (currentAcknowledgement); + + buffer -> data = command; + buffer -> dataLength = sizeof (ENetProtocolAcknowledge); + + host -> packetSize += buffer -> dataLength; + + reliableSequenceNumber = ENET_HOST_TO_NET_16 (acknowledgement -> command.header.reliableSequenceNumber); + + command -> header.command = ENET_PROTOCOL_COMMAND_ACKNOWLEDGE; + command -> header.channelID = acknowledgement -> command.header.channelID; + command -> header.reliableSequenceNumber = reliableSequenceNumber; + command -> acknowledge.receivedReliableSequenceNumber = reliableSequenceNumber; + command -> acknowledge.receivedSentTime = ENET_HOST_TO_NET_16 (acknowledgement -> sentTime); + + if ((acknowledgement -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_DISCONNECT) + enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE); + + enet_list_remove (& acknowledgement -> acknowledgementList); + enet_free (acknowledgement); + + ++ command; + ++ buffer; + } + + host -> commandCount = command - host -> commands; + host -> bufferCount = buffer - host -> buffers; +} + +static void +enet_protocol_send_unreliable_outgoing_commands (ENetHost * host, ENetPeer * peer) +{ + ENetProtocol * command = & host -> commands [host -> commandCount]; + ENetBuffer * buffer = & host -> buffers [host -> bufferCount]; + ENetOutgoingCommand * outgoingCommand; + ENetListIterator currentCommand; + + currentCommand = enet_list_begin (& peer -> outgoingUnreliableCommands); + + while (currentCommand != enet_list_end (& peer -> outgoingUnreliableCommands)) + { + size_t commandSize; + + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + commandSize = commandSizes [outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK]; + + if (command >= & host -> commands [sizeof (host -> commands) / sizeof (ENetProtocol)] || + buffer + 1 >= & host -> buffers [sizeof (host -> buffers) / sizeof (ENetBuffer)] || + peer -> mtu - host -> packetSize < commandSize || + (outgoingCommand -> packet != NULL && + peer -> mtu - host -> packetSize < commandSize + outgoingCommand -> fragmentLength)) + { + host -> continueSending = 1; + + break; + } + + currentCommand = enet_list_next (currentCommand); + + if (outgoingCommand -> packet != NULL && outgoingCommand -> fragmentOffset == 0) + { + peer -> packetThrottleCounter += ENET_PEER_PACKET_THROTTLE_COUNTER; + peer -> packetThrottleCounter %= ENET_PEER_PACKET_THROTTLE_SCALE; + + if (peer -> packetThrottleCounter > peer -> packetThrottle) + { + enet_uint16 reliableSequenceNumber = outgoingCommand -> reliableSequenceNumber, + unreliableSequenceNumber = outgoingCommand -> unreliableSequenceNumber; + for (;;) + { + -- outgoingCommand -> packet -> referenceCount; + + if (outgoingCommand -> packet -> referenceCount == 0) + enet_packet_destroy (outgoingCommand -> packet); + + enet_list_remove (& outgoingCommand -> outgoingCommandList); + enet_free (outgoingCommand); + + if (currentCommand == enet_list_end (& peer -> outgoingUnreliableCommands)) + break; + + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + if (outgoingCommand -> reliableSequenceNumber != reliableSequenceNumber || + outgoingCommand -> unreliableSequenceNumber != unreliableSequenceNumber) + break; + + currentCommand = enet_list_next (currentCommand); + } + + continue; + } + } + + buffer -> data = command; + buffer -> dataLength = commandSize; + + host -> packetSize += buffer -> dataLength; + + * command = outgoingCommand -> command; + + enet_list_remove (& outgoingCommand -> outgoingCommandList); + + if (outgoingCommand -> packet != NULL) + { + ++ buffer; + + buffer -> data = outgoingCommand -> packet -> data + outgoingCommand -> fragmentOffset; + buffer -> dataLength = outgoingCommand -> fragmentLength; + + host -> packetSize += buffer -> dataLength; + + enet_list_insert (enet_list_end (& peer -> sentUnreliableCommands), outgoingCommand); + } + else + enet_free (outgoingCommand); + + ++ command; + ++ buffer; + } + + host -> commandCount = command - host -> commands; + host -> bufferCount = buffer - host -> buffers; + + if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER && + enet_list_empty (& peer -> outgoingReliableCommands) && + enet_list_empty (& peer -> outgoingUnreliableCommands) && + enet_list_empty (& peer -> sentReliableCommands)) + enet_peer_disconnect (peer, peer -> eventData); +} + +static int +enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * event) +{ + ENetOutgoingCommand * outgoingCommand; + ENetListIterator currentCommand, insertPosition; + + currentCommand = enet_list_begin (& peer -> sentReliableCommands); + insertPosition = enet_list_begin (& peer -> outgoingReliableCommands); + + while (currentCommand != enet_list_end (& peer -> sentReliableCommands)) + { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + currentCommand = enet_list_next (currentCommand); + + if (ENET_TIME_DIFFERENCE (host -> serviceTime, outgoingCommand -> sentTime) < outgoingCommand -> roundTripTimeout) + continue; + + if (peer -> earliestTimeout == 0 || + ENET_TIME_LESS (outgoingCommand -> sentTime, peer -> earliestTimeout)) + peer -> earliestTimeout = outgoingCommand -> sentTime; + + if (peer -> earliestTimeout != 0 && + (ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= peer -> timeoutMaximum || + (outgoingCommand -> roundTripTimeout >= outgoingCommand -> roundTripTimeoutLimit && + ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= peer -> timeoutMinimum))) + { + enet_protocol_notify_disconnect (host, peer, event); + + return 1; + } + + if (outgoingCommand -> packet != NULL) + peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength; + + ++ peer -> packetsLost; + + outgoingCommand -> roundTripTimeout *= 2; + + enet_list_insert (insertPosition, enet_list_remove (& outgoingCommand -> outgoingCommandList)); + + if (currentCommand == enet_list_begin (& peer -> sentReliableCommands) && + ! enet_list_empty (& peer -> sentReliableCommands)) + { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + peer -> nextTimeout = outgoingCommand -> sentTime + outgoingCommand -> roundTripTimeout; + } + } + + return 0; +} + +static int +enet_protocol_send_reliable_outgoing_commands (ENetHost * host, ENetPeer * peer) +{ + ENetProtocol * command = & host -> commands [host -> commandCount]; + ENetBuffer * buffer = & host -> buffers [host -> bufferCount]; + ENetOutgoingCommand * outgoingCommand; + ENetListIterator currentCommand; + ENetChannel *channel; + enet_uint16 reliableWindow; + size_t commandSize; + int windowExceeded = 0, windowWrap = 0, canPing = 1; + + currentCommand = enet_list_begin (& peer -> outgoingReliableCommands); + + while (currentCommand != enet_list_end (& peer -> outgoingReliableCommands)) + { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + channel = outgoingCommand -> command.header.channelID < peer -> channelCount ? & peer -> channels [outgoingCommand -> command.header.channelID] : NULL; + reliableWindow = outgoingCommand -> reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + if (channel != NULL) + { + if (! windowWrap && + outgoingCommand -> sendAttempts < 1 && + ! (outgoingCommand -> reliableSequenceNumber % ENET_PEER_RELIABLE_WINDOW_SIZE) && + (channel -> reliableWindows [(reliableWindow + ENET_PEER_RELIABLE_WINDOWS - 1) % ENET_PEER_RELIABLE_WINDOWS] >= ENET_PEER_RELIABLE_WINDOW_SIZE || + channel -> usedReliableWindows & ((((1 << ENET_PEER_FREE_RELIABLE_WINDOWS) - 1) << reliableWindow) | + (((1 << ENET_PEER_FREE_RELIABLE_WINDOWS) - 1) >> (ENET_PEER_RELIABLE_WINDOWS - reliableWindow))))) + windowWrap = 1; + if (windowWrap) + { + currentCommand = enet_list_next (currentCommand); + + continue; + } + } + + if (outgoingCommand -> packet != NULL) + { + if (! windowExceeded) + { + enet_uint32 windowSize = (peer -> packetThrottle * peer -> windowSize) / ENET_PEER_PACKET_THROTTLE_SCALE; + + if (peer -> reliableDataInTransit + outgoingCommand -> fragmentLength > ENET_MAX (windowSize, peer -> mtu)) + windowExceeded = 1; + } + if (windowExceeded) + { + currentCommand = enet_list_next (currentCommand); + + continue; + } + } + + canPing = 0; + + commandSize = commandSizes [outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK]; + if (command >= & host -> commands [sizeof (host -> commands) / sizeof (ENetProtocol)] || + buffer + 1 >= & host -> buffers [sizeof (host -> buffers) / sizeof (ENetBuffer)] || + peer -> mtu - host -> packetSize < commandSize || + (outgoingCommand -> packet != NULL && + (enet_uint16) (peer -> mtu - host -> packetSize) < (enet_uint16) (commandSize + outgoingCommand -> fragmentLength))) + { + host -> continueSending = 1; + + break; + } + + currentCommand = enet_list_next (currentCommand); + + if (channel != NULL && outgoingCommand -> sendAttempts < 1) + { + channel -> usedReliableWindows |= 1 << reliableWindow; + ++ channel -> reliableWindows [reliableWindow]; + } + + ++ outgoingCommand -> sendAttempts; + + if (outgoingCommand -> roundTripTimeout == 0) + { + outgoingCommand -> roundTripTimeout = peer -> roundTripTime + 4 * peer -> roundTripTimeVariance; + outgoingCommand -> roundTripTimeoutLimit = peer -> timeoutLimit * outgoingCommand -> roundTripTimeout; + } + + if (enet_list_empty (& peer -> sentReliableCommands)) + peer -> nextTimeout = host -> serviceTime + outgoingCommand -> roundTripTimeout; + + enet_list_insert (enet_list_end (& peer -> sentReliableCommands), + enet_list_remove (& outgoingCommand -> outgoingCommandList)); + + outgoingCommand -> sentTime = host -> serviceTime; + + buffer -> data = command; + buffer -> dataLength = commandSize; + + host -> packetSize += buffer -> dataLength; + host -> headerFlags |= ENET_PROTOCOL_HEADER_FLAG_SENT_TIME; + + * command = outgoingCommand -> command; + + if (outgoingCommand -> packet != NULL) + { + ++ buffer; + + buffer -> data = outgoingCommand -> packet -> data + outgoingCommand -> fragmentOffset; + buffer -> dataLength = outgoingCommand -> fragmentLength; + + host -> packetSize += outgoingCommand -> fragmentLength; + + peer -> reliableDataInTransit += outgoingCommand -> fragmentLength; + } + + ++ peer -> packetsSent; + + ++ command; + ++ buffer; + } + + host -> commandCount = command - host -> commands; + host -> bufferCount = buffer - host -> buffers; + + return canPing; +} + +static int +enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int checkForTimeouts) +{ + enet_uint8 headerData [sizeof (ENetProtocolHeader) + sizeof (enet_uint32)]; + ENetProtocolHeader * header = (ENetProtocolHeader *) headerData; + ENetPeer * currentPeer; + int sentLength; + size_t shouldCompress = 0; + + host -> continueSending = 1; + + while (host -> continueSending) + for (host -> continueSending = 0, + currentPeer = host -> peers; + currentPeer < & host -> peers [host -> peerCount]; + ++ currentPeer) + { + if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED || + currentPeer -> state == ENET_PEER_STATE_ZOMBIE) + continue; + + host -> headerFlags = 0; + host -> commandCount = 0; + host -> bufferCount = 1; + host -> packetSize = sizeof (ENetProtocolHeader); + + if (! enet_list_empty (& currentPeer -> acknowledgements)) + enet_protocol_send_acknowledgements (host, currentPeer); + + if (checkForTimeouts != 0 && + ! enet_list_empty (& currentPeer -> sentReliableCommands) && + ENET_TIME_GREATER_EQUAL (host -> serviceTime, currentPeer -> nextTimeout) && + enet_protocol_check_timeouts (host, currentPeer, event) == 1) + { + if (event != NULL && event -> type != ENET_EVENT_TYPE_NONE) + return 1; + else + continue; + } + + if ((enet_list_empty (& currentPeer -> outgoingReliableCommands) || + enet_protocol_send_reliable_outgoing_commands (host, currentPeer)) && + enet_list_empty (& currentPeer -> sentReliableCommands) && + ENET_TIME_DIFFERENCE (host -> serviceTime, currentPeer -> lastReceiveTime) >= currentPeer -> pingInterval && + currentPeer -> mtu - host -> packetSize >= sizeof (ENetProtocolPing)) + { + enet_peer_ping (currentPeer); + enet_protocol_send_reliable_outgoing_commands (host, currentPeer); + } + + if (! enet_list_empty (& currentPeer -> outgoingUnreliableCommands)) + enet_protocol_send_unreliable_outgoing_commands (host, currentPeer); + + if (host -> commandCount == 0) + continue; + + if (currentPeer -> packetLossEpoch == 0) + currentPeer -> packetLossEpoch = host -> serviceTime; + else + if (ENET_TIME_DIFFERENCE (host -> serviceTime, currentPeer -> packetLossEpoch) >= ENET_PEER_PACKET_LOSS_INTERVAL && + currentPeer -> packetsSent > 0) + { + enet_uint32 packetLoss = currentPeer -> packetsLost * ENET_PEER_PACKET_LOSS_SCALE / currentPeer -> packetsSent; + +#ifdef ENET_DEBUG + printf ("peer %u: %f%%+-%f%% packet loss, %u+-%u ms round trip time, %f%% throttle, %u/%u outgoing, %u/%u incoming\n", currentPeer -> incomingPeerID, currentPeer -> packetLoss / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> packetLossVariance / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> roundTripTime, currentPeer -> roundTripTimeVariance, currentPeer -> packetThrottle / (float) ENET_PEER_PACKET_THROTTLE_SCALE, enet_list_size (& currentPeer -> outgoingReliableCommands), enet_list_size (& currentPeer -> outgoingUnreliableCommands), currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingReliableCommands) : 0, currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingUnreliableCommands) : 0); +#endif + + currentPeer -> packetLossVariance -= currentPeer -> packetLossVariance / 4; + + if (packetLoss >= currentPeer -> packetLoss) + { + currentPeer -> packetLoss += (packetLoss - currentPeer -> packetLoss) / 8; + currentPeer -> packetLossVariance += (packetLoss - currentPeer -> packetLoss) / 4; + } + else + { + currentPeer -> packetLoss -= (currentPeer -> packetLoss - packetLoss) / 8; + currentPeer -> packetLossVariance += (currentPeer -> packetLoss - packetLoss) / 4; + } + + currentPeer -> packetLossEpoch = host -> serviceTime; + currentPeer -> packetsSent = 0; + currentPeer -> packetsLost = 0; + } + + host -> buffers -> data = headerData; + if (host -> headerFlags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME) + { + header -> sentTime = ENET_HOST_TO_NET_16 (host -> serviceTime & 0xFFFF); + + host -> buffers -> dataLength = sizeof (ENetProtocolHeader); + } + else + host -> buffers -> dataLength = (size_t) & ((ENetProtocolHeader *) 0) -> sentTime; + + shouldCompress = 0; + if (host -> compressor.context != NULL && host -> compressor.compress != NULL) + { + size_t originalSize = host -> packetSize - sizeof(ENetProtocolHeader), + compressedSize = host -> compressor.compress (host -> compressor.context, + & host -> buffers [1], host -> bufferCount - 1, + originalSize, + host -> packetData [1], + originalSize); + if (compressedSize > 0 && compressedSize < originalSize) + { + host -> headerFlags |= ENET_PROTOCOL_HEADER_FLAG_COMPRESSED; + shouldCompress = compressedSize; +#ifdef ENET_DEBUG_COMPRESS + printf ("peer %u: compressed %u -> %u (%u%%)\n", currentPeer -> incomingPeerID, originalSize, compressedSize, (compressedSize * 100) / originalSize); +#endif + } + } + + if (currentPeer -> outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID) + host -> headerFlags |= currentPeer -> outgoingSessionID << ENET_PROTOCOL_HEADER_SESSION_SHIFT; + header -> peerID = ENET_HOST_TO_NET_16 (currentPeer -> outgoingPeerID | host -> headerFlags); + if (host -> checksum != NULL) + { + enet_uint32 * checksum = (enet_uint32 *) & headerData [host -> buffers -> dataLength]; + * checksum = currentPeer -> outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID ? currentPeer -> connectID : 0; + host -> buffers -> dataLength += sizeof (enet_uint32); + * checksum = host -> checksum (host -> buffers, host -> bufferCount); + } + + if (shouldCompress > 0) + { + host -> buffers [1].data = host -> packetData [1]; + host -> buffers [1].dataLength = shouldCompress; + host -> bufferCount = 2; + } + + currentPeer -> lastSendTime = host -> serviceTime; + + sentLength = enet_socket_send (host -> socket, & currentPeer -> address, host -> buffers, host -> bufferCount); + + enet_protocol_remove_sent_unreliable_commands (currentPeer); + + if (sentLength < 0) + return -1; + + host -> totalSentData += sentLength; + host -> totalSentPackets ++; + } + + return 0; +} + +/** Sends any queued packets on the host specified to its designated peers. + + @param host host to flush + @remarks this function need only be used in circumstances where one wishes to send queued packets earlier than in a call to enet_host_service(). + @ingroup host +*/ +void +enet_host_flush (ENetHost * host) +{ + host -> serviceTime = enet_time_get (); + + enet_protocol_send_outgoing_commands (host, NULL, 0); +} + +/** Checks for any queued events on the host and dispatches one if available. + + @param host host to check for events + @param event an event structure where event details will be placed if available + @retval > 0 if an event was dispatched + @retval 0 if no events are available + @retval < 0 on failure + @ingroup host +*/ +int +enet_host_check_events (ENetHost * host, ENetEvent * event) +{ + if (event == NULL) return -1; + + event -> type = ENET_EVENT_TYPE_NONE; + event -> peer = NULL; + event -> packet = NULL; + + return enet_protocol_dispatch_incoming_commands (host, event); +} + +/** Waits for events on the host specified and shuttles packets between + the host and its peers. + + @param host host to service + @param event an event structure where event details will be placed if one occurs + if event == NULL then no events will be delivered + @param timeout number of milliseconds that ENet should wait for events + @retval > 0 if an event occurred within the specified time limit + @retval 0 if no event occurred + @retval < 0 on failure + @remarks enet_host_service should be called fairly regularly for adequate performance + @ingroup host +*/ +int +enet_host_service (ENetHost * host, ENetEvent * event, enet_uint32 timeout) +{ + enet_uint32 waitCondition; + + if (event != NULL) + { + event -> type = ENET_EVENT_TYPE_NONE; + event -> peer = NULL; + event -> packet = NULL; + + switch (enet_protocol_dispatch_incoming_commands (host, event)) + { + case 1: + return 1; + + case -1: +#ifdef ENET_DEBUG + perror ("Error dispatching incoming packets"); +#endif + + return -1; + + default: + break; + } + } + + host -> serviceTime = enet_time_get (); + + timeout += host -> serviceTime; + + do + { + if (ENET_TIME_DIFFERENCE (host -> serviceTime, host -> bandwidthThrottleEpoch) >= ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL) + enet_host_bandwidth_throttle (host); + + switch (enet_protocol_send_outgoing_commands (host, event, 1)) + { + case 1: + return 1; + + case -1: +#ifdef ENET_DEBUG + perror ("Error sending outgoing packets"); +#endif + + return -1; + + default: + break; + } + + switch (enet_protocol_receive_incoming_commands (host, event)) + { + case 1: + return 1; + + case -1: +#ifdef ENET_DEBUG + perror ("Error receiving incoming packets"); +#endif + + return -1; + + default: + break; + } + + switch (enet_protocol_send_outgoing_commands (host, event, 1)) + { + case 1: + return 1; + + case -1: +#ifdef ENET_DEBUG + perror ("Error sending outgoing packets"); +#endif + + return -1; + + default: + break; + } + + if (event != NULL) + { + switch (enet_protocol_dispatch_incoming_commands (host, event)) + { + case 1: + return 1; + + case -1: +#ifdef ENET_DEBUG + perror ("Error dispatching incoming packets"); +#endif + + return -1; + + default: + break; + } + } + + if (ENET_TIME_GREATER_EQUAL (host -> serviceTime, timeout)) + return 0; + + do + { + host -> serviceTime = enet_time_get (); + + if (ENET_TIME_GREATER_EQUAL (host -> serviceTime, timeout)) + return 0; + + waitCondition = ENET_SOCKET_WAIT_RECEIVE | ENET_SOCKET_WAIT_INTERRUPT; + + if (enet_socket_wait (host -> socket, & waitCondition, ENET_TIME_DIFFERENCE (timeout, host -> serviceTime)) != 0) + return -1; + } + while (waitCondition & ENET_SOCKET_WAIT_INTERRUPT); + + host -> serviceTime = enet_time_get (); + } while (waitCondition & ENET_SOCKET_WAIT_RECEIVE); + + return 0; +} + diff --git a/modules/enet/register_types.cpp b/modules/enet/register_types.cpp new file mode 100644 index 0000000000..630b76ced8 --- /dev/null +++ b/modules/enet/register_types.cpp @@ -0,0 +1,51 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" +#include "error_macros.h" +#include "networked_multiplayer_enet.h" + +static bool enet_ok=false; + +void register_enet_types() { + + if (enet_initialize() !=0 ) { + ERR_PRINT("ENet initialization failure"); + } else { + enet_ok=true; + } + + ObjectTypeDB::register_type<NetworkedMultiplayerENet>(); +} + +void unregister_enet_types() { + + if (enet_ok) + enet_deinitialize(); + +} diff --git a/modules/enet/register_types.h b/modules/enet/register_types.h new file mode 100644 index 0000000000..50f34dc67f --- /dev/null +++ b/modules/enet/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_enet_types(); +void unregister_enet_types(); diff --git a/modules/enet/unix.c b/modules/enet/unix.c new file mode 100644 index 0000000000..3138cc04b6 --- /dev/null +++ b/modules/enet/unix.c @@ -0,0 +1,616 @@ +/** + @file unix.c + @brief ENet Unix system specific functions +*/ +#ifndef _WIN32 + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <arpa/inet.h> +#include <netinet/tcp.h> +#include <netdb.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <time.h> + +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +//@godot: added this since enet takes them fromt he build system +#define HAS_POLL +#define HAS_FCNTL +#define HAS_SOCKLEN_T + + +#ifdef __APPLE__ +#ifdef HAS_POLL +#undef HAS_POLL +#endif +#ifndef HAS_FCNTL +#define HAS_FCNTL 1 +#endif +#ifndef HAS_INET_PTON +#define HAS_INET_PTON 1 +#endif +#ifndef HAS_INET_NTOP +#define HAS_INET_NTOP 1 +#endif +#ifndef HAS_MSGHDR_FLAGS +#define HAS_MSGHDR_FLAGS 1 +#endif +#ifndef HAS_SOCKLEN_T +#define HAS_SOCKLEN_T 1 +#endif +#ifndef HAS_GETADDRINFO +#define HAS_GETADDRINFO 1 +#endif +#ifndef HAS_GETNAMEINFO +#define HAS_GETNAMEINFO 1 +#endif +#endif + +#ifdef HAS_FCNTL +#include <fcntl.h> +#endif + +#ifdef HAS_POLL +#include <sys/poll.h> +#endif + +#ifndef HAS_SOCKLEN_T +typedef int socklen_t; +#endif + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +static enet_uint32 timeBase = 0; + +int +enet_initialize (void) +{ + return 0; +} + +void +enet_deinitialize (void) +{ +} + +enet_uint32 +enet_host_random_seed (void) +{ + return (enet_uint32) time (NULL); +} + +enet_uint32 +enet_time_get (void) +{ + struct timeval timeVal; + + gettimeofday (& timeVal, NULL); + + return timeVal.tv_sec * 1000 + timeVal.tv_usec / 1000 - timeBase; +} + +void +enet_time_set (enet_uint32 newTimeBase) +{ + struct timeval timeVal; + + gettimeofday (& timeVal, NULL); + + timeBase = timeVal.tv_sec * 1000 + timeVal.tv_usec / 1000 - newTimeBase; +} + +int +enet_address_set_host (ENetAddress * address, const char * name) +{ +#ifdef HAS_GETADDRINFO + struct addrinfo hints, * resultList = NULL, * result = NULL; + + memset (& hints, 0, sizeof (hints)); + hints.ai_family = AF_INET; + + if (getaddrinfo (name, NULL, NULL, & resultList) != 0) + return -1; + + for (result = resultList; result != NULL; result = result -> ai_next) + { + if (result -> ai_family == AF_INET && result -> ai_addr != NULL && result -> ai_addrlen >= sizeof (struct sockaddr_in)) + { + struct sockaddr_in * sin = (struct sockaddr_in *) result -> ai_addr; + + address -> host = sin -> sin_addr.s_addr; + + freeaddrinfo (resultList); + + return 0; + } + } + + if (resultList != NULL) + freeaddrinfo (resultList); +#else + struct hostent * hostEntry = NULL; +#ifdef HAS_GETHOSTBYNAME_R + struct hostent hostData; + char buffer [2048]; + int errnum; + +#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) + gethostbyname_r (name, & hostData, buffer, sizeof (buffer), & hostEntry, & errnum); +#else + hostEntry = gethostbyname_r (name, & hostData, buffer, sizeof (buffer), & errnum); +#endif +#else + hostEntry = gethostbyname (name); +#endif + + if (hostEntry != NULL && hostEntry -> h_addrtype == AF_INET) + { + address -> host = * (enet_uint32 *) hostEntry -> h_addr_list [0]; + + return 0; + } +#endif + +#ifdef HAS_INET_PTON + if (! inet_pton (AF_INET, name, & address -> host)) +#else + if (! inet_aton (name, (struct in_addr *) & address -> host)) +#endif + return -1; + + return 0; +} + +int +enet_address_get_host_ip (const ENetAddress * address, char * name, size_t nameLength) +{ +#ifdef HAS_INET_NTOP + if (inet_ntop (AF_INET, & address -> host, name, nameLength) == NULL) +#else + char * addr = inet_ntoa (* (struct in_addr *) & address -> host); + if (addr != NULL) + { + size_t addrLen = strlen(addr); + if (addrLen >= nameLength) + return -1; + memcpy (name, addr, addrLen + 1); + } + else +#endif + return -1; + return 0; +} + +int +enet_address_get_host (const ENetAddress * address, char * name, size_t nameLength) +{ +#ifdef HAS_GETNAMEINFO + struct sockaddr_in sin; + int err; + + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + + err = getnameinfo ((struct sockaddr *) & sin, sizeof (sin), name, nameLength, NULL, 0, NI_NAMEREQD); + if (! err) + { + if (name != NULL && nameLength > 0 && ! memchr (name, '\0', nameLength)) + return -1; + return 0; + } + if (err != EAI_NONAME) + return -1; +#else + struct in_addr in; + struct hostent * hostEntry = NULL; +#ifdef HAS_GETHOSTBYADDR_R + struct hostent hostData; + char buffer [2048]; + int errnum; + + in.s_addr = address -> host; + +#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) + gethostbyaddr_r ((char *) & in, sizeof (struct in_addr), AF_INET, & hostData, buffer, sizeof (buffer), & hostEntry, & errnum); +#else + hostEntry = gethostbyaddr_r ((char *) & in, sizeof (struct in_addr), AF_INET, & hostData, buffer, sizeof (buffer), & errnum); +#endif +#else + in.s_addr = address -> host; + + hostEntry = gethostbyaddr ((char *) & in, sizeof (struct in_addr), AF_INET); +#endif + + if (hostEntry != NULL) + { + size_t hostLen = strlen (hostEntry -> h_name); + if (hostLen >= nameLength) + return -1; + memcpy (name, hostEntry -> h_name, hostLen + 1); + return 0; + } +#endif + + return enet_address_get_host_ip (address, name, nameLength); +} + +int +enet_socket_bind (ENetSocket socket, const ENetAddress * address) +{ + struct sockaddr_in sin; + + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + + if (address != NULL) + { + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + } + else + { + sin.sin_port = 0; + sin.sin_addr.s_addr = INADDR_ANY; + } + + return bind (socket, + (struct sockaddr *) & sin, + sizeof (struct sockaddr_in)); +} + +int +enet_socket_get_address (ENetSocket socket, ENetAddress * address) +{ + struct sockaddr_in sin; + socklen_t sinLength = sizeof (struct sockaddr_in); + + if (getsockname (socket, (struct sockaddr *) & sin, & sinLength) == -1) + return -1; + + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + + return 0; +} + +int +enet_socket_listen (ENetSocket socket, int backlog) +{ + return listen (socket, backlog < 0 ? SOMAXCONN : backlog); +} + +ENetSocket +enet_socket_create (ENetSocketType type) +{ + return socket (PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0); +} + +int +enet_socket_set_option (ENetSocket socket, ENetSocketOption option, int value) +{ + int result = -1; + switch (option) + { + case ENET_SOCKOPT_NONBLOCK: +#ifdef HAS_FCNTL + result = fcntl (socket, F_SETFL, (value ? O_NONBLOCK : 0) | (fcntl (socket, F_GETFL) & ~O_NONBLOCK)); +#else + result = ioctl (socket, FIONBIO, & value); +#endif + break; + + case ENET_SOCKOPT_BROADCAST: + result = setsockopt (socket, SOL_SOCKET, SO_BROADCAST, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_REUSEADDR: + result = setsockopt (socket, SOL_SOCKET, SO_REUSEADDR, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_RCVBUF: + result = setsockopt (socket, SOL_SOCKET, SO_RCVBUF, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_SNDBUF: + result = setsockopt (socket, SOL_SOCKET, SO_SNDBUF, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_RCVTIMEO: + { + struct timeval timeVal; + timeVal.tv_sec = value / 1000; + timeVal.tv_usec = (value % 1000) * 1000; + result = setsockopt (socket, SOL_SOCKET, SO_RCVTIMEO, (char *) & timeVal, sizeof (struct timeval)); + break; + } + + case ENET_SOCKOPT_SNDTIMEO: + { + struct timeval timeVal; + timeVal.tv_sec = value / 1000; + timeVal.tv_usec = (value % 1000) * 1000; + result = setsockopt (socket, SOL_SOCKET, SO_SNDTIMEO, (char *) & timeVal, sizeof (struct timeval)); + break; + } + + case ENET_SOCKOPT_NODELAY: + result = setsockopt (socket, IPPROTO_TCP, TCP_NODELAY, (char *) & value, sizeof (int)); + break; + + default: + break; + } + return result == -1 ? -1 : 0; +} + +int +enet_socket_get_option (ENetSocket socket, ENetSocketOption option, int * value) +{ + int result = -1; + socklen_t len; + switch (option) + { + case ENET_SOCKOPT_ERROR: + len = sizeof (int); + result = getsockopt (socket, SOL_SOCKET, SO_ERROR, value, & len); + break; + + default: + break; + } + return result == -1 ? -1 : 0; +} + +int +enet_socket_connect (ENetSocket socket, const ENetAddress * address) +{ + struct sockaddr_in sin; + int result; + + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + + result = connect (socket, (struct sockaddr *) & sin, sizeof (struct sockaddr_in)); + if (result == -1 && errno == EINPROGRESS) + return 0; + + return result; +} + +ENetSocket +enet_socket_accept (ENetSocket socket, ENetAddress * address) +{ + int result; + struct sockaddr_in sin; + socklen_t sinLength = sizeof (struct sockaddr_in); + + result = accept (socket, + address != NULL ? (struct sockaddr *) & sin : NULL, + address != NULL ? & sinLength : NULL); + + if (result == -1) + return ENET_SOCKET_NULL; + + if (address != NULL) + { + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + } + + return result; +} + +int +enet_socket_shutdown (ENetSocket socket, ENetSocketShutdown how) +{ + return shutdown (socket, (int) how); +} + +void +enet_socket_destroy (ENetSocket socket) +{ + if (socket != -1) + close (socket); +} + +int +enet_socket_send (ENetSocket socket, + const ENetAddress * address, + const ENetBuffer * buffers, + size_t bufferCount) +{ + struct msghdr msgHdr; + struct sockaddr_in sin; + int sentLength; + + memset (& msgHdr, 0, sizeof (struct msghdr)); + + if (address != NULL) + { + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + + msgHdr.msg_name = & sin; + msgHdr.msg_namelen = sizeof (struct sockaddr_in); + } + + msgHdr.msg_iov = (struct iovec *) buffers; + msgHdr.msg_iovlen = bufferCount; + + sentLength = sendmsg (socket, & msgHdr, MSG_NOSIGNAL); + + if (sentLength == -1) + { + if (errno == EWOULDBLOCK) + return 0; + + return -1; + } + + return sentLength; +} + +int +enet_socket_receive (ENetSocket socket, + ENetAddress * address, + ENetBuffer * buffers, + size_t bufferCount) +{ + struct msghdr msgHdr; + struct sockaddr_in sin; + int recvLength; + + memset (& msgHdr, 0, sizeof (struct msghdr)); + + if (address != NULL) + { + msgHdr.msg_name = & sin; + msgHdr.msg_namelen = sizeof (struct sockaddr_in); + } + + msgHdr.msg_iov = (struct iovec *) buffers; + msgHdr.msg_iovlen = bufferCount; + + recvLength = recvmsg (socket, & msgHdr, MSG_NOSIGNAL); + + if (recvLength == -1) + { + if (errno == EWOULDBLOCK) + return 0; + + return -1; + } + +#ifdef HAS_MSGHDR_FLAGS + if (msgHdr.msg_flags & MSG_TRUNC) + return -1; +#endif + + if (address != NULL) + { + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + } + + return recvLength; +} + +int +enet_socketset_select (ENetSocket maxSocket, ENetSocketSet * readSet, ENetSocketSet * writeSet, enet_uint32 timeout) +{ + struct timeval timeVal; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + return select (maxSocket + 1, readSet, writeSet, NULL, & timeVal); +} + +int +enet_socket_wait (ENetSocket socket, enet_uint32 * condition, enet_uint32 timeout) +{ +#ifdef HAS_POLL + struct pollfd pollSocket; + int pollCount; + + pollSocket.fd = socket; + pollSocket.events = 0; + + if (* condition & ENET_SOCKET_WAIT_SEND) + pollSocket.events |= POLLOUT; + + if (* condition & ENET_SOCKET_WAIT_RECEIVE) + pollSocket.events |= POLLIN; + + pollCount = poll (& pollSocket, 1, timeout); + + if (pollCount < 0) + { + if (errno == EINTR && * condition & ENET_SOCKET_WAIT_INTERRUPT) + { + * condition = ENET_SOCKET_WAIT_INTERRUPT; + + return 0; + } + + return -1; + } + + * condition = ENET_SOCKET_WAIT_NONE; + + if (pollCount == 0) + return 0; + + if (pollSocket.revents & POLLOUT) + * condition |= ENET_SOCKET_WAIT_SEND; + + if (pollSocket.revents & POLLIN) + * condition |= ENET_SOCKET_WAIT_RECEIVE; + + return 0; +#else + fd_set readSet, writeSet; + struct timeval timeVal; + int selectCount; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + FD_ZERO (& readSet); + FD_ZERO (& writeSet); + + if (* condition & ENET_SOCKET_WAIT_SEND) + FD_SET (socket, & writeSet); + + if (* condition & ENET_SOCKET_WAIT_RECEIVE) + FD_SET (socket, & readSet); + + selectCount = select (socket + 1, & readSet, & writeSet, NULL, & timeVal); + + if (selectCount < 0) + { + if (errno == EINTR && * condition & ENET_SOCKET_WAIT_INTERRUPT) + { + * condition = ENET_SOCKET_WAIT_INTERRUPT; + + return 0; + } + + return -1; + } + + * condition = ENET_SOCKET_WAIT_NONE; + + if (selectCount == 0) + return 0; + + if (FD_ISSET (socket, & writeSet)) + * condition |= ENET_SOCKET_WAIT_SEND; + + if (FD_ISSET (socket, & readSet)) + * condition |= ENET_SOCKET_WAIT_RECEIVE; + + return 0; +#endif +} + +#endif + diff --git a/modules/enet/win32.c b/modules/enet/win32.c new file mode 100644 index 0000000000..d77fa9a49a --- /dev/null +++ b/modules/enet/win32.c @@ -0,0 +1,422 @@ +/** + @file win32.c + @brief ENet Win32 system specific functions +*/ +#ifdef _WIN32 + +#define ENET_BUILDING_LIB 0 +#include "enet/enet.h" +#include <windows.h> +#include <mmsystem.h> + +static enet_uint32 timeBase = 0; + +int +enet_initialize (void) +{ + WORD versionRequested = MAKEWORD (1, 1); + WSADATA wsaData; + + if (WSAStartup (versionRequested, & wsaData)) + return -1; + + if (LOBYTE (wsaData.wVersion) != 1|| + HIBYTE (wsaData.wVersion) != 1) + { + WSACleanup (); + + return -1; + } + + timeBeginPeriod (1); + + return 0; +} + +void +enet_deinitialize (void) +{ + timeEndPeriod (1); + + WSACleanup (); +} + +enet_uint32 +enet_host_random_seed (void) +{ + return (enet_uint32) timeGetTime (); +} + +enet_uint32 +enet_time_get (void) +{ + return (enet_uint32) timeGetTime () - timeBase; +} + +void +enet_time_set (enet_uint32 newTimeBase) +{ + timeBase = (enet_uint32) timeGetTime () - newTimeBase; +} + +int +enet_address_set_host (ENetAddress * address, const char * name) +{ + struct hostent * hostEntry; + + hostEntry = gethostbyname (name); + if (hostEntry == NULL || + hostEntry -> h_addrtype != AF_INET) + { + unsigned long host = inet_addr (name); + if (host == INADDR_NONE) + return -1; + address -> host = host; + return 0; + } + + address -> host = * (enet_uint32 *) hostEntry -> h_addr_list [0]; + + return 0; +} + +int +enet_address_get_host_ip (const ENetAddress * address, char * name, size_t nameLength) +{ + char * addr = inet_ntoa (* (struct in_addr *) & address -> host); + if (addr == NULL) + return -1; + else + { + size_t addrLen = strlen(addr); + if (addrLen >= nameLength) + return -1; + memcpy (name, addr, addrLen + 1); + } + return 0; +} + +int +enet_address_get_host (const ENetAddress * address, char * name, size_t nameLength) +{ + struct in_addr in; + struct hostent * hostEntry; + + in.s_addr = address -> host; + + hostEntry = gethostbyaddr ((char *) & in, sizeof (struct in_addr), AF_INET); + if (hostEntry == NULL) + return enet_address_get_host_ip (address, name, nameLength); + else + { + size_t hostLen = strlen (hostEntry -> h_name); + if (hostLen >= nameLength) + return -1; + memcpy (name, hostEntry -> h_name, hostLen + 1); + } + + return 0; +} + +int +enet_socket_bind (ENetSocket socket, const ENetAddress * address) +{ + struct sockaddr_in sin; + + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + + if (address != NULL) + { + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + } + else + { + sin.sin_port = 0; + sin.sin_addr.s_addr = INADDR_ANY; + } + + return bind (socket, + (struct sockaddr *) & sin, + sizeof (struct sockaddr_in)) == SOCKET_ERROR ? -1 : 0; +} + +int +enet_socket_get_address (ENetSocket socket, ENetAddress * address) +{ + struct sockaddr_in sin; + int sinLength = sizeof (struct sockaddr_in); + + if (getsockname (socket, (struct sockaddr *) & sin, & sinLength) == -1) + return -1; + + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + + return 0; +} + +int +enet_socket_listen (ENetSocket socket, int backlog) +{ + return listen (socket, backlog < 0 ? SOMAXCONN : backlog) == SOCKET_ERROR ? -1 : 0; +} + +ENetSocket +enet_socket_create (ENetSocketType type) +{ + return socket (PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0); +} + +int +enet_socket_set_option (ENetSocket socket, ENetSocketOption option, int value) +{ + int result = SOCKET_ERROR; + switch (option) + { + case ENET_SOCKOPT_NONBLOCK: + { + u_long nonBlocking = (u_long) value; + result = ioctlsocket (socket, FIONBIO, & nonBlocking); + break; + } + + case ENET_SOCKOPT_BROADCAST: + result = setsockopt (socket, SOL_SOCKET, SO_BROADCAST, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_REUSEADDR: + result = setsockopt (socket, SOL_SOCKET, SO_REUSEADDR, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_RCVBUF: + result = setsockopt (socket, SOL_SOCKET, SO_RCVBUF, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_SNDBUF: + result = setsockopt (socket, SOL_SOCKET, SO_SNDBUF, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_RCVTIMEO: + result = setsockopt (socket, SOL_SOCKET, SO_RCVTIMEO, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_SNDTIMEO: + result = setsockopt (socket, SOL_SOCKET, SO_SNDTIMEO, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_NODELAY: + result = setsockopt (socket, IPPROTO_TCP, TCP_NODELAY, (char *) & value, sizeof (int)); + break; + + default: + break; + } + return result == SOCKET_ERROR ? -1 : 0; +} + +int +enet_socket_get_option (ENetSocket socket, ENetSocketOption option, int * value) +{ + int result = SOCKET_ERROR, len; + switch (option) + { + case ENET_SOCKOPT_ERROR: + len = sizeof(int); + result = getsockopt (socket, SOL_SOCKET, SO_ERROR, (char *) value, & len); + break; + + default: + break; + } + return result == SOCKET_ERROR ? -1 : 0; +} + +int +enet_socket_connect (ENetSocket socket, const ENetAddress * address) +{ + struct sockaddr_in sin; + int result; + + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + + result = connect (socket, (struct sockaddr *) & sin, sizeof (struct sockaddr_in)); + if (result == SOCKET_ERROR && WSAGetLastError () != WSAEWOULDBLOCK) + return -1; + + return 0; +} + +ENetSocket +enet_socket_accept (ENetSocket socket, ENetAddress * address) +{ + SOCKET result; + struct sockaddr_in sin; + int sinLength = sizeof (struct sockaddr_in); + + result = accept (socket, + address != NULL ? (struct sockaddr *) & sin : NULL, + address != NULL ? & sinLength : NULL); + + if (result == INVALID_SOCKET) + return ENET_SOCKET_NULL; + + if (address != NULL) + { + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + } + + return result; +} + +int +enet_socket_shutdown (ENetSocket socket, ENetSocketShutdown how) +{ + return shutdown (socket, (int) how) == SOCKET_ERROR ? -1 : 0; +} + +void +enet_socket_destroy (ENetSocket socket) +{ + if (socket != INVALID_SOCKET) + closesocket (socket); +} + +int +enet_socket_send (ENetSocket socket, + const ENetAddress * address, + const ENetBuffer * buffers, + size_t bufferCount) +{ + struct sockaddr_in sin; + DWORD sentLength; + + if (address != NULL) + { + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + } + + if (WSASendTo (socket, + (LPWSABUF) buffers, + (DWORD) bufferCount, + & sentLength, + 0, + address != NULL ? (struct sockaddr *) & sin : NULL, + address != NULL ? sizeof (struct sockaddr_in) : 0, + NULL, + NULL) == SOCKET_ERROR) + { + if (WSAGetLastError () == WSAEWOULDBLOCK) + return 0; + + return -1; + } + + return (int) sentLength; +} + +int +enet_socket_receive (ENetSocket socket, + ENetAddress * address, + ENetBuffer * buffers, + size_t bufferCount) +{ + INT sinLength = sizeof (struct sockaddr_in); + DWORD flags = 0, + recvLength; + struct sockaddr_in sin; + + if (WSARecvFrom (socket, + (LPWSABUF) buffers, + (DWORD) bufferCount, + & recvLength, + & flags, + address != NULL ? (struct sockaddr *) & sin : NULL, + address != NULL ? & sinLength : NULL, + NULL, + NULL) == SOCKET_ERROR) + { + switch (WSAGetLastError ()) + { + case WSAEWOULDBLOCK: + case WSAECONNRESET: + return 0; + } + + return -1; + } + + if (flags & MSG_PARTIAL) + return -1; + + if (address != NULL) + { + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + } + + return (int) recvLength; +} + +int +enet_socketset_select (ENetSocket maxSocket, ENetSocketSet * readSet, ENetSocketSet * writeSet, enet_uint32 timeout) +{ + struct timeval timeVal; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + return select (maxSocket + 1, readSet, writeSet, NULL, & timeVal); +} + +int +enet_socket_wait (ENetSocket socket, enet_uint32 * condition, enet_uint32 timeout) +{ + fd_set readSet, writeSet; + struct timeval timeVal; + int selectCount; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + FD_ZERO (& readSet); + FD_ZERO (& writeSet); + + if (* condition & ENET_SOCKET_WAIT_SEND) + FD_SET (socket, & writeSet); + + if (* condition & ENET_SOCKET_WAIT_RECEIVE) + FD_SET (socket, & readSet); + + selectCount = select (socket + 1, & readSet, & writeSet, NULL, & timeVal); + + if (selectCount < 0) + return -1; + + * condition = ENET_SOCKET_WAIT_NONE; + + if (selectCount == 0) + return 0; + + if (FD_ISSET (socket, & writeSet)) + * condition |= ENET_SOCKET_WAIT_SEND; + + if (FD_ISSET (socket, & readSet)) + * condition |= ENET_SOCKET_WAIT_RECEIVE; + + return 0; +} + +#endif + diff --git a/modules/gdscript/gd_compiler.cpp b/modules/gdscript/gd_compiler.cpp index 7a2e9c9638..ce8b6a6ea4 100644 --- a/modules/gdscript/gd_compiler.cpp +++ b/modules/gdscript/gd_compiler.cpp @@ -460,7 +460,6 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre const GDParser::Node *instance = on->arguments[0]; - bool in_static=false; if (instance->type==GDParser::Node::TYPE_SELF) { //room for optimization @@ -550,17 +549,25 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre int index; if (named) { -#ifdef DEBUG_ENABLED if (on->arguments[0]->type==GDParser::Node::TYPE_SELF && codegen.script && codegen.function_node && !codegen.function_node->_static) { - const Map<StringName,GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(static_cast<GDParser::IdentifierNode*>(on->arguments[1])->name); + GDParser::IdentifierNode* identifier = static_cast<GDParser::IdentifierNode*>(on->arguments[1]); + const Map<StringName,GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(identifier->name); + +#ifdef DEBUG_ENABLED if (MI && MI->get().getter==codegen.function_node->name) { String n = static_cast<GDParser::IdentifierNode*>(on->arguments[1])->name; _set_error("Must use '"+n+"' instead of 'self."+n+"' in getter.",on); return -1; } - } #endif + + if (MI && MI->get().getter=="") { + // Faster than indexing self (as if no self. had been used) + return (MI->get().index)|(GDFunction::ADDR_TYPE_MEMBER<<GDFunction::ADDR_BITS); + } + } + index=codegen.get_name_map_pos(static_cast<GDParser::IdentifierNode*>(on->arguments[1])->name); } else { @@ -763,8 +770,6 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre Vector<int> setchain; - int prev_key_idx=-1; - for(List<GDParser::OperatorNode*>::Element *E=chain.back();E;E=E->prev()) { @@ -814,7 +819,6 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre setchain.push_back(named ? GDFunction::OPCODE_SET_NAMED : GDFunction::OPCODE_SET); prev_pos=dst_pos; - prev_key_idx=key_idx; } @@ -1203,7 +1207,6 @@ Error GDCompiler::_parse_function(GDScript *p_script,const GDParser::ClassNode * if (p_func) { for(int i=0;i<p_func->arguments.size();i++) { - int idx = i; codegen.add_stack_identifier(p_func->arguments[i],i); #ifdef TOOLS_ENABLED argnames.push_back(p_func->arguments[i]); @@ -1294,8 +1297,10 @@ Error GDCompiler::_parse_function(GDScript *p_script,const GDParser::ClassNode * gdfunc = p_script->member_functions[func_name]; //} - if (p_func) + if (p_func) { gdfunc->_static=p_func->_static; + gdfunc->rpc_mode=p_func->rpc_mode; + } #ifdef TOOLS_ENABLED gdfunc->arg_names=argnames; @@ -1433,6 +1438,7 @@ Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDPa p_script->member_functions.clear(); p_script->member_indices.clear(); p_script->member_info.clear(); + p_script->_signals.clear(); p_script->initializer=NULL; p_script->subclasses.clear(); @@ -1441,7 +1447,6 @@ Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDPa p_script->name=p_class->name; - int index_from=0; Ref<GDNativeClass> native; if (p_class->extends_used) { @@ -1497,7 +1502,8 @@ Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDPa String sub = p_class->extends_class[i]; if (script->subclasses.has(sub)) { - script=script->subclasses[sub]; + Ref<Script> subclass = script->subclasses[sub]; //avoid reference from dissapearing + script=subclass; } else { _set_error("Could not find subclass: "+sub,p_class); @@ -1611,6 +1617,9 @@ Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDPa p_script->member_default_values[name]=p_class->variables[i].default_value; } #endif + } else { + + p_script->member_info[name]=PropertyInfo(Variant::NIL,name,PROPERTY_HINT_NONE,"",PROPERTY_USAGE_SCRIPT_VARIABLE); } //int new_idx = p_script->member_indices.size(); @@ -1618,6 +1627,8 @@ Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDPa minfo.index = p_script->member_indices.size(); minfo.setter = p_class->variables[i].setter; minfo.getter = p_class->variables[i].getter; + minfo.rpc_mode=p_class->variables[i].rpc_mode; + p_script->member_indices[name]=minfo; p_script->members.insert(name); @@ -1680,6 +1691,7 @@ Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDPa if (err) return err; + p_script->constants.insert(name,subclass); //once parsed, goes to the list of constants p_script->subclasses.insert(name,subclass); @@ -1779,6 +1791,7 @@ Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDPa ScriptInstance *si = E->get()->get_script_instance(); if (si->is_placeholder()) { +#ifdef TOOLS_ENABLED PlaceHolderScriptInstance *psi = static_cast<PlaceHolderScriptInstance*>(si); if (p_script->is_tool()) { @@ -1807,8 +1820,9 @@ Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDPa //well, tough luck, not goinna do anything here } } +#endif } else { - print_line("RELOAD MEMBERS"); + GDInstance *gi = static_cast<GDInstance*>(si); gi->reload_members(); } diff --git a/modules/gdscript/gd_editor.cpp b/modules/gdscript/gd_editor.cpp index 7e5ff620c9..2a80531ec5 100644 --- a/modules/gdscript/gd_editor.cpp +++ b/modules/gdscript/gd_editor.cpp @@ -44,21 +44,26 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { } -String GDScriptLanguage::get_template(const String& p_class_name, const String& p_base_class_name) const { +Ref<Script> GDScriptLanguage::get_template(const String& p_class_name, const String& p_base_class_name) const { String _template = String()+ - "\nextends %BASE%\n\n"+ - "# member variables here, example:\n"+ - "# var a=2\n"+ - "# var b=\"textvar\"\n\n"+ + "extends %BASE%\n\n"+ + "# class member variables go here, for example:\n"+ + "# var a = 2\n"+ + "# var b = \"textvar\"\n\n"+ "func _ready():\n"+ "\t# Called every time the node is added to the scene.\n"+ "\t# Initialization here\n"+ - "\tpass\n"+ - "\n"+ - "\n"; + "\tpass\n"; + + _template = _template.replace("%BASE%",p_base_class_name); + + Ref<GDScript> script; + script.instance(); + script->set_source_code(_template); + + return script; - return _template.replace("%BASE%",p_base_class_name); } @@ -212,7 +217,7 @@ String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,""); int l = _debug_call_stack_pos - p_level -1; - return _call_stack[l].function->get_script()->get_path(); + return _call_stack[l].function->get_source(); } void GDScriptLanguage::debug_get_stack_level_locals(int p_level,List<String> *p_locals, List<Variant> *p_values, int p_max_subitems,int p_max_depth) { @@ -297,7 +302,7 @@ void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const } { MethodInfo mi; - mi.name="yield"; + mi.name="yield:GDFunctionState"; mi.arguments.push_back(PropertyInfo(Variant::OBJECT,"object")); mi.arguments.push_back(PropertyInfo(Variant::STRING,"signal")); mi.default_arguments.push_back(Variant::NIL); @@ -328,7 +333,7 @@ String GDScriptLanguage::make_function(const String& p_class,const String& p_nam for(int i=0;i<p_args.size();i++) { if (i>0) s+=", "; - s+=p_args[i]; + s+=p_args[i].get_slice(":",0); } s+=" "; } @@ -449,62 +454,21 @@ static Ref<Reference> _get_parent_class(GDCompletionContext& context) { } String base=context._class->extends_class[0]; - const GDParser::ClassNode *p = context._class->owner; - Ref<GDScript> base_class; -#if 0 - while(p) { - - if (p->subclasses.has(base)) { - - base_class=p->subclasses[base]; - break; - } - p=p->_owner; - } -#endif - if (base_class.is_valid()) { -#if 0 - for(int i=1;i<context._class->extends_class.size();i++) { - - String subclass=context._class->extends_class[i]; - - if (base_class->subclasses.has(subclass)) { - - base_class=base_class->subclasses[subclass]; - } else { - - //print_line("Could not find subclass: "+subclass); - return _get_type_from_class(context); //fail please - } - } - - script=base_class; -#endif - - } else { - - if (context._class->extends_class.size()>1) { - - return REF(); - - - } - //if not found, try engine classes - if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { - return REF(); - } + if (context._class->extends_class.size()>1) { - int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base]; - native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx]; - if (!native.is_valid()) { + return REF(); - print_line("Global not a class: '"+base+"'"); + } + //if not found, try engine classes + if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { - } - return native; + return REF(); } + int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base]; + native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx]; + return native; } @@ -1024,7 +988,7 @@ static bool _guess_identifier_type_in_block(GDCompletionContext& context,int p_l } -static bool _guess_identifier_from_assignment_in_function(GDCompletionContext& context,const StringName& p_identifier, const StringName& p_function,GDCompletionIdentifier &r_type) { +static bool _guess_identifier_from_assignment_in_function(GDCompletionContext& context, int p_src_line, const StringName& p_identifier, const StringName& p_function,GDCompletionIdentifier &r_type) { const GDParser::FunctionNode* func=NULL; for(int i=0;i<context._class->functions.size();i++) { @@ -1039,7 +1003,9 @@ static bool _guess_identifier_from_assignment_in_function(GDCompletionContext& c for(int i=0;i<func->body->statements.size();i++) { - + if (func->body->statements[i]->line == p_src_line) { + break; + } if (func->body->statements[i]->type==GDParser::BlockNode::TYPE_OPERATOR) { const GDParser::OperatorNode *op = static_cast<const GDParser::OperatorNode *>(func->body->statements[i]); @@ -1160,11 +1126,11 @@ static bool _guess_identifier_type(GDCompletionContext& context,int p_line,const } //try to guess from assignment in construtor or _ready - if (_guess_identifier_from_assignment_in_function(context,p_identifier,"_ready",r_type)) + if (_guess_identifier_from_assignment_in_function(context,p_line+1,p_identifier,"_ready",r_type)) return true; - if (_guess_identifier_from_assignment_in_function(context,p_identifier,"_enter_tree",r_type)) + if (_guess_identifier_from_assignment_in_function(context,p_line+1,p_identifier,"_enter_tree",r_type)) return true; - if (_guess_identifier_from_assignment_in_function(context,p_identifier,"_init",r_type)) + if (_guess_identifier_from_assignment_in_function(context,p_line+1,p_identifier,"_init",r_type)) return true; return false; @@ -1495,7 +1461,7 @@ static void _make_function_hint(const GDParser::FunctionNode* p_func,int p_argid } -static void _find_type_arguments(const GDParser::Node*p_node,int p_line,const StringName& p_method,const GDCompletionIdentifier& id, int p_argidx, Set<String>& result, String& arghint) { +static void _find_type_arguments(GDCompletionContext& context,const GDParser::Node*p_node,int p_line,const StringName& p_method,const GDCompletionIdentifier& id, int p_argidx, Set<String>& result, String& arghint) { //print_line("find type arguments?"); @@ -1732,9 +1698,31 @@ static void _find_type_arguments(const GDParser::Node*p_node,int p_line,const St if (p_argidx==0) { List<MethodInfo> sigs; ObjectTypeDB::get_signal_list(id.obj_type,&sigs); + + if (id.script.is_valid()) { + id.script->get_script_signal_list(&sigs); + } else if (id.value.get_type()==Variant::OBJECT) { + Object *obj = id.value; + if (obj && !obj->get_script().is_null()) { + Ref<Script> scr=obj->get_script(); + if (scr.is_valid()) { + scr->get_script_signal_list(&sigs); + } + } + } + for (List<MethodInfo>::Element *E=sigs.front();E;E=E->next()) { result.insert("\""+E->get().name+"\""); } + + } else if (p_argidx==2){ + + + if (context._class) { + for(int i=0;i<context._class->functions.size();i++) { + result.insert("\""+context._class->functions[i]->name+"\""); + } + } } /*if (p_argidx==2) { @@ -1976,7 +1964,7 @@ static void _find_call_arguments(GDCompletionContext& context,const GDParser::No if (!context._class->owner) ci.value=context.base; - _find_type_arguments(p_node,p_line,id->name,ci,p_argidx,result,arghint); + _find_type_arguments(context,p_node,p_line,id->name,ci,p_argidx,result,arghint); //guess type.. /* List<MethodInfo> methods; @@ -1999,7 +1987,7 @@ static void _find_call_arguments(GDCompletionContext& context,const GDParser::No GDCompletionIdentifier ci; if (_guess_expression_type(context,op->arguments[0],p_line,ci)) { - _find_type_arguments(p_node,p_line,id->name,ci,p_argidx,result,arghint); + _find_type_arguments(context,p_node,p_line,id->name,ci,p_argidx,result,arghint); return; } @@ -2103,10 +2091,8 @@ static void _find_call_arguments(GDCompletionContext& context,const GDParser::No } Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base_path, Object*p_owner, List<String>* r_options, String &r_call_hint) { - //print_line( p_code.replace(String::chr(0xFFFF),"<cursor>")); GDParser p; - //Error parse(const String& p_code, const String& p_base_path="", bool p_just_validate=false,const String& p_self_path="",bool p_for_completion=false); Error err = p.parse(p_code,p_base_path,false,"",true); bool isfunction=false; @@ -2122,10 +2108,8 @@ Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base switch(p.get_completion_type()) { case GDParser::COMPLETION_NONE: { - print_line("No completion"); } break; case GDParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: { - print_line("Built in type constant"); List<StringName> constants; Variant::get_numeric_constants_for_type(p.get_completion_built_in_constant(),&constants); for(List<StringName>::Element *E=constants.front();E;E=E->next()) { @@ -2141,7 +2125,6 @@ Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base _find_identifiers(context,p.get_completion_line(),isfunction,options); } break; case GDParser::COMPLETION_PARENT_FUNCTION: { - print_line("parent function"); } break; case GDParser::COMPLETION_METHOD: @@ -2210,7 +2193,6 @@ Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base if (code!="") { //if there is code, parse it. This way is slower but updates in real-time GDParser p; - //Error parse(const String& p_code, const String& p_base_path="", bool p_just_validate=false,const String& p_self_path="",bool p_for_completion=false); Error err = p.parse(scr->get_source_code(),scr->get_path().get_base_dir(),true,"",false); @@ -2427,7 +2409,24 @@ Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base } } } break; + case GDParser::COMPLETION_YIELD: { + + const GDParser::Node *node = p.get_completion_node(); + + GDCompletionIdentifier t; + if (!_guess_expression_type(context,node,p.get_completion_line(),t)) + break; + if (t.type==Variant::OBJECT && t.obj_type!=StringName()) { + + List<MethodInfo> sigs; + ObjectTypeDB::get_signal_list(t.obj_type,&sigs); + for (List<MethodInfo>::Element *E=sigs.front();E;E=E->next()) { + options.insert("\""+E->get().name+"\""); + } + } + + } break; } diff --git a/modules/gdscript/gd_function.cpp b/modules/gdscript/gd_function.cpp index 04522aadc2..b2cc6341c1 100644 --- a/modules/gdscript/gd_function.cpp +++ b/modules/gdscript/gd_function.cpp @@ -372,8 +372,7 @@ Variant GDFunction::call(GDInstance *p_instance, const Variant **p_args, int p_a if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language()==GDScriptLanguage::get_singleton()) { - GDInstance *ins = static_cast<GDInstance*>(obj_A->get_script_instance()); - GDScript *cmp = ins->script.ptr(); + GDScript *cmp = static_cast<GDScript*>(obj_A->get_script_instance()->get_script().ptr()); //bool found=false; while(cmp) { @@ -654,10 +653,10 @@ Variant GDFunction::call(GDInstance *p_instance, const Variant **p_args, int p_a if (call_ret) { GET_VARIANT_PTR(ret,argc); - *ret = base->call(*methodname,(const Variant**)argptrs,argc,err); + base->call_ptr(*methodname,(const Variant**)argptrs,argc,ret,err); } else { - base->call(*methodname,(const Variant**)argptrs,argc,err); + base->call_ptr(*methodname,(const Variant**)argptrs,argc,NULL,err); } #ifdef DEBUG_ENABLED if (GDScriptLanguage::get_singleton()->profiling) { @@ -727,7 +726,12 @@ Variant GDFunction::call(GDInstance *p_instance, const Variant **p_args, int p_a String methodstr = GDFunctions::get_func_name(func); - err_text=_get_call_error(err,"built-in function '"+methodstr+"'",(const Variant**)argptrs); + if (dst->get_type()==Variant::STRING) { + //call provided error string + err_text="Error calling built-in function '"+methodstr+"': "+String(*dst); + } else { + err_text=_get_call_error(err,"built-in function '"+methodstr+"'",(const Variant**)argptrs); + } break; } ip+=argc+1; @@ -841,6 +845,8 @@ Variant GDFunction::call(GDInstance *p_instance, const Variant **p_args, int p_a gdfs->state._class=_class; gdfs->state.ip=ip+ipofs; gdfs->state.line=line; + gdfs->state.instance_id=(p_instance && p_instance->get_owner())?p_instance->get_owner()->get_instance_ID():0; + gdfs->state.script_id=_class->get_instance_ID(); //gdfs->state.result_pos=ip+ipofs-1; gdfs->state.defarg=defarg; gdfs->state.instance=p_instance; @@ -1303,6 +1309,7 @@ GDFunction::GDFunction() : function_list(this) { _stack_size=0; _call_size=0; + rpc_mode=ScriptInstance::RPC_MODE_DISABLED; name="<anonymous>"; #ifdef DEBUG_ENABLED _func_cname=NULL; @@ -1347,6 +1354,18 @@ GDFunction::~GDFunction() { Variant GDFunctionState::_signal_callback(const Variant** p_args, int p_argcount, Variant::CallError& r_error) { +#ifdef DEBUG_ENABLED + if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) { + ERR_EXPLAIN("Resumed after yield, but class instance is gone"); + ERR_FAIL_V(Variant()); + } + + if (state.script_id && !ObjectDB::get_instance(state.script_id)) { + ERR_EXPLAIN("Resumed after yield, but script is gone"); + ERR_FAIL_V(Variant()); + } +#endif + Variant arg; r_error.error=Variant::CallError::CALL_OK; @@ -1393,6 +1412,17 @@ bool GDFunctionState::is_valid() const { Variant GDFunctionState::resume(const Variant& p_arg) { ERR_FAIL_COND_V(!function,Variant()); +#ifdef DEBUG_ENABLED + if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) { + ERR_EXPLAIN("Resumed after yield, but class instance is gone"); + ERR_FAIL_V(Variant()); + } + + if (state.script_id && !ObjectDB::get_instance(state.script_id)) { + ERR_EXPLAIN("Resumed after yield, but script is gone"); + ERR_FAIL_V(Variant()); + } +#endif state.result=p_arg; Variant::CallError err; diff --git a/modules/gdscript/gd_function.h b/modules/gdscript/gd_function.h index 1f790eaadc..f1c5b13ca1 100644 --- a/modules/gdscript/gd_function.h +++ b/modules/gdscript/gd_function.h @@ -7,6 +7,7 @@ #include "variant.h" #include "string_db.h" #include "reference.h" +#include "script_language.h" class GDInstance; class GDScript; @@ -64,6 +65,14 @@ public: ADDR_TYPE_NIL=8 }; + enum RPCMode { + RPC_DISABLED, + RPC_ENABLED, + RPC_SYNC, + RPC_SYNC_MASTER, + RPC_SYNC_SLAVE + }; + struct StackDebug { int line; @@ -91,6 +100,8 @@ friend class GDCompiler; int _call_size; int _initial_line; bool _static; + ScriptInstance::RPCMode rpc_mode; + GDScript *_script; StringName name; @@ -136,6 +147,9 @@ public: struct CallState { + ObjectID instance_id; //by debug only + ObjectID script_id; + GDInstance *instance; Vector<uint8_t> stack; int stack_size; @@ -160,6 +174,7 @@ public: int get_default_argument_count() const; int get_default_argument_addr(int p_idx) const; GDScript *get_script() const { return _script; } + StringName get_source() const { return source; } void debug_get_stack_member_state(int p_line,List<Pair<StringName,int> > *r_stackvars) const; @@ -181,6 +196,7 @@ public: Variant call(GDInstance *p_instance,const Variant **p_args, int p_argcount,Variant::CallError& r_err,CallState *p_state=NULL); + _FORCE_INLINE_ ScriptInstance::RPCMode get_rpc_mode() const { return rpc_mode; } GDFunction(); ~GDFunction(); }; diff --git a/modules/gdscript/gd_functions.cpp b/modules/gdscript/gd_functions.cpp index 1c05a71d01..a565e866d0 100644 --- a/modules/gdscript/gd_functions.cpp +++ b/modules/gdscript/gd_functions.cpp @@ -87,6 +87,7 @@ const char *GDFunctions::get_func_name(Function p_func) { "funcref", "convert", "typeof", + "type_exists", "str", "print", "printt", @@ -120,11 +121,13 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va if (p_arg_count<m_count) {\ r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;\ r_error.argument=m_count;\ + r_ret=Variant();\ return;\ }\ if (p_arg_count>m_count) {\ r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;\ r_error.argument=m_count;\ + r_ret=Variant();\ return;\ } @@ -133,6 +136,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;\ r_error.argument=m_arg;\ r_error.expected=Variant::REAL;\ + r_ret=Variant();\ return;\ } @@ -244,6 +248,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::REAL; + r_ret=Variant(); } } break; case MATH_SIGN: { @@ -261,6 +266,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::REAL; + r_ret=Variant(); } } break; case MATH_POW: { @@ -298,7 +304,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va case MATH_DECIMALS: { VALIDATE_ARG_COUNT(1); VALIDATE_ARG_NUM(0); - r_ret=Math::decimals(*p_args[0]); + r_ret=Math::step_decimals(*p_args[0]); } break; case MATH_STEPIFY: { VALIDATE_ARG_COUNT(2); @@ -442,6 +448,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::OBJECT; + r_ret=Variant(); return; } @@ -479,7 +486,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::OBJECT; - r_ret=Variant(); + r_ret=Variant(); return; } @@ -495,7 +502,6 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va Ref<FuncRef> fr = memnew( FuncRef); - Object *obj = *p_args[0]; fr->set_instance(*p_args[0]); fr->set_function(*p_args[1]); @@ -508,8 +514,11 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va int type=*p_args[1]; if (type<0 || type>=Variant::VARIANT_MAX) { - ERR_PRINT("Invalid type argument to convert()"); - r_ret=Variant::NIL; + r_ret=RTR("Invalid type argument to convert(), use TYPE_* constants."); + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::INT; + return; } else { @@ -523,6 +532,12 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_ret = p_args[0]->get_type(); } break; + case TYPE_EXISTS: { + + VALIDATE_ARG_COUNT(1); + r_ret = ObjectTypeDB::type_exists(*p_args[0]); + + } break; case TEXT_STR: { String str; @@ -638,7 +653,8 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::STRING; - r_ret=Variant(); + r_ret="Parse error at line "+itos(line)+": "+errs; + return; } } break; @@ -652,7 +668,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::NIL; - r_ret=Variant(); + r_ret="Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."; return; } @@ -680,11 +696,10 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va ByteArray::Read r=varr.read(); Error err = decode_variant(ret,r.ptr(),varr.size(),NULL); if (err!=OK) { - ERR_PRINT("Not enough bytes for decoding.."); + r_ret=RTR("Not enough bytes for decoding bytes, or invalid format."); r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::RAW_ARRAY; - r_ret=Variant(); return; } @@ -701,6 +716,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument=1; + r_ret=Variant(); } break; case 1: { @@ -759,9 +775,9 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va int incr=*p_args[2]; if (incr==0) { - ERR_EXPLAIN("step argument is zero!"); + r_ret=RTR("step argument is zero!"); r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; - ERR_FAIL(); + return; } Array arr(true); @@ -812,6 +828,8 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_error.argument=3; + r_ret=Variant(); + } break; } @@ -847,8 +865,8 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::DICTIONARY; - ERR_PRINT("Not a script with an instance"); - + r_ret=RTR("Not a script with an instance"); + return; } else { GDInstance *ins = static_cast<GDInstance*>(obj->get_script_instance()); @@ -858,7 +876,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::DICTIONARY; - ERR_PRINT("Not based on a script"); + r_ret=RTR("Not based on a script"); return; } @@ -879,8 +897,10 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::DICTIONARY; - print_line("PATH: "+p->path); - ERR_PRINT("Not based on a resource file"); + r_ret=Variant(); + + + r_ret=RTR("Not based on a resource file"); return; } @@ -926,6 +946,8 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::DICTIONARY; + r_ret=Variant(); + return; } @@ -936,6 +958,8 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::OBJECT; + r_ret=RTR("Invalid instance dictionary format (missing @path)"); + return; } @@ -945,6 +969,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::OBJECT; + r_ret=RTR("Invalid instance dictionary format (can't load script at @path)"); return; } @@ -955,6 +980,8 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::OBJECT; + r_ret=Variant(); + r_ret=RTR("Invalid instance dictionary format (invalid script at @path)"); return; } @@ -971,20 +998,22 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::OBJECT; + r_ret=Variant(); + r_ret=RTR("Invalid instance dictionary (invalid subclasses)"); return; } } r_ret = gdscr->_new(NULL,0,r_error); - GDInstance *ins = static_cast<GDInstance*>(static_cast<Object*>(r_ret)->get_script_instance()); - Ref<GDScript> gd_ref = ins->get_script(); + GDInstance *ins = static_cast<GDInstance*>(static_cast<Object*>(r_ret)->get_script_instance()); + Ref<GDScript> gd_ref = ins->get_script(); - for(Map<StringName,GDScript::MemberInfo>::Element *E = gd_ref->member_indices.front(); E; E = E->next()) { - if(d.has(E->key())) { - ins->members[E->get().index] = d[E->key()]; - } - } + for(Map<StringName,GDScript::MemberInfo>::Element *E = gd_ref->member_indices.front(); E; E = E->next()) { + if(d.has(E->key())) { + ins->members[E->get().index] = d[E->key()]; + } + } } break; case HASH: { @@ -998,11 +1027,15 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va if (p_arg_count<3) { r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument=3; + r_ret=Variant(); + return; } if (p_arg_count>4) { r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_error.argument=4; + r_ret=Variant(); + return; } @@ -1010,11 +1043,11 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va VALIDATE_ARG_NUM(1); VALIDATE_ARG_NUM(2); - Color color(*p_args[0],*p_args[1],*p_args[2]); + Color color((float)*p_args[0]/255.0f,(float)*p_args[1]/255.0f,(float)*p_args[2]/255.0f); if (p_arg_count==4) { VALIDATE_ARG_NUM(3); - color.a=*p_args[3]; + color.a=(float)*p_args[3]/255.0f; } r_ret=color; @@ -1036,6 +1069,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va if (p_args[0]->get_type()!=Variant::INT && p_args[0]->get_type()!=Variant::REAL) { r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; + r_error.expected=Variant::INT; r_ret=Variant(); break; } @@ -1098,6 +1132,7 @@ bool GDFunctions::is_deterministic(Function p_func) { case LOGIC_NEAREST_PO2: case TYPE_CONVERT: case TYPE_OF: + case TYPE_EXISTS: case TEXT_STR: case COLOR8: // enable for debug only, otherwise not desirable - case GEN_RANGE: @@ -1281,12 +1316,12 @@ MethodInfo GDFunctions::get_info(Function p_func) { return mi; } break; case MATH_SEED: { - MethodInfo mi("seed",PropertyInfo(Variant::REAL,"seed")); + MethodInfo mi("seed",PropertyInfo(Variant::INT,"seed")); mi.return_val.type=Variant::NIL; return mi; } break; case MATH_RANDSEED: { - MethodInfo mi("rand_seed",PropertyInfo(Variant::REAL,"seed")); + MethodInfo mi("rand_seed",PropertyInfo(Variant::INT,"seed")); mi.return_val.type=Variant::ARRAY; return mi; } break; @@ -1361,6 +1396,13 @@ MethodInfo GDFunctions::get_info(Function p_func) { return mi; } break; + case TYPE_EXISTS: { + + MethodInfo mi("type_exists",PropertyInfo(Variant::STRING,"type")); + mi.return_val.type=Variant::BOOL; + return mi; + + } break; case TEXT_STR: { MethodInfo mi("str",PropertyInfo(Variant::NIL,"what"),PropertyInfo(Variant::NIL,"...")); diff --git a/modules/gdscript/gd_functions.h b/modules/gdscript/gd_functions.h index 8c88472567..c78956fe20 100644 --- a/modules/gdscript/gd_functions.h +++ b/modules/gdscript/gd_functions.h @@ -81,6 +81,7 @@ public: FUNC_FUNCREF, TYPE_CONVERT, TYPE_OF, + TYPE_EXISTS, TEXT_STR, TEXT_PRINT, TEXT_PRINT_TABBED, diff --git a/modules/gdscript/gd_parser.cpp b/modules/gdscript/gd_parser.cpp index 9e90027a70..e5a8dc0152 100644 --- a/modules/gdscript/gd_parser.cpp +++ b/modules/gdscript/gd_parser.cpp @@ -31,6 +31,7 @@ #include "io/resource_loader.h" #include "os/file_access.h" #include "script_language.h" +#include "gd_script.h" template<class T> T* GDParser::alloc_node() { @@ -216,7 +217,7 @@ bool GDParser::_get_completable_identifier(CompletionType p_type,StringName& ide } -GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_allow_assign) { +GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_allow_assign,bool p_parsing_constant) { // Vector<Node*> expressions; // Vector<OperatorNode::Operator> operators; @@ -225,6 +226,8 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ Node *expr=NULL; + int op_line = tokenizer->get_token_line(); // when operators are created at the bottom, the line might have been changed (\n found) + while(true) { @@ -243,7 +246,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ //subexpression () tokenizer->advance(); parenthesis++; - Node* subexpr = _parse_expression(p_parent,p_static); + Node* subexpr = _parse_expression(p_parent,p_static,p_allow_assign,p_parsing_constant); parenthesis--; if (!subexpr) return NULL; @@ -375,6 +378,21 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ tokenizer->advance(); + if (tokenizer->get_token()==GDTokenizer::TK_CURSOR) { + + + completion_cursor=StringName(); + completion_node=object; + completion_type=COMPLETION_YIELD; + completion_class=current_class; + completion_function=current_function; + completion_line=tokenizer->get_token_line(); + completion_argument=0; + completion_block=current_block; + completion_found=true; + tokenizer->advance(); + } + Node *signal = _parse_and_reduce_expression(p_parent,p_static); if (!signal) return NULL; @@ -477,20 +495,30 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ } else if (tokenizer->get_token()==GDTokenizer::TK_IDENTIFIER) { //identifier (reference) - const ClassNode* cln = static_cast<const ClassNode*>(get_parse_tree()); + const ClassNode* cln = current_class; bool bfn = false; StringName identifier; if (_get_completable_identifier(COMPLETION_IDENTIFIER,identifier)) { } - for( int i=0; i<cln->constant_expressions.size(); ++i ) { + if (p_parsing_constant) { + for( int i=0; i<cln->constant_expressions.size(); ++i ) { - if( cln->constant_expressions[i].identifier == identifier ) { + if( cln->constant_expressions[i].identifier == identifier ) { - expr = cln->constant_expressions[i].expression; - bfn = true; - break; + expr = cln->constant_expressions[i].expression; + bfn = true; + break; + } + } + + if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { + //check from constants + ConstantNode *constant = alloc_node<ConstantNode>(); + constant->value = GDScriptLanguage::get_singleton()->get_global_array()[ GDScriptLanguage::get_singleton()->get_global_map()[identifier] ]; + expr=constant; + bfn = true; } } @@ -503,8 +531,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ } else if (/*tokenizer->get_token()==GDTokenizer::TK_OP_ADD ||*/ tokenizer->get_token()==GDTokenizer::TK_OP_SUB || tokenizer->get_token()==GDTokenizer::TK_OP_NOT || tokenizer->get_token()==GDTokenizer::TK_OP_BIT_INVERT) { //single prefix operators like !expr -expr ++expr --expr - OperatorNode *op = alloc_node<OperatorNode>(); - + alloc_node<OperatorNode>(); Expression e; e.is_op=true; @@ -567,7 +594,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ _set_error("',' or ']' expected"); return NULL; } - Node *n = _parse_expression(arr,p_static); + Node *n = _parse_expression(arr,p_static,p_allow_assign,p_parsing_constant); if (!n) return NULL; arr->elements.push_back(n); @@ -674,7 +701,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ expecting=DICT_EXPECT_VALUE; } else { //python/js style more flexible - key = _parse_expression(dict,p_static); + key = _parse_expression(dict,p_static,p_allow_assign,p_parsing_constant); if (!key) return NULL; expecting=DICT_EXPECT_COLON; @@ -682,7 +709,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ } if (expecting==DICT_EXPECT_VALUE) { - Node *value = _parse_expression(dict,p_static); + Node *value = _parse_expression(dict,p_static,p_allow_assign,p_parsing_constant); if (!value) return NULL; expecting=DICT_EXPECT_COMMA; @@ -730,7 +757,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ //find list [ or find dictionary { - print_line("found bug?"); + //print_line("found bug?"); _set_error("Error parsing expression, misplaced: "+String(tokenizer->get_token_name(tokenizer->get_token()))); return NULL; //nothing @@ -833,7 +860,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ tokenizer->advance(1); - Node *subexpr = _parse_expression(op,p_static); + Node *subexpr = _parse_expression(op,p_static,p_allow_assign,p_parsing_constant); if (!subexpr) { return NULL; } @@ -1041,6 +1068,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ OperatorNode *op = alloc_node<OperatorNode>(); op->op=expression[i].op; op->arguments.push_back(expression[i+1].node); + op->line=op_line; //line might have been changed from a \n expression[i].is_op=false; expression[i].node=op; expression.remove(i+1); @@ -1056,6 +1084,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ OperatorNode *op = alloc_node<OperatorNode>(); op->op=expression[next_op].op; + op->line=op_line; //line might have been changed from a \n if (expression[next_op-1].is_op) { @@ -1069,8 +1098,8 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ // can be followed by an unary op in a valid combination, // due to how precedence works, unaries will always dissapear first - _set_error("Parser bug.."); - + _set_error("Unexpected two consecutive operators."); + return NULL; } @@ -1258,6 +1287,8 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { } break; } + error_line=op->line; + return p_node; } @@ -1293,6 +1324,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { Variant v = ca->value.get(cb->value,&valid); if (!valid) { _set_error("invalid index in constant expression"); + error_line=op->line; return op; } @@ -1330,6 +1362,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { Variant v = ca->value.get_named(ib->name,&valid); if (!valid) { _set_error("invalid index '"+String(ib->name)+"' in constant expression"); + error_line=op->line; return op; } @@ -1359,6 +1392,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { if (op->arguments[0]->type==Node::TYPE_CONSTANT) { _set_error("Can't assign to constant",tokenizer->get_token_line()-1); + error_line=op->line; return op; } @@ -1374,6 +1408,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { Variant::evaluate(m_vop,static_cast<ConstantNode*>(op->arguments[0])->value,Variant(),res,valid);\ if (!valid) {\ _set_error("Invalid operand for unary operator");\ + error_line=op->line;\ return p_node;\ }\ ConstantNode *cn = alloc_node<ConstantNode>();\ @@ -1386,6 +1421,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { Variant::evaluate(m_vop,static_cast<ConstantNode*>(op->arguments[0])->value,static_cast<ConstantNode*>(op->arguments[1])->value,res,valid);\ if (!valid) {\ _set_error("Invalid operands for operator");\ + error_line=op->line;\ return p_node;\ }\ ConstantNode *cn = alloc_node<ConstantNode>();\ @@ -1432,7 +1468,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { GDParser::Node* GDParser::_parse_and_reduce_expression(Node *p_parent,bool p_static,bool p_reduce_const,bool p_allow_assign) { - Node* expr=_parse_expression(p_parent,p_static,p_allow_assign); + Node* expr=_parse_expression(p_parent,p_static,p_allow_assign,p_reduce_const); if (!expr || error_set) return NULL; expr = _reduce_expression(expr,p_reduce_const); @@ -1526,6 +1562,10 @@ void GDParser::_parse_block(BlockNode *p_block,bool p_static) { return; } tokenizer->advance(); + if(tokenizer->get_token()==GDTokenizer::TK_SEMICOLON) { + // Ignore semicolon after 'pass' + tokenizer->advance(); + } } break; case GDTokenizer::TK_PR_VAR: { //variale declaration and (eventual) initialization @@ -2035,6 +2075,7 @@ void GDParser::_parse_class(ClassNode *p_class) { if (error_set) return; + if (indent_level>tab_level.back()->get()) { p_class->end_line=tokenizer->get_token_line(); return; //go back a level @@ -2331,6 +2372,9 @@ void GDParser::_parse_class(ClassNode *p_class) { function->_static=_static; function->line=fnline; + function->rpc_mode=rpc_mode; + rpc_mode=ScriptInstance::RPC_MODE_DISABLED; + if (_static) p_class->static_functions.push_back(function); @@ -2408,6 +2452,7 @@ void GDParser::_parse_class(ClassNode *p_class) { return; } current_export.type=type; + current_export.usage|=PROPERTY_USAGE_SCRIPT_VARIABLE; tokenizer->advance(); if (tokenizer->get_token()==GDTokenizer::TK_COMMA) { // hint expected next! @@ -2782,6 +2827,8 @@ void GDParser::_parse_class(ClassNode *p_class) { current_export.type=Variant::OBJECT; current_export.hint=PROPERTY_HINT_RESOURCE_TYPE; + current_export.usage|=PROPERTY_USAGE_SCRIPT_VARIABLE; + current_export.hint_string=identifier; tokenizer->advance(); @@ -2799,25 +2846,101 @@ void GDParser::_parse_class(ClassNode *p_class) { } - if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR) { + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR && tokenizer->get_token()!=GDTokenizer::TK_PR_ONREADY && tokenizer->get_token()!=GDTokenizer::TK_PR_REMOTE && tokenizer->get_token()!=GDTokenizer::TK_PR_MASTER && tokenizer->get_token()!=GDTokenizer::TK_PR_SLAVE && tokenizer->get_token()!=GDTokenizer::TK_PR_SYNC) { current_export=PropertyInfo(); - _set_error("Expected 'var'."); + _set_error("Expected 'var', 'onready', 'remote', 'master', 'slave' or 'sync'."); return; } - }; //fallthrough to var + continue; + } break; case GDTokenizer::TK_PR_ONREADY: { - if (token==GDTokenizer::TK_PR_ONREADY) { - //may be fallthrough from export, ignore if so - tokenizer->advance(); + //may be fallthrough from export, ignore if so + tokenizer->advance(); + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR) { + _set_error("Expected 'var'."); + return; + } + + continue; + } break; + case GDTokenizer::TK_PR_REMOTE: { + + //may be fallthrough from export, ignore if so + tokenizer->advance(); + if (current_export.type) { if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR) { _set_error("Expected 'var'."); return; } + + } else { + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR && tokenizer->get_token()!=GDTokenizer::TK_PR_FUNCTION) { + _set_error("Expected 'var' or 'func'."); + return; + } } - }; //fallthrough to var + rpc_mode=ScriptInstance::RPC_MODE_REMOTE; + + continue; + } break; + case GDTokenizer::TK_PR_MASTER: { + + //may be fallthrough from export, ignore if so + tokenizer->advance(); + if (current_export.type) { + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR) { + _set_error("Expected 'var'."); + return; + } + + } else { + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR && tokenizer->get_token()!=GDTokenizer::TK_PR_FUNCTION) { + _set_error("Expected 'var' or 'func'."); + return; + } + } + + rpc_mode=ScriptInstance::RPC_MODE_MASTER; + continue; + } break; + case GDTokenizer::TK_PR_SLAVE: { + + //may be fallthrough from export, ignore if so + tokenizer->advance(); + if (current_export.type) { + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR) { + _set_error("Expected 'var'."); + return; + } + + } else { + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR && tokenizer->get_token()!=GDTokenizer::TK_PR_FUNCTION) { + _set_error("Expected 'var' or 'func'."); + return; + } + } + + rpc_mode=ScriptInstance::RPC_MODE_SLAVE; + continue; + } break; + case GDTokenizer::TK_PR_SYNC: { + + //may be fallthrough from export, ignore if so + tokenizer->advance(); + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR && tokenizer->get_token()!=GDTokenizer::TK_PR_FUNCTION) { + if (current_export.type) + _set_error("Expected 'var'."); + else + _set_error("Expected 'var' or 'func'."); + return; + } + + rpc_mode=ScriptInstance::RPC_MODE_SYNC; + continue; + } break; case GDTokenizer::TK_PR_VAR: { //variale declaration and (eventual) initialization @@ -2841,8 +2964,12 @@ void GDParser::_parse_class(ClassNode *p_class) { member.expression=NULL; member._export.name=member.identifier; member.line=tokenizer->get_token_line(); + member.rpc_mode=rpc_mode; + tokenizer->advance(); + rpc_mode=ScriptInstance::RPC_MODE_DISABLED; + if (tokenizer->get_token()==GDTokenizer::TK_OP_ASSIGN) { #ifdef DEBUG_ENABLED @@ -2901,6 +3028,7 @@ void GDParser::_parse_class(ClassNode *p_class) { return; } member._export.type=cn->value.get_type(); + member._export.usage|=PROPERTY_USAGE_SCRIPT_VARIABLE; } } #ifdef TOOLS_ENABLED @@ -3184,6 +3312,7 @@ void GDParser::clear() { current_class=NULL; completion_found=false; + rpc_mode=ScriptInstance::RPC_MODE_DISABLED; current_function=NULL; diff --git a/modules/gdscript/gd_parser.h b/modules/gdscript/gd_parser.h index 6c49c1df52..9e6f6e6765 100644 --- a/modules/gdscript/gd_parser.h +++ b/modules/gdscript/gd_parser.h @@ -33,6 +33,7 @@ #include "gd_functions.h" #include "map.h" #include "object.h" +#include "script_language.h" class GDParser { public: @@ -88,6 +89,7 @@ public: StringName getter; int line; Node *expression; + ScriptInstance::RPCMode rpc_mode; }; struct Constant { StringName identifier; @@ -119,12 +121,13 @@ public: struct FunctionNode : public Node { bool _static; + ScriptInstance::RPCMode rpc_mode; StringName name; Vector<StringName> arguments; Vector<Node*> default_values; BlockNode *body; - FunctionNode() { type=TYPE_FUNCTION; _static=false; } + FunctionNode() { type=TYPE_FUNCTION; _static=false; rpc_mode=ScriptInstance::RPC_MODE_DISABLED; } }; @@ -375,7 +378,8 @@ public: COMPLETION_METHOD, COMPLETION_CALL_ARGUMENTS, COMPLETION_INDEX, - COMPLETION_VIRTUAL_FUNC + COMPLETION_VIRTUAL_FUNC, + COMPLETION_YIELD, }; @@ -428,6 +432,9 @@ private: PropertyInfo current_export; + ScriptInstance::RPCMode rpc_mode; + + void _set_error(const String& p_error, int p_line=-1, int p_column=-1); bool _recover_from_completion(); @@ -435,7 +442,7 @@ private: bool _parse_arguments(Node* p_parent, Vector<Node*>& p_args, bool p_static, bool p_can_codecomplete=false); bool _enter_indent_block(BlockNode *p_block=NULL); bool _parse_newline(); - Node* _parse_expression(Node *p_parent,bool p_static,bool p_allow_assign=false); + Node* _parse_expression(Node *p_parent, bool p_static, bool p_allow_assign=false, bool p_parsing_constant=false); Node* _reduce_expression(Node *p_node,bool p_to_const=false); Node* _parse_and_reduce_expression(Node *p_parent,bool p_static,bool p_reduce_const=false,bool p_allow_assign=false); diff --git a/modules/gdscript/gd_script.cpp b/modules/gdscript/gd_script.cpp index dcd0641f76..b97a0fcbb6 100644 --- a/modules/gdscript/gd_script.cpp +++ b/modules/gdscript/gd_script.cpp @@ -179,6 +179,15 @@ bool GDScript::can_instance() const { } +Ref<Script> GDScript::get_base_script() const { + + if (_base) { + return Ref<GDScript>( _base ); + } else { + return Ref<Script>(); + } +} + StringName GDScript::get_instance_base_type() const { if (native.is_valid()) @@ -249,6 +258,85 @@ void GDScript::_update_placeholder(PlaceHolderScriptInstance *p_placeholder) { }*/ #endif + +void GDScript::get_script_method_list(List<MethodInfo> *p_list) const { + + for (const Map<StringName,GDFunction*>::Element *E=member_functions.front();E;E=E->next()) { + MethodInfo mi; + mi.name=E->key(); + for(int i=0;i<E->get()->get_argument_count();i++) { + PropertyInfo arg; + arg.type=Variant::NIL; //variant + arg.name=E->get()->get_argument_name(i); + mi.arguments.push_back(arg); + } + + mi.return_val.name="Variant"; + p_list->push_back(mi); + } +} + +void GDScript::get_script_property_list(List<PropertyInfo> *p_list) const { + + const GDScript *sptr=this; + List<PropertyInfo> props; + + while(sptr) { + + Vector<_GDScriptMemberSort> msort; + for(Map<StringName,PropertyInfo>::Element *E=sptr->member_info.front();E;E=E->next()) { + + _GDScriptMemberSort ms; + ERR_CONTINUE(!sptr->member_indices.has(E->key())); + ms.index=sptr->member_indices[E->key()].index; + ms.name=E->key(); + msort.push_back(ms); + + } + + msort.sort(); + msort.invert(); + for(int i=0;i<msort.size();i++) { + + props.push_front(sptr->member_info[msort[i].name]); + + } + + sptr = sptr->_base; + } + + for (List<PropertyInfo>::Element *E=props.front();E;E=E->next()) { + p_list->push_back(E->get()); + } + +} + +bool GDScript::has_method(const StringName& p_method) const { + + return member_functions.has(p_method); +} + +MethodInfo GDScript::get_method_info(const StringName& p_method) const { + + const Map<StringName,GDFunction*>::Element *E=member_functions.find(p_method); + if (!E) + return MethodInfo(); + + MethodInfo mi; + mi.name=E->key(); + for(int i=0;i<E->get()->get_argument_count();i++) { + PropertyInfo arg; + arg.type=Variant::NIL; //variant + arg.name=E->get()->get_argument_name(i); + mi.arguments.push_back(arg); + } + + mi.return_val.name="Variant"; + return mi; + +} + + bool GDScript::get_property_default_value(const StringName& p_property, Variant &r_value) const { #ifdef TOOLS_ENABLED @@ -663,7 +751,7 @@ void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const { void GDScript::_bind_methods() { - ObjectTypeDB::bind_native_method(METHOD_FLAGS_DEFAULT,"new",&GDScript::_new,MethodInfo("new")); + ObjectTypeDB::bind_native_method(METHOD_FLAGS_DEFAULT,"new",&GDScript::_new,MethodInfo(Variant::OBJECT,"new")); ObjectTypeDB::bind_method(_MD("get_as_byte_code"),&GDScript::get_as_byte_code); @@ -874,6 +962,10 @@ GDScript::~GDScript() { memdelete( E->get() ); } + for (Map<StringName,Ref<GDScript> >::Element *E=subclasses.front();E;E=E->next()) { + E->get()->_owner=NULL; //bye, you are no longer owned cause I died + } + #ifdef DEBUG_ENABLED if (GDScriptLanguage::get_singleton()->lock) { GDScriptLanguage::get_singleton()->lock->lock(); @@ -960,11 +1052,16 @@ bool GDInstance::get(const StringName& p_name, Variant &r_ret) const { } { - const Map<StringName,Variant>::Element *E = script->constants.find(p_name); - if (E) { - r_ret=E->get(); - return true; //index found + const GDScript *sl = sptr; + while(sl) { + const Map<StringName,Variant>::Element *E = sl->constants.find(p_name); + if (E) { + r_ret=E->get(); + return true; //index found + + } + sl=sl->_base; } } @@ -1213,6 +1310,8 @@ void GDInstance::call_multilevel_reversed(const StringName& p_method,const Varia } } + + void GDInstance::notification(int p_notification) { //notification is not virutal, it gets called at ALL levels just like in C. @@ -1245,6 +1344,46 @@ ScriptLanguage *GDInstance::get_language() { return GDScriptLanguage::get_singleton(); } +GDInstance::RPCMode GDInstance::get_rpc_mode(const StringName& p_method) const { + + const GDScript *cscript = script.ptr(); + + while(cscript) { + const Map<StringName,GDFunction*>::Element *E=cscript->member_functions.find(p_method); + if (E) { + + if (E->get()->get_rpc_mode()!=RPC_MODE_DISABLED) { + return E->get()->get_rpc_mode(); + } + + } + cscript=cscript->_base; + } + + return RPC_MODE_DISABLED; +} + +GDInstance::RPCMode GDInstance::get_rset_mode(const StringName& p_variable) const { + + const GDScript *cscript = script.ptr(); + + while(cscript) { + const Map<StringName,GDScript::MemberInfo>::Element *E=cscript->member_indices.find(p_variable); + if (E) { + + if (E->get().rpc_mode) { + return E->get().rpc_mode; + } + + } + cscript=cscript->_base; + } + + return RPC_MODE_DISABLED; +} + + + void GDInstance::reload_members() { #ifdef DEBUG_ENABLED @@ -1558,6 +1697,127 @@ void GDScriptLanguage::reload_all_scripts() { #endif } + +void GDScriptLanguage::reload_tool_script(const Ref<Script>& p_script,bool p_soft_reload) { + + +#ifdef DEBUG_ENABLED + + if (lock) { + lock->lock(); + } + + List<Ref<GDScript> > scripts; + + SelfList<GDScript> *elem=script_list.first(); + while(elem) { + if (elem->self()->get_path().is_resource_file()) { + + scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident + } + elem=elem->next(); + } + + if (lock) { + lock->unlock(); + } + + //when someone asks you why dynamically typed languages are easier to write.... + + Map< Ref<GDScript>, Map<ObjectID,List<Pair<StringName,Variant> > > > to_reload; + + //as scripts are going to be reloaded, must proceed without locking here + + scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order + + for(List<Ref<GDScript> >::Element *E=scripts.front();E;E=E->next()) { + + bool reload = E->get()==p_script || to_reload.has(E->get()->get_base()); + + if (!reload) + continue; + + to_reload.insert(E->get(),Map<ObjectID,List<Pair<StringName,Variant> > >()); + + if (!p_soft_reload) { + + //save state and remove script from instances + Map<ObjectID,List<Pair<StringName,Variant> > >& map = to_reload[E->get()]; + + while(E->get()->instances.front()) { + Object *obj = E->get()->instances.front()->get(); + //save instance info + List<Pair<StringName,Variant> > state; + if (obj->get_script_instance()) { + + obj->get_script_instance()->get_property_state(state); + map[obj->get_instance_ID()]=state; + obj->set_script(RefPtr()); + } + } + + //same thing for placeholders +#ifdef TOOLS_ENABLED + + while(E->get()->placeholders.size()) { + + Object *obj = E->get()->placeholders.front()->get()->get_owner(); + //save instance info + List<Pair<StringName,Variant> > state; + if (obj->get_script_instance()) { + + obj->get_script_instance()->get_property_state(state); + map[obj->get_instance_ID()]=state; + obj->set_script(RefPtr()); + } + } +#endif + + for(Map<ObjectID,List<Pair<StringName,Variant> > >::Element *F=E->get()->pending_reload_state.front();F;F=F->next()) { + map[F->key()]=F->get(); //pending to reload, use this one instead + } + } + } + + for(Map< Ref<GDScript>, Map<ObjectID,List<Pair<StringName,Variant> > > >::Element *E=to_reload.front();E;E=E->next()) { + + Ref<GDScript> scr = E->key(); + scr->reload(p_soft_reload); + + //restore state if saved + for (Map<ObjectID,List<Pair<StringName,Variant> > >::Element *F=E->get().front();F;F=F->next()) { + + Object *obj = ObjectDB::get_instance(F->key()); + if (!obj) + continue; + + if (!p_soft_reload) { + //clear it just in case (may be a pending reload state) + obj->set_script(RefPtr()); + } + obj->set_script(scr.get_ref_ptr()); + if (!obj->get_script_instance()) { + //failed, save reload state for next time if not saved + if (!scr->pending_reload_state.has(obj->get_instance_ID())) { + scr->pending_reload_state[obj->get_instance_ID()]=F->get(); + } + continue; + } + + for (List<Pair<StringName,Variant> >::Element *G=F->get().front();G;G=G->next()) { + obj->get_script_instance()->set(G->get().first,G->get().second); + } + + scr->pending_reload_state.erase(obj->get_instance_ID()); //as it reloaded, remove pending state + } + + //if instance states were saved, set them! + } + + +#endif +} + void GDScriptLanguage::frame() { // print_line("calls: "+itos(calls)); @@ -1602,6 +1862,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "false", "float", "int", + "bool", "null", "PI", "self", @@ -1634,6 +1895,10 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "pass", "return", "while", + "remote", + "sync", + "master", + "slave", 0}; @@ -1791,6 +2056,11 @@ Error ResourceFormatSaverGDScript::save(const String &p_path,const RES& p_resour } file->close(); memdelete(file); + + if (ScriptServer::is_reload_scripts_on_save_enabled()) { + GDScriptLanguage::get_singleton()->reload_tool_script(p_resource,false); + } + return OK; } diff --git a/modules/gdscript/gd_script.h b/modules/gdscript/gd_script.h index f052d13685..0c3e1eb614 100644 --- a/modules/gdscript/gd_script.h +++ b/modules/gdscript/gd_script.h @@ -64,6 +64,7 @@ class GDScript : public Script { int index; StringName setter; StringName getter; + ScriptInstance::RPCMode rpc_mode; }; friend class GDInstance; @@ -120,7 +121,11 @@ friend class GDScriptLanguage; virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder); #endif +#ifdef DEBUG_ENABLED + + Map<ObjectID,List<Pair<StringName,Variant> > > pending_reload_state; +#endif bool _update_exports(); @@ -157,6 +162,8 @@ public: Variant _new(const Variant** p_args,int p_argcount,Variant::CallError& r_error); virtual bool can_instance() const; + virtual Ref<Script> get_base_script() const; + virtual StringName get_instance_base_type() const; // this may not work in all scripts, will return empty if so virtual ScriptInstance* instance_create(Object *p_this); virtual bool instance_has(const Object *p_this) const; @@ -177,6 +184,13 @@ public: bool get_property_default_value(const StringName& p_property,Variant& r_value) const; + virtual void get_script_method_list(List<MethodInfo> *p_list) const; + virtual bool has_method(const StringName& p_method) const; + virtual MethodInfo get_method_info(const StringName& p_method) const; + + virtual void get_script_property_list(List<PropertyInfo> *p_list) const; + + virtual ScriptLanguage *get_language() const; GDScript(); @@ -202,6 +216,8 @@ friend class GDCompiler; public: + _FORCE_INLINE_ Object* get_owner() { return owner; } + virtual bool set(const StringName& p_name, const Variant& p_value); virtual bool get(const StringName& p_name, Variant &r_ret) const; virtual void get_property_list(List<PropertyInfo> *p_properties) const; @@ -226,6 +242,10 @@ public: void reload_members(); + virtual RPCMode get_rpc_mode(const StringName& p_method) const; + virtual RPCMode get_rset_mode(const StringName& p_variable) const; + + GDInstance(); ~GDInstance(); @@ -240,29 +260,31 @@ class GDScriptLanguage : public ScriptLanguage { Map<StringName,int> globals; - struct CallLevel { + struct CallLevel { - Variant *stack; - GDFunction *function; - GDInstance *instance; - int *ip; - int *line; + Variant *stack; + GDFunction *function; + GDInstance *instance; + int *ip; + int *line; - }; + }; - int _debug_parse_err_line; - String _debug_parse_err_file; - String _debug_error; - int _debug_call_stack_pos; - int _debug_max_call_stack; - CallLevel *_call_stack; + int _debug_parse_err_line; + String _debug_parse_err_file; + String _debug_error; + int _debug_call_stack_pos; + int _debug_max_call_stack; + CallLevel *_call_stack; void _add_global(const StringName& p_name,const Variant& p_value); Mutex *lock; + + friend class GDScript; SelfList<GDScript>::List script_list; @@ -276,54 +298,54 @@ public: int calls; - bool debug_break(const String& p_error,bool p_allow_continue=true); - bool debug_break_parse(const String& p_file, int p_line,const String& p_error); + bool debug_break(const String& p_error,bool p_allow_continue=true); + bool debug_break_parse(const String& p_file, int p_line,const String& p_error); - _FORCE_INLINE_ void enter_function(GDInstance *p_instance,GDFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) { + _FORCE_INLINE_ void enter_function(GDInstance *p_instance,GDFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) { - if (Thread::get_main_ID()!=Thread::get_caller_ID()) - return; //no support for other threads than main for now + if (Thread::get_main_ID()!=Thread::get_caller_ID()) + return; //no support for other threads than main for now - if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) - ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() +1 ); + if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) + ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() +1 ); - if (_debug_call_stack_pos >= _debug_max_call_stack) { - //stack overflow - _debug_error="Stack Overflow (Stack Size: "+itos(_debug_max_call_stack)+")"; - ScriptDebugger::get_singleton()->debug(this); - return; - } + if (_debug_call_stack_pos >= _debug_max_call_stack) { + //stack overflow + _debug_error="Stack Overflow (Stack Size: "+itos(_debug_max_call_stack)+")"; + ScriptDebugger::get_singleton()->debug(this); + return; + } - _call_stack[_debug_call_stack_pos].stack=p_stack; - _call_stack[_debug_call_stack_pos].instance=p_instance; - _call_stack[_debug_call_stack_pos].function=p_function; - _call_stack[_debug_call_stack_pos].ip=p_ip; - _call_stack[_debug_call_stack_pos].line=p_line; - _debug_call_stack_pos++; - } + _call_stack[_debug_call_stack_pos].stack=p_stack; + _call_stack[_debug_call_stack_pos].instance=p_instance; + _call_stack[_debug_call_stack_pos].function=p_function; + _call_stack[_debug_call_stack_pos].ip=p_ip; + _call_stack[_debug_call_stack_pos].line=p_line; + _debug_call_stack_pos++; + } - _FORCE_INLINE_ void exit_function() { + _FORCE_INLINE_ void exit_function() { - if (Thread::get_main_ID()!=Thread::get_caller_ID()) - return; //no support for other threads than main for now + if (Thread::get_main_ID()!=Thread::get_caller_ID()) + return; //no support for other threads than main for now - if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) - ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() -1 ); + if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) + ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() -1 ); - if (_debug_call_stack_pos==0) { + if (_debug_call_stack_pos==0) { - _debug_error="Stack Underflow (Engine Bug)"; - ScriptDebugger::get_singleton()->debug(this); - return; - } + _debug_error="Stack Underflow (Engine Bug)"; + ScriptDebugger::get_singleton()->debug(this); + return; + } - _debug_call_stack_pos--; - } + _debug_call_stack_pos--; + } virtual Vector<StackInfo> debug_get_current_stack_info() { - if (Thread::get_main_ID()!=Thread::get_caller_ID()) - return Vector<StackInfo>(); + if (Thread::get_main_ID()!=Thread::get_caller_ID()) + return Vector<StackInfo>(); Vector<StackInfo> csi; csi.resize(_debug_call_stack_pos); @@ -365,7 +387,7 @@ public: virtual void get_reserved_words(List<String> *p_words) const; virtual void get_comment_delimiters(List<String> *p_delimiters) const; virtual void get_string_delimiters(List<String> *p_delimiters) const; - virtual String get_template(const String& p_class_name, const String& p_base_class_name) const; + virtual Ref<Script> get_template(const String& p_class_name, const String& p_base_class_name) const; virtual bool validate(const String& p_script,int &r_line_error,int &r_col_error,String& r_test_error, const String& p_path="",List<String> *r_functions=NULL) const; virtual Script *create_script() const; virtual bool has_named_classes() const; @@ -389,6 +411,7 @@ public: virtual String debug_parse_stack_level_expression(int p_level,const String& p_expression,int p_max_subitems=-1,int p_max_depth=-1); virtual void reload_all_scripts(); + virtual void reload_tool_script(const Ref<Script>& p_script,bool p_soft_reload); virtual void frame(); diff --git a/modules/gdscript/gd_tokenizer.cpp b/modules/gdscript/gd_tokenizer.cpp index 56eacfd20e..47e740b227 100644 --- a/modules/gdscript/gd_tokenizer.cpp +++ b/modules/gdscript/gd_tokenizer.cpp @@ -79,8 +79,8 @@ const char* GDTokenizer::token_names[TK_MAX]={ "for", "do", "while", -"switch", -"case", +"switch (reserved)", +"case (reserved)", "break", "continue", "pass", @@ -100,6 +100,10 @@ const char* GDTokenizer::token_names[TK_MAX]={ "yield", "signal", "breakpoint", +"rpc", +"sync", +"master", +"slave", "'['", "']'", "'{'", @@ -725,7 +729,7 @@ void GDTokenizerText::_advance() { if (hexa_found) { int val = str.hex_to_int(); _make_constant(val); - } else if (period_found) { + } else if (period_found || exponent_found) { real_t val = str.to_double(); //print_line("*%*%*%*% to convert: "+str+" result: "+rtos(val)); _make_constant(val); @@ -865,6 +869,10 @@ void GDTokenizerText::_advance() { {TK_PR_YIELD,"yield"}, {TK_PR_SIGNAL,"signal"}, {TK_PR_BREAKPOINT,"breakpoint"}, + {TK_PR_REMOTE,"remote"}, + {TK_PR_MASTER,"master"}, + {TK_PR_SLAVE,"slave"}, + {TK_PR_SYNC,"sync"}, {TK_PR_CONST,"const"}, //controlflow {TK_CF_IF,"if"}, @@ -874,6 +882,7 @@ void GDTokenizerText::_advance() { {TK_CF_WHILE,"while"}, {TK_CF_DO,"do"}, {TK_CF_SWITCH,"switch"}, + {TK_CF_CASE,"case"}, {TK_CF_BREAK,"break"}, {TK_CF_CONTINUE,"continue"}, {TK_CF_RETURN,"return"}, @@ -1046,7 +1055,7 @@ void GDTokenizerText::advance(int p_amount) { ////////////////////////////////////////////////////////////////////////////////////////////////////// -#define BYTECODE_VERSION 10 +#define BYTECODE_VERSION 11 Error GDTokenizerBuffer::set_code_buffer(const Vector<uint8_t> & p_buffer) { @@ -1155,7 +1164,6 @@ Vector<uint8_t> GDTokenizerBuffer::parse_code_string(const String& p_code) { GDTokenizerText tt; tt.set_code(p_code); int line=-1; - int col=0; while(true) { diff --git a/modules/gdscript/gd_tokenizer.h b/modules/gdscript/gd_tokenizer.h index aaff573090..1815f82894 100644 --- a/modules/gdscript/gd_tokenizer.h +++ b/modules/gdscript/gd_tokenizer.h @@ -107,6 +107,10 @@ public: TK_PR_YIELD, TK_PR_SIGNAL, TK_PR_BREAKPOINT, + TK_PR_REMOTE, + TK_PR_SYNC, + TK_PR_MASTER, + TK_PR_SLAVE, TK_BRACKET_OPEN, TK_BRACKET_CLOSE, TK_CURLY_BRACKET_OPEN, diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 2aea494f39..95b18cae4d 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -1,14 +1,31 @@ -/*************************************************/ -/* register_script_types.cpp */ -/*************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/*************************************************/ -/* Source code within this file is: */ -/* (c) 2007-2016 Juan Linietsky, Ariel Manzur */ -/* All Rights Reserved. */ -/*************************************************/ - +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ #include "register_types.h" #include "gd_script.h" @@ -141,7 +158,7 @@ void register_gdscript_types() { void unregister_gdscript_types() { - + ScriptServer::unregister_language(script_language_gd); if (script_language_gd) memdelete( script_language_gd ); diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 503e723de2..6e73244b57 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -912,7 +912,6 @@ void GridMap::_octant_bake(const OctantKey &p_key, const Ref<TriangleMesh>& p_tm int lc = p_lights.size(); const BakeLight* bl = p_lights.ptr(); float ofs = cell_size*0.02; - float att = 0.2; for(;V;V=V->next()) { @@ -1809,6 +1808,7 @@ bool GridMap::is_using_baked_light() const{ } + GridMap::GridMap() { cell_size=2; @@ -1830,7 +1830,7 @@ GridMap::GridMap() { baked_light_instance=NULL; use_baked_light=false; - + navigation = NULL; } diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 87afe3d5a4..9bdad6713d 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -212,6 +212,18 @@ void GridMapEditor::_menu_option(int p_option) { _update_areas_display(); update_areas(); } break; + case MENU_OPTION_SELECTION_DUPLICATE: + if (!(selection.active && input_action==INPUT_NONE)) + return; + if (last_mouseover==Vector3(-1,-1,-1)) //nono mouseovering anythin + break; + + input_action=INPUT_DUPLICATE; + selection.click=last_mouseover; + selection.current=last_mouseover; + selection.duplicate_rot=0; + _update_duplicate_indicator(); + break; case MENU_OPTION_SELECTION_CLEAR: { if (!selection.active) return; @@ -377,7 +389,9 @@ bool GridMapEditor::do_input_action(Camera* p_camera,const Point2& p_point,bool int item=node->get_cell_item(cell[0],cell[1],cell[2]); if (item>=0) { selected_pallete=item; + theme_pallete->set_current(item); update_pallete(); + _update_cursor_instance(); } return true; } if (input_action==INPUT_PAINT) { @@ -530,29 +544,6 @@ bool GridMapEditor::forward_spatial_input_event(Camera* p_camera,const InputEven if (edit_mode->get_selected()==0) { // regular click switch (p_event.type) { - case InputEvent::KEY: { - - if (p_event.key.pressed && p_event.key.scancode==KEY_D && p_event.key.mod.shift && selection.active && input_action==INPUT_NONE) { - - if (last_mouseover==Vector3(-1,-1,-1)) //nono mouseovering anythin - return false; - - input_action=INPUT_DUPLICATE; - selection.click=last_mouseover; - selection.current=last_mouseover; - selection.duplicate_rot=0; - _update_duplicate_indicator(); - - - } - - if (p_event.key.pressed && p_event.key.scancode==KEY_DELETE && selection.active) { - - _delete_selection(); - return true; - } - - } break; case InputEvent::MOUSE_BUTTON: { if (p_event.mouse_button.button_index==BUTTON_WHEEL_UP && (p_event.mouse_button.mod.command || p_event.mouse_button.mod.shift)) { @@ -741,7 +732,7 @@ void GridMapEditor::update_pallete() { } float min_size = EDITOR_DEF("grid_map/preview_size",64); - theme_pallete->set_min_icon_size(Size2(min_size, min_size)); + theme_pallete->set_fixed_icon_size(Size2(min_size, min_size)); theme_pallete->set_fixed_column_width(min_size*3/2); theme_pallete->set_max_text_lines(2); @@ -851,6 +842,11 @@ void GridMapEditor::edit(GridMap *p_gridmap) { VS *vs = VS::get_singleton(); last_mouseover=Vector3(-1,-1,-1); + input_action=INPUT_NONE; + selection.active=false; + _update_selection_transform(); + _update_duplicate_indicator(); + spatial_editor = editor->get_editor_plugin_screen()->cast_to<SpatialEditorPlugin>(); if (!node) { @@ -1247,7 +1243,8 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { options->get_popup()->add_item("Create Exterior Connector",MENU_OPTION_SELECTION_MAKE_EXTERIOR_CONNECTOR); options->get_popup()->add_item("Erase Area",MENU_OPTION_REMOVE_AREA); options->get_popup()->add_separator(); - options->get_popup()->add_item("Selection -> Clear",MENU_OPTION_SELECTION_CLEAR); + options->get_popup()->add_item("Selection -> Duplicate",MENU_OPTION_SELECTION_DUPLICATE,KEY_MASK_SHIFT+KEY_INSERT); + options->get_popup()->add_item("Selection -> Clear",MENU_OPTION_SELECTION_CLEAR,KEY_MASK_SHIFT+KEY_DELETE); //options->get_popup()->add_separator(); //options->get_popup()->add_item("Configure",MENU_OPTION_CONFIGURE); @@ -1266,7 +1263,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { settings_pick_distance->set_max(10000.0f); settings_pick_distance->set_min(500.0f); settings_pick_distance->set_step(1.0f); - settings_pick_distance->set_val(EDITOR_DEF("gridmap_editor/pick_distance", 5000.0)); + settings_pick_distance->set_val(EDITOR_DEF("grid_map/pick_distance", 5000.0)); settings_vbc->add_margin_child("Pick Distance:", settings_pick_distance); clip_mode=CLIP_DISABLED; diff --git a/modules/gridmap/grid_map_editor_plugin.h b/modules/gridmap/grid_map_editor_plugin.h index fc43866ef3..535c51bcbf 100644 --- a/modules/gridmap/grid_map_editor_plugin.h +++ b/modules/gridmap/grid_map_editor_plugin.h @@ -167,6 +167,7 @@ class GridMapEditor : public VBoxContainer { MENU_OPTION_DUPLICATE_SELECTS, MENU_OPTION_SELECTION_MAKE_AREA, MENU_OPTION_SELECTION_MAKE_EXTERIOR_CONNECTOR, + MENU_OPTION_SELECTION_DUPLICATE, MENU_OPTION_SELECTION_CLEAR, MENU_OPTION_REMOVE_AREA, MENU_OPTION_GRIDMAP_SETTINGS diff --git a/modules/ik/ik.cpp b/modules/ik/ik.cpp index 6c383fdb55..35b3ba7e83 100644 --- a/modules/ik/ik.cpp +++ b/modules/ik/ik.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ /* ik.cpp */ +/* Copyright (c) 2016 Sergey Lapin <slapinid@gmail.com> */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ -/* This file is Copyright (c) 2016 Sergey Lapin <slapinid@gmail.com> */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -241,7 +241,7 @@ void InverseKinematics::_notification(int p_what) } } break; case NOTIFICATION_PROCESS: { - float delta = get_process_delta_time(); + Spatial *sksp = skel->cast_to<Spatial>(); if (!bound) break; diff --git a/modules/ik/ik.h b/modules/ik/ik.h index 9daddb229d..b57d69b026 100644 --- a/modules/ik/ik.h +++ b/modules/ik/ik.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* ik.h */ +/* ik.h */ +/* Copyright (c) 2016 Sergey Lapin <slapinid@gmail.com> */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ -/* This file is (c) 2016 Sergey Lapin <slapinid@gmail.com> */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/register_module_types.h b/modules/register_module_types.h index 3cc0422d80..683ce7c6b8 100644 --- a/modules/register_module_types.h +++ b/modules/register_module_types.h @@ -1,3 +1,31 @@ +/*************************************************************************/ +/* register_module_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ #ifndef REGISTER_MODULE_TYPES_H #define REGISTER_MODULE_TYPES_H diff --git a/modules/visual_script/SCsub b/modules/visual_script/SCsub new file mode 100644 index 0000000000..403fe68f66 --- /dev/null +++ b/modules/visual_script/SCsub @@ -0,0 +1,5 @@ +Import('env') + +env.add_source_files(env.modules_sources,"*.cpp") + +Export('env') diff --git a/modules/visual_script/config.py b/modules/visual_script/config.py new file mode 100644 index 0000000000..ea7e83378a --- /dev/null +++ b/modules/visual_script/config.py @@ -0,0 +1,11 @@ + + +def can_build(platform): + return True + + +def configure(env): + pass + + + diff --git a/modules/visual_script/register_types.cpp b/modules/visual_script/register_types.cpp new file mode 100644 index 0000000000..ad54149b51 --- /dev/null +++ b/modules/visual_script/register_types.cpp @@ -0,0 +1,131 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "visual_script.h" +#include "visual_script_editor.h" +#include "io/resource_loader.h" +#include "visual_script_nodes.h" +#include "visual_script_func_nodes.h" +#include "visual_script_builtin_funcs.h" +#include "visual_script_flow_control.h" +#include "visual_script_yield_nodes.h" +#include "visual_script_expression.h" + + +VisualScriptLanguage *visual_script_language=NULL; + + +void register_visual_script_types() { + + visual_script_language=memnew( VisualScriptLanguage ); + //script_language_gd->init(); + ScriptServer::register_language(visual_script_language); + + ObjectTypeDB::register_type<VisualScript>(); + ObjectTypeDB::register_virtual_type<VisualScriptNode>(); + ObjectTypeDB::register_virtual_type<VisualScriptFunctionState>(); + ObjectTypeDB::register_type<VisualScriptFunction>(); + ObjectTypeDB::register_type<VisualScriptOperator>(); + ObjectTypeDB::register_type<VisualScriptVariableSet>(); + ObjectTypeDB::register_type<VisualScriptVariableGet>(); + ObjectTypeDB::register_type<VisualScriptConstant>(); + ObjectTypeDB::register_type<VisualScriptIndexGet>(); + ObjectTypeDB::register_type<VisualScriptIndexSet>(); + ObjectTypeDB::register_type<VisualScriptGlobalConstant>(); + ObjectTypeDB::register_type<VisualScriptClassConstant>(); + ObjectTypeDB::register_type<VisualScriptMathConstant>(); + ObjectTypeDB::register_type<VisualScriptBasicTypeConstant>(); + ObjectTypeDB::register_type<VisualScriptEngineSingleton>(); + ObjectTypeDB::register_type<VisualScriptSceneNode>(); + ObjectTypeDB::register_type<VisualScriptSceneTree>(); + ObjectTypeDB::register_type<VisualScriptResourcePath>(); + ObjectTypeDB::register_type<VisualScriptSelf>(); + ObjectTypeDB::register_type<VisualScriptCustomNode>(); + ObjectTypeDB::register_type<VisualScriptSubCall>(); + ObjectTypeDB::register_type<VisualScriptComment>(); + ObjectTypeDB::register_type<VisualScriptConstructor>(); + ObjectTypeDB::register_type<VisualScriptLocalVar>(); + ObjectTypeDB::register_type<VisualScriptLocalVarSet>(); + ObjectTypeDB::register_type<VisualScriptInputAction>(); + ObjectTypeDB::register_type<VisualScriptDeconstruct>(); + ObjectTypeDB::register_type<VisualScriptPreload>(); + ObjectTypeDB::register_type<VisualScriptTypeCast>(); + + + ObjectTypeDB::register_type<VisualScriptFunctionCall>(); + ObjectTypeDB::register_type<VisualScriptPropertySet>(); + ObjectTypeDB::register_type<VisualScriptPropertyGet>(); +// ObjectTypeDB::register_type<VisualScriptScriptCall>(); + ObjectTypeDB::register_type<VisualScriptEmitSignal>(); + + ObjectTypeDB::register_type<VisualScriptReturn>(); + ObjectTypeDB::register_type<VisualScriptCondition>(); + ObjectTypeDB::register_type<VisualScriptWhile>(); + ObjectTypeDB::register_type<VisualScriptIterator>(); + ObjectTypeDB::register_type<VisualScriptSequence>(); + ObjectTypeDB::register_type<VisualScriptInputFilter>(); + ObjectTypeDB::register_type<VisualScriptSwitch >(); + + ObjectTypeDB::register_type<VisualScriptYield>(); + ObjectTypeDB::register_type<VisualScriptYieldSignal>(); + + ObjectTypeDB::register_type<VisualScriptBuiltinFunc>(); + + + ObjectTypeDB::register_type<VisualScriptExpression>(); + + register_visual_script_nodes(); + register_visual_script_func_nodes(); + register_visual_script_builtin_func_node(); + register_visual_script_flow_control_nodes(); + register_visual_script_yield_nodes(); + register_visual_script_expression_node(); + +#ifdef TOOLS_ENABLED + VisualScriptEditor::register_editor(); +#endif + + +} + +void unregister_visual_script_types() { + + + unregister_visual_script_nodes(); + + ScriptServer::unregister_language(visual_script_language); + +#ifdef TOOLS_ENABLED + VisualScriptEditor::free_clipboard(); +#endif + if (visual_script_language) + memdelete( visual_script_language ); + +} diff --git a/modules/visual_script/register_types.h b/modules/visual_script/register_types.h new file mode 100644 index 0000000000..0a5805eb0b --- /dev/null +++ b/modules/visual_script/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_visual_script_types(); +void unregister_visual_script_types(); diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp new file mode 100644 index 0000000000..d0a933a11c --- /dev/null +++ b/modules/visual_script/visual_script.cpp @@ -0,0 +1,2841 @@ +#include "visual_script.h" +#include "visual_script_nodes.h" +#include "scene/main/node.h" +#include "os/os.h" +#include "globals.h" + + + +//used by editor, this is not really saved +void VisualScriptNode::set_breakpoint(bool p_breakpoint) { + breakpoint=p_breakpoint; +} + +bool VisualScriptNode::is_breakpoint() const { + + return breakpoint; +} + +void VisualScriptNode::_notification(int p_what) { + + if (p_what==NOTIFICATION_POSTINITIALIZE) { + + int dvc = get_input_value_port_count(); + for(int i=0;i<dvc;i++) { + Variant::Type expected = get_input_value_port_info(i).type; + Variant::CallError ce; + default_input_values.push_back(Variant::construct(expected,NULL,0,ce,false)); + } + } +} + +void VisualScriptNode::ports_changed_notify(){ + + + default_input_values.resize( MAX(default_input_values.size(),get_input_value_port_count()) ); //let it grow as big as possible, we don't want to lose values on resize + + emit_signal("ports_changed"); + +} + +void VisualScriptNode::set_default_input_value(int p_port,const Variant& p_value) { + + ERR_FAIL_INDEX(p_port,default_input_values.size()); + + default_input_values[p_port]=p_value; + +#ifdef TOOLS_ENABLED + for (Set<VisualScript*>::Element *E=scripts_used.front();E;E=E->next()) { + E->get()->set_edited(true); + } +#endif + +} + +Variant VisualScriptNode::get_default_input_value(int p_port) const { + + ERR_FAIL_INDEX_V(p_port,default_input_values.size(),Variant()); + return default_input_values[p_port]; +} + +void VisualScriptNode::_set_default_input_values(Array p_values) { + + + default_input_values=p_values; +} + + +void VisualScriptNode::validate_input_default_values() { + + + + default_input_values.resize(get_input_value_port_count()); + + //actually validate on save + for(int i=0;i<get_input_value_port_count();i++) { + + Variant::Type expected = get_input_value_port_info(i).type; + + + if (expected==Variant::NIL || expected==default_input_values[i].get_type()) { + continue; + } else { + //not the same, reconvert + Variant::CallError ce; + Variant existing = default_input_values[i]; + const Variant *existingp=&existing; + default_input_values[i] = Variant::construct(expected,&existingp,1,ce,false); + if (ce.error!=Variant::CallError::CALL_OK) { + //could not convert? force.. + default_input_values[i] = Variant::construct(expected,NULL,0,ce,false); + } + } + } +} + +Array VisualScriptNode::_get_default_input_values() const { + + //validate on save, since on load there is little info about this + + return default_input_values; +} + + + +void VisualScriptNode::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("get_visual_script:VisualScript"),&VisualScriptNode::get_visual_script); + ObjectTypeDB::bind_method(_MD("set_default_input_value","port_idx","value:Variant"),&VisualScriptNode::set_default_input_value); + ObjectTypeDB::bind_method(_MD("get_default_input_value:Variant","port_idx"),&VisualScriptNode::get_default_input_value); + ObjectTypeDB::bind_method(_MD("_set_default_input_values","values"),&VisualScriptNode::_set_default_input_values); + ObjectTypeDB::bind_method(_MD("_get_default_input_values"),&VisualScriptNode::_get_default_input_values); + + ADD_PROPERTY(PropertyInfo(Variant::ARRAY,"_default_input_values",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("_set_default_input_values"),_SCS("_get_default_input_values")); + ADD_SIGNAL(MethodInfo("ports_changed")); +} + +VisualScriptNode::TypeGuess VisualScriptNode::guess_output_type(TypeGuess* p_inputs,int p_output) const { + + PropertyInfo pinfo = get_output_value_port_info(p_output); + + TypeGuess tg; + + tg.type=pinfo.type; + if (pinfo.hint==PROPERTY_HINT_RESOURCE_TYPE) { + tg.obj_type=pinfo.hint_string; + } + + return tg; +} + +Ref<VisualScript> VisualScriptNode::get_visual_script() const { + + if (scripts_used.size()) + return Ref<VisualScript>(scripts_used.front()->get()); + + return Ref<VisualScript>(); +} + +VisualScriptNode::VisualScriptNode() { + breakpoint=false; +} + +//////////////// + +///////////////////// + +VisualScriptNodeInstance::VisualScriptNodeInstance() { + + sequence_outputs=NULL; + input_ports=NULL; +} + +VisualScriptNodeInstance::~VisualScriptNodeInstance() { + + if (sequence_outputs) { + memdelete_arr(sequence_outputs); + } + + if (input_ports) { + memdelete_arr(input_ports); + } + + if (output_ports) { + memdelete_arr(output_ports); + } + +} + +void VisualScript::add_function(const StringName& p_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!String(p_name).is_valid_identifier()); + ERR_FAIL_COND(functions.has(p_name)); + + functions[p_name]=Function(); + functions[p_name].scroll=Vector2(-50,-100); +} + +bool VisualScript::has_function(const StringName& p_name) const { + + return functions.has(p_name); + +} +void VisualScript::remove_function(const StringName& p_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_name)); + + for (Map<int,Function::NodeData>::Element *E=functions[p_name].nodes.front();E;E=E->next()) { + + E->get().node->disconnect("ports_changed",this,"_node_ports_changed"); + E->get().node->scripts_used.erase(this); + } + + functions.erase(p_name); + +} + +void VisualScript::rename_function(const StringName& p_name,const StringName& p_new_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_name)); + if (p_new_name==p_name) + return; + + ERR_FAIL_COND(!String(p_new_name).is_valid_identifier()); + + ERR_FAIL_COND(functions.has(p_new_name)); + ERR_FAIL_COND(variables.has(p_new_name)); + ERR_FAIL_COND(custom_signals.has(p_new_name)); + + functions[p_new_name]=functions[p_name]; + functions.erase(p_name); + +} + +void VisualScript::set_function_scroll(const StringName& p_name, const Vector2& p_scroll) { + + ERR_FAIL_COND(!functions.has(p_name)); + functions[p_name].scroll=p_scroll; + +} + +Vector2 VisualScript::get_function_scroll(const StringName& p_name) const { + + ERR_FAIL_COND_V(!functions.has(p_name),Vector2()); + return functions[p_name].scroll; + +} + + +void VisualScript::get_function_list(List<StringName> *r_functions) const { + + for (const Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + r_functions->push_back(E->key()); + } + + r_functions->sort_custom<StringName::AlphCompare>(); + +} + +int VisualScript::get_function_node_id(const StringName& p_name) const { + + ERR_FAIL_COND_V(!functions.has(p_name),-1); + + return functions[p_name].function_id; + +} + + +void VisualScript::_node_ports_changed(int p_id) { + + + + StringName function; + + for (Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + + if (E->get().nodes.has(p_id)) { + function=E->key(); + break; + } + } + + ERR_FAIL_COND(function==StringName()); + + Function &func = functions[function]; + Ref<VisualScriptNode> vsn = func.nodes[p_id].node; + + if (OS::get_singleton()->get_main_loop() && OS::get_singleton()->get_main_loop()->cast_to<SceneTree>() && OS::get_singleton()->get_main_loop()->cast_to<SceneTree>()->is_editor_hint()) { + vsn->validate_input_default_values(); //force validate default values when editing on editor + } + + //must revalidate all the functions + + { + List<SequenceConnection> to_remove; + + for (Set<SequenceConnection>::Element *E=func.sequence_connections.front();E;E=E->next()) { + if (E->get().from_node==p_id && E->get().from_output>=vsn->get_output_sequence_port_count()) { + + to_remove.push_back(E->get()); + } + if (E->get().to_node==p_id && !vsn->has_input_sequence_port()) { + + to_remove.push_back(E->get()); + } + } + + while(to_remove.size()) { + func.sequence_connections.erase(to_remove.front()->get()); + to_remove.pop_front(); + } + } + + { + + List<DataConnection> to_remove; + + + for (Set<DataConnection>::Element *E=func.data_connections.front();E;E=E->next()) { + if (E->get().from_node==p_id && E->get().from_port>=vsn->get_output_value_port_count()) { + to_remove.push_back(E->get()); + } + if (E->get().to_node==p_id && E->get().to_port>=vsn->get_input_value_port_count()) { + to_remove.push_back(E->get()); + } + } + + while(to_remove.size()) { + func.data_connections.erase(to_remove.front()->get()); + to_remove.pop_front(); + } + } + +#ifdef TOOLS_ENABLED + set_edited(true); //something changed, let's set as edited + emit_signal("node_ports_changed",function,p_id); +#endif +} + +void VisualScript::add_node(const StringName& p_func,int p_id, const Ref<VisualScriptNode>& p_node, const Point2 &p_pos) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_func)); + + + for (Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + + ERR_FAIL_COND(E->get().nodes.has(p_id)); //id can exist only one in script, even for different functions + } + + Function &func = functions[p_func]; + + + if (p_node->cast_to<VisualScriptFunction>()) { + //the function indeed + ERR_EXPLAIN("A function node already has been set here."); + ERR_FAIL_COND(func.function_id>=0); + + func.function_id=p_id; + } + + Function::NodeData nd; + nd.node=p_node; + nd.pos=p_pos; + + Ref<VisualScriptNode> vsn = p_node; + vsn->connect("ports_changed",this,"_node_ports_changed",varray(p_id)); + vsn->scripts_used.insert(this); + + + + func.nodes[p_id]=nd; +} + +void VisualScript::remove_node(const StringName& p_func,int p_id){ + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_func)); + Function &func = functions[p_func]; + + ERR_FAIL_COND(!func.nodes.has(p_id)); + { + List<SequenceConnection> to_remove; + + for (Set<SequenceConnection>::Element *E=func.sequence_connections.front();E;E=E->next()) { + if (E->get().from_node==p_id || E->get().to_node==p_id) { + to_remove.push_back(E->get()); + } + } + + while(to_remove.size()) { + func.sequence_connections.erase(to_remove.front()->get()); + to_remove.pop_front(); + } + } + + { + + List<DataConnection> to_remove; + + + for (Set<DataConnection>::Element *E=func.data_connections.front();E;E=E->next()) { + if (E->get().from_node==p_id || E->get().to_node==p_id) { + to_remove.push_back(E->get()); + } + } + + while(to_remove.size()) { + func.data_connections.erase(to_remove.front()->get()); + to_remove.pop_front(); + } + } + + if (func.nodes[p_id].node->cast_to<VisualScriptFunction>()) { + func.function_id=-1; //revert to invalid + } + + func.nodes[p_id].node->disconnect("ports_changed",this,"_node_ports_changed"); + func.nodes[p_id].node->scripts_used.erase(this); + + func.nodes.erase(p_id); + + +} + + +bool VisualScript::has_node(const StringName& p_func,int p_id) const { + + ERR_FAIL_COND_V(!functions.has(p_func),false); + const Function &func = functions[p_func]; + + return func.nodes.has(p_id); +} + +Ref<VisualScriptNode> VisualScript::get_node(const StringName& p_func,int p_id) const{ + + ERR_FAIL_COND_V(!functions.has(p_func),Ref<VisualScriptNode>()); + const Function &func = functions[p_func]; + + ERR_FAIL_COND_V(!func.nodes.has(p_id),Ref<VisualScriptNode>()); + + return func.nodes[p_id].node; +} + +void VisualScript::set_node_pos(const StringName& p_func,int p_id,const Point2& p_pos) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_func)); + Function &func = functions[p_func]; + + ERR_FAIL_COND(!func.nodes.has(p_id)); + func.nodes[p_id].pos=p_pos; +} + +Point2 VisualScript::get_node_pos(const StringName& p_func,int p_id) const{ + + ERR_FAIL_COND_V(!functions.has(p_func),Point2()); + const Function &func = functions[p_func]; + + ERR_FAIL_COND_V(!func.nodes.has(p_id),Point2()); + return func.nodes[p_id].pos; +} + + +void VisualScript::get_node_list(const StringName& p_func,List<int> *r_nodes) const{ + + ERR_FAIL_COND(!functions.has(p_func)); + const Function &func = functions[p_func]; + + for (const Map<int,Function::NodeData>::Element *E=func.nodes.front();E;E=E->next()) { + r_nodes->push_back(E->key()); + } + +} + + +void VisualScript::sequence_connect(const StringName& p_func,int p_from_node,int p_from_output,int p_to_node){ + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_func)); + Function &func = functions[p_func]; + + + SequenceConnection sc; + sc.from_node=p_from_node; + sc.from_output=p_from_output; + sc.to_node=p_to_node; + ERR_FAIL_COND(func.sequence_connections.has(sc)); + + func.sequence_connections.insert(sc); + +} + +void VisualScript::sequence_disconnect(const StringName& p_func,int p_from_node,int p_from_output,int p_to_node){ + + ERR_FAIL_COND(!functions.has(p_func)); + Function &func = functions[p_func]; + + SequenceConnection sc; + sc.from_node=p_from_node; + sc.from_output=p_from_output; + sc.to_node=p_to_node; + ERR_FAIL_COND(!func.sequence_connections.has(sc)); + + func.sequence_connections.erase(sc); + +} + +bool VisualScript::has_sequence_connection(const StringName& p_func,int p_from_node,int p_from_output,int p_to_node) const{ + + ERR_FAIL_COND_V(!functions.has(p_func),false); + const Function &func = functions[p_func]; + + SequenceConnection sc; + sc.from_node=p_from_node; + sc.from_output=p_from_output; + sc.to_node=p_to_node; + + return func.sequence_connections.has(sc); +} + +void VisualScript::get_sequence_connection_list(const StringName& p_func,List<SequenceConnection> *r_connection) const { + + ERR_FAIL_COND(!functions.has(p_func)); + const Function &func = functions[p_func]; + + for (const Set<SequenceConnection>::Element *E=func.sequence_connections.front();E;E=E->next()) { + r_connection->push_back(E->get()); + } +} + + +void VisualScript::data_connect(const StringName& p_func,int p_from_node,int p_from_port,int p_to_node,int p_to_port) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_func)); + Function &func = functions[p_func]; + + DataConnection dc; + dc.from_node=p_from_node; + dc.from_port=p_from_port; + dc.to_node=p_to_node; + dc.to_port=p_to_port; + + ERR_FAIL_COND( func.data_connections.has(dc)); + + func.data_connections.insert(dc); +} + +void VisualScript::data_disconnect(const StringName& p_func,int p_from_node,int p_from_port,int p_to_node,int p_to_port) { + + ERR_FAIL_COND(!functions.has(p_func)); + Function &func = functions[p_func]; + + DataConnection dc; + dc.from_node=p_from_node; + dc.from_port=p_from_port; + dc.to_node=p_to_node; + dc.to_port=p_to_port; + + ERR_FAIL_COND( !func.data_connections.has(dc)); + + func.data_connections.erase(dc); + +} + +bool VisualScript::has_data_connection(const StringName& p_func,int p_from_node,int p_from_port,int p_to_node,int p_to_port) const { + + ERR_FAIL_COND_V(!functions.has(p_func),false); + const Function &func = functions[p_func]; + + DataConnection dc; + dc.from_node=p_from_node; + dc.from_port=p_from_port; + dc.to_node=p_to_node; + dc.to_port=p_to_port; + + return func.data_connections.has(dc); + +} + +bool VisualScript::is_input_value_port_connected(const StringName& p_func,int p_node,int p_port) const { + + ERR_FAIL_COND_V(!functions.has(p_func),false); + const Function &func = functions[p_func]; + + for (const Set<DataConnection>::Element *E=func.data_connections.front();E;E=E->next()) { + if (E->get().to_node==p_node && E->get().to_port==p_port) + return true; + } + + return false; +} + +bool VisualScript::get_input_value_port_connection_source(const StringName& p_func,int p_node,int p_port,int *r_node,int *r_port) const { + + ERR_FAIL_COND_V(!functions.has(p_func),false); + const Function &func = functions[p_func]; + + for (const Set<DataConnection>::Element *E=func.data_connections.front();E;E=E->next()) { + if (E->get().to_node==p_node && E->get().to_port==p_port) { + *r_node=E->get().from_node; + *r_port=E->get().from_port; + return true; + } + } + + return false; + +} + +void VisualScript::get_data_connection_list(const StringName& p_func,List<DataConnection> *r_connection) const { + + ERR_FAIL_COND(!functions.has(p_func)); + const Function &func = functions[p_func]; + + for (const Set<DataConnection>::Element *E=func.data_connections.front();E;E=E->next()) { + r_connection->push_back(E->get()); + } +} + +void VisualScript::add_variable(const StringName& p_name,const Variant& p_default_value,bool p_export) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!String(p_name).is_valid_identifier()); + ERR_FAIL_COND(variables.has(p_name)); + + Variable v; + v.default_value=p_default_value; + v.info.type=p_default_value.get_type(); + v.info.name=p_name; + v.info.hint=PROPERTY_HINT_NONE; + v._export=p_export; + + variables[p_name]=v; + +#ifdef TOOLS_ENABLED + _update_placeholders(); +#endif + +} + +bool VisualScript::has_variable(const StringName& p_name) const { + + return variables.has(p_name); +} + +void VisualScript::remove_variable(const StringName& p_name) { + + ERR_FAIL_COND(!variables.has(p_name)); + variables.erase(p_name); + +#ifdef TOOLS_ENABLED + _update_placeholders(); +#endif +} + +void VisualScript::set_variable_default_value(const StringName& p_name,const Variant& p_value){ + + ERR_FAIL_COND(!variables.has(p_name)); + + variables[p_name].default_value=p_value; + +#ifdef TOOLS_ENABLED + _update_placeholders(); +#endif + + +} +Variant VisualScript::get_variable_default_value(const StringName& p_name) const{ + + ERR_FAIL_COND_V(!variables.has(p_name),Variant()); + return variables[p_name].default_value; + +} +void VisualScript::set_variable_info(const StringName& p_name,const PropertyInfo& p_info){ + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!variables.has(p_name)); + variables[p_name].info=p_info; + variables[p_name].info.name=p_name; + +#ifdef TOOLS_ENABLED + _update_placeholders(); +#endif + + +} +PropertyInfo VisualScript::get_variable_info(const StringName& p_name) const{ + + ERR_FAIL_COND_V(!variables.has(p_name),PropertyInfo()); + return variables[p_name].info; +} + +void VisualScript::set_variable_export(const StringName& p_name,bool p_export) { + + ERR_FAIL_COND(!variables.has(p_name)); + + variables[p_name]._export=p_export; +} + +bool VisualScript::get_variable_export(const StringName& p_name) const { + + ERR_FAIL_COND_V(!variables.has(p_name),false); + return variables[p_name]._export; + +} + + +void VisualScript::_set_variable_info(const StringName& p_name,const Dictionary& p_info) { + + PropertyInfo pinfo; + if (p_info.has("type")) + pinfo.type=Variant::Type(int(p_info["type"])); + if (p_info.has("name")) + pinfo.name=p_info["name"]; + if (p_info.has("hint")) + pinfo.hint=PropertyHint(int(p_info["hint"])); + if (p_info.has("hint_string")) + pinfo.hint_string=p_info["hint_string"]; + if (p_info.has("usage")) + pinfo.usage=p_info["usage"]; + + set_variable_info(p_name,pinfo); +} + +Dictionary VisualScript::_get_variable_info(const StringName& p_name) const{ + + PropertyInfo pinfo=get_variable_info(p_name); + Dictionary d; + d["type"]=pinfo.type; + d["name"]=pinfo.name; + d["hint"]=pinfo.hint; + d["hint_string"]=pinfo.hint_string; + d["usage"]=pinfo.usage; + + return d; +} + +void VisualScript::get_variable_list(List<StringName> *r_variables) const{ + + + for (Map<StringName,Variable>::Element *E=variables.front();E;E=E->next()) { + r_variables->push_back(E->key()); + } + + r_variables->sort_custom<StringName::AlphCompare>(); +} + + +void VisualScript::set_instance_base_type(const StringName& p_type) { + + ERR_FAIL_COND( instances.size() ); + base_type=p_type; +} + + +void VisualScript::rename_variable(const StringName& p_name,const StringName& p_new_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!variables.has(p_name)); + if (p_new_name==p_name) + return; + + ERR_FAIL_COND(!String(p_new_name).is_valid_identifier()); + + ERR_FAIL_COND(functions.has(p_new_name)); + ERR_FAIL_COND(variables.has(p_new_name)); + ERR_FAIL_COND(custom_signals.has(p_new_name)); + + variables[p_new_name]=variables[p_name]; + variables.erase(p_name); + +} + +void VisualScript::add_custom_signal(const StringName& p_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!String(p_name).is_valid_identifier()); + ERR_FAIL_COND(custom_signals.has(p_name)); + + custom_signals[p_name]=Vector<Argument>(); +} + +bool VisualScript::has_custom_signal(const StringName& p_name) const { + + return custom_signals.has(p_name); + +} +void VisualScript::custom_signal_add_argument(const StringName& p_func,Variant::Type p_type,const String& p_name,int p_index) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_func)); + Argument arg; + arg.type=p_type; + arg.name=p_name; + if (p_index<0) + custom_signals[p_func].push_back(arg); + else + custom_signals[p_func].insert(0,arg); + +} +void VisualScript::custom_signal_set_argument_type(const StringName& p_func,int p_argidx,Variant::Type p_type) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_func)); + ERR_FAIL_INDEX(p_argidx,custom_signals[p_func].size()); + custom_signals[p_func][p_argidx].type=p_type; +} +Variant::Type VisualScript::custom_signal_get_argument_type(const StringName& p_func,int p_argidx) const { + + ERR_FAIL_COND_V(!custom_signals.has(p_func),Variant::NIL); + ERR_FAIL_INDEX_V(p_argidx,custom_signals[p_func].size(),Variant::NIL); + return custom_signals[p_func][p_argidx].type; +} +void VisualScript::custom_signal_set_argument_name(const StringName& p_func,int p_argidx,const String& p_name) { + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_func)); + ERR_FAIL_INDEX(p_argidx,custom_signals[p_func].size()); + custom_signals[p_func][p_argidx].name=p_name; + +} +String VisualScript::custom_signal_get_argument_name(const StringName& p_func,int p_argidx) const { + + ERR_FAIL_COND_V(!custom_signals.has(p_func),String()); + ERR_FAIL_INDEX_V(p_argidx,custom_signals[p_func].size(),String()); + return custom_signals[p_func][p_argidx].name; + +} +void VisualScript::custom_signal_remove_argument(const StringName& p_func,int p_argidx) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_func)); + ERR_FAIL_INDEX(p_argidx,custom_signals[p_func].size()); + custom_signals[p_func].remove(p_argidx); + +} + +int VisualScript::custom_signal_get_argument_count(const StringName& p_func) const { + + ERR_FAIL_COND_V(!custom_signals.has(p_func),0); + return custom_signals[p_func].size(); + +} +void VisualScript::custom_signal_swap_argument(const StringName& p_func,int p_argidx,int p_with_argidx) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_func)); + ERR_FAIL_INDEX(p_argidx,custom_signals[p_func].size()); + ERR_FAIL_INDEX(p_with_argidx,custom_signals[p_func].size()); + + SWAP( custom_signals[p_func][p_argidx], custom_signals[p_func][p_with_argidx] ); + +} +void VisualScript::remove_custom_signal(const StringName& p_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_name)); + custom_signals.erase(p_name); + +} + +void VisualScript::rename_custom_signal(const StringName& p_name,const StringName& p_new_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_name)); + if (p_new_name==p_name) + return; + + ERR_FAIL_COND(!String(p_new_name).is_valid_identifier()); + + ERR_FAIL_COND(functions.has(p_new_name)); + ERR_FAIL_COND(variables.has(p_new_name)); + ERR_FAIL_COND(custom_signals.has(p_new_name)); + + custom_signals[p_new_name]=custom_signals[p_name]; + custom_signals.erase(p_name); + +} + +void VisualScript::get_custom_signal_list(List<StringName> *r_custom_signals) const { + + for (const Map<StringName,Vector<Argument> >::Element *E=custom_signals.front();E;E=E->next()) { + r_custom_signals->push_back(E->key()); + } + + r_custom_signals->sort_custom<StringName::AlphCompare>(); + +} + +int VisualScript::get_available_id() const { + + int max_id=0; + for (Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + if (E->get().nodes.empty()) + continue; + + int last_id = E->get().nodes.back()->key(); + max_id=MAX(max_id,last_id+1); + } + + return max_id; +} + +///////////////////////////////// + + +bool VisualScript::can_instance() const { + + return true;//ScriptServer::is_scripting_enabled(); + +} + + +StringName VisualScript::get_instance_base_type() const { + + return base_type; +} + +Ref<Script> VisualScript::get_base_script() const { + return Ref<Script>(); // no inheritance in visual script +} + + +#ifdef TOOLS_ENABLED +void VisualScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { + + + placeholders.erase(p_placeholder); +} + + +void VisualScript::_update_placeholders() { + + if (placeholders.size()==0) + return; //no bother if no placeholders + List<PropertyInfo> pinfo; + Map<StringName,Variant> values; + + for (Map<StringName,Variable>::Element *E=variables.front();E;E=E->next()) { + + if (!E->get()._export) + continue; + + PropertyInfo p = E->get().info; + p.name=String(E->key()); + pinfo.push_back(p); + values[p.name]=E->get().default_value; + } + + for (Set<PlaceHolderScriptInstance*>::Element *E=placeholders.front();E;E=E->next()) { + + E->get()->update(pinfo,values); + } + +} + +#endif + + +ScriptInstance* VisualScript::instance_create(Object *p_this) { + + + +#ifdef TOOLS_ENABLED + + if (!ScriptServer::is_scripting_enabled()) { + + + PlaceHolderScriptInstance *sins = memnew( PlaceHolderScriptInstance(VisualScriptLanguage::singleton,Ref<Script>((Script*)this),p_this)); + placeholders.insert(sins); + + List<PropertyInfo> pinfo; + Map<StringName,Variant> values; + + for (Map<StringName,Variable>::Element *E=variables.front();E;E=E->next()) { + + if (!E->get()._export) + continue; + + PropertyInfo p = E->get().info; + p.name=String(E->key()); + pinfo.push_back(p); + values[p.name]=E->get().default_value; + } + + sins->update(pinfo,values); + + return sins; + } +#endif + + + VisualScriptInstance *instance=memnew( VisualScriptInstance ); + instance->create(Ref<VisualScript>(this),p_this); + + + if (VisualScriptLanguage::singleton->lock) + VisualScriptLanguage::singleton->lock->lock(); + + instances[p_this]=instance; + + if (VisualScriptLanguage::singleton->lock) + VisualScriptLanguage::singleton->lock->unlock(); + + return instance; +} + +bool VisualScript::instance_has(const Object *p_this) const { + + return instances.has((Object*)p_this); +} + +bool VisualScript::has_source_code() const { + + return false; +} + +String VisualScript::get_source_code() const { + + return String(); +} + +void VisualScript::set_source_code(const String& p_code) { + +} + +Error VisualScript::reload(bool p_keep_state) { + + return OK; +} + + +bool VisualScript::is_tool() const { + + return false; +} + + +String VisualScript::get_node_type() const { + + return String(); +} + + +ScriptLanguage *VisualScript::get_language() const { + + return VisualScriptLanguage::singleton; +} + + +bool VisualScript::has_script_signal(const StringName& p_signal) const { + + return custom_signals.has(p_signal); +} + +void VisualScript::get_script_signal_list(List<MethodInfo> *r_signals) const { + + for (const Map<StringName,Vector<Argument> >::Element *E=custom_signals.front();E;E=E->next()) { + + MethodInfo mi; + mi.name=E->key(); + for(int i=0;i<E->get().size();i++) { + PropertyInfo arg; + arg.type=E->get()[i].type; + arg.name=E->get()[i].name; + mi.arguments.push_back(arg); + } + + + r_signals->push_back(mi); + } + + +} + + +bool VisualScript::get_property_default_value(const StringName& p_property,Variant& r_value) const { + + if (!variables.has(p_property)) + return false; + + r_value=variables[ p_property ].default_value; + return true; +} +void VisualScript::get_script_method_list(List<MethodInfo> *p_list) const { + + for (Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + + MethodInfo mi; + mi.name=E->key(); + if (E->get().function_id>=0) { + + Ref<VisualScriptFunction> func=E->get().nodes[E->get().function_id].node; + if (func.is_valid()) { + + for(int i=0;i<func->get_argument_count();i++) { + PropertyInfo arg; + arg.name=func->get_argument_name(i); + arg.type=func->get_argument_type(i); + mi.arguments.push_back(arg); + } + } + } + + p_list->push_back(mi); + } +} + +bool VisualScript::has_method(const StringName& p_method) const { + + return functions.has(p_method); +} +MethodInfo VisualScript::get_method_info(const StringName& p_method) const{ + + const Map<StringName,Function>::Element *E=functions.find(p_method); + if (!E) + return MethodInfo(); + + MethodInfo mi; + mi.name=E->key(); + if (E->get().function_id>=0) { + + Ref<VisualScriptFunction> func=E->get().nodes[E->get().function_id].node; + if (func.is_valid()) { + + for(int i=0;i<func->get_argument_count();i++) { + PropertyInfo arg; + arg.name=func->get_argument_name(i); + arg.type=func->get_argument_type(i); + mi.arguments.push_back(arg); + } + } + } + + return mi; +} + +void VisualScript::get_script_property_list(List<PropertyInfo> *p_list) const { + + List<StringName> vars; + get_variable_list(&vars); + + for (List<StringName>::Element *E=vars.front();E;E=E->next()) { + if (!variables[E->get()]._export) + continue; + p_list->push_back(variables[E->get()].info); + } +} + +#ifdef TOOLS_ENABLED +bool VisualScript::are_subnodes_edited() const { + + for(const Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + + for (const Map<int,Function::NodeData>::Element *F=E->get().nodes.front();F;F=F->next()) { + if (F->get().node->is_edited()) { + return true; + } + } + } + + return false; +} +#endif + +void VisualScript::_set_data(const Dictionary& p_data) { + + Dictionary d = p_data; + if (d.has("base_type")) + base_type=d["base_type"]; + + variables.clear(); + Array vars=d["variables"]; + for (int i=0;i<vars.size();i++) { + + Dictionary v=vars[i]; + StringName name = v["name"]; + add_variable(name); + _set_variable_info(name,v); + set_variable_default_value(name,v["default_value"]); + set_variable_export(name,v.has("export") && bool(v["export"])); + + } + + + custom_signals.clear(); + Array sigs=d["signals"]; + for (int i=0;i<sigs.size();i++) { + + Dictionary cs=sigs[i]; + add_custom_signal(cs["name"]); + + Array args=cs["arguments"]; + for(int j=0;j<args.size();j+=2) { + custom_signal_add_argument(cs["name"],Variant::Type(int(args[j+1])),args[j]); + } + } + + Array funcs=d["functions"]; + functions.clear(); + + for (int i=0;i<funcs.size();i++) { + + Dictionary func=funcs[i]; + + + StringName name=func["name"]; + //int id=func["function_id"]; + add_function(name); + + set_function_scroll(name,func["scroll"]); + + Array nodes = func["nodes"]; + + for(int i=0;i<nodes.size();i+=3) { + + add_node(name,nodes[i],nodes[i+2],nodes[i+1]); + } + + + Array sequence_connections=func["sequence_connections"]; + + for (int j=0;j<sequence_connections.size();j+=3) { + + sequence_connect(name,sequence_connections[j+0],sequence_connections[j+1],sequence_connections[j+2]); + } + + + Array data_connections=func["data_connections"]; + + for (int j=0;j<data_connections.size();j+=4) { + + data_connect(name,data_connections[j+0],data_connections[j+1],data_connections[j+2],data_connections[j+3]); + + } + + + } + +} + +Dictionary VisualScript::_get_data() const{ + + Dictionary d; + d["base_type"]=base_type; + Array vars; + for (const Map<StringName,Variable>::Element *E=variables.front();E;E=E->next()) { + + Dictionary var = _get_variable_info(E->key()); + var["name"]=E->key(); //make sure it's the right one + var["default_value"]=E->get().default_value; + var["export"]=E->get()._export; + vars.push_back(var); + } + d["variables"]=vars; + + Array sigs; + for (const Map<StringName,Vector<Argument> >::Element *E=custom_signals.front();E;E=E->next()) { + + Dictionary cs; + cs["name"]=E->key(); + Array args; + for(int i=0;i<E->get().size();i++) { + args.push_back(E->get()[i].name); + args.push_back(E->get()[i].type); + } + cs["arguments"]=args; + + sigs.push_back(cs); + } + + d["signals"]=sigs; + + Array funcs; + + for (const Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + + Dictionary func; + func["name"]=E->key(); + func["function_id"]=E->get().function_id; + func["scroll"]=E->get().scroll; + + Array nodes; + + for (const Map<int,Function::NodeData>::Element *F=E->get().nodes.front();F;F=F->next()) { + + nodes.push_back(F->key()); + nodes.push_back(F->get().pos); + nodes.push_back(F->get().node); + + } + + func["nodes"]=nodes; + + Array sequence_connections; + + for (const Set<SequenceConnection>::Element *F=E->get().sequence_connections.front();F;F=F->next()) { + + sequence_connections.push_back(F->get().from_node); + sequence_connections.push_back(F->get().from_output); + sequence_connections.push_back(F->get().to_node); + + } + + + func["sequence_connections"]=sequence_connections; + + Array data_connections; + + for (const Set<DataConnection>::Element *F=E->get().data_connections.front();F;F=F->next()) { + + data_connections.push_back(F->get().from_node); + data_connections.push_back(F->get().from_port); + data_connections.push_back(F->get().to_node); + data_connections.push_back(F->get().to_port); + + } + + + func["data_connections"]=data_connections; + + funcs.push_back(func); + + } + + d["functions"]=funcs; + + + return d; + +} + +void VisualScript::_bind_methods() { + + + + ObjectTypeDB::bind_method(_MD("_node_ports_changed"),&VisualScript::_node_ports_changed); + + ObjectTypeDB::bind_method(_MD("add_function","name"),&VisualScript::add_function); + ObjectTypeDB::bind_method(_MD("has_function","name"),&VisualScript::has_function); + ObjectTypeDB::bind_method(_MD("remove_function","name"),&VisualScript::remove_function); + ObjectTypeDB::bind_method(_MD("rename_function","name","new_name"),&VisualScript::rename_function); + ObjectTypeDB::bind_method(_MD("set_function_scroll","ofs"),&VisualScript::set_function_scroll); + ObjectTypeDB::bind_method(_MD("get_function_scroll"),&VisualScript::get_function_scroll); + + ObjectTypeDB::bind_method(_MD("add_node","func","id","node","pos"),&VisualScript::add_node,DEFVAL(Point2())); + ObjectTypeDB::bind_method(_MD("remove_node","func","id"),&VisualScript::remove_node); + ObjectTypeDB::bind_method(_MD("get_function_node_id","name"),&VisualScript::get_function_node_id); + + ObjectTypeDB::bind_method(_MD("get_node","func","id"),&VisualScript::get_node); + ObjectTypeDB::bind_method(_MD("has_node","func","id"),&VisualScript::has_node); + ObjectTypeDB::bind_method(_MD("set_node_pos","func","id","pos"),&VisualScript::set_node_pos); + ObjectTypeDB::bind_method(_MD("get_node_pos","func","id"),&VisualScript::get_node_pos); + + ObjectTypeDB::bind_method(_MD("sequence_connect","func","from_node","from_output","to_node"),&VisualScript::sequence_connect); + ObjectTypeDB::bind_method(_MD("sequence_disconnect","func","from_node","from_output","to_node"),&VisualScript::sequence_disconnect); + ObjectTypeDB::bind_method(_MD("has_sequence_connection","func","from_node","from_output","to_node"),&VisualScript::has_sequence_connection); + + ObjectTypeDB::bind_method(_MD("data_connect","func","from_node","from_port","to_node","to_port"),&VisualScript::data_connect); + ObjectTypeDB::bind_method(_MD("data_disconnect","func","from_node","from_port","to_node","to_port"),&VisualScript::data_disconnect); + ObjectTypeDB::bind_method(_MD("has_data_connection","func","from_node","from_port","to_node","to_port"),&VisualScript::has_data_connection); + + ObjectTypeDB::bind_method(_MD("add_variable","name","default_value","export"),&VisualScript::add_variable,DEFVAL(Variant()),DEFVAL(false)); + ObjectTypeDB::bind_method(_MD("has_variable","name"),&VisualScript::has_variable); + ObjectTypeDB::bind_method(_MD("remove_variable","name"),&VisualScript::remove_variable); + ObjectTypeDB::bind_method(_MD("set_variable_default_value","name","value"),&VisualScript::set_variable_default_value); + ObjectTypeDB::bind_method(_MD("get_variable_default_value","name"),&VisualScript::get_variable_default_value); + ObjectTypeDB::bind_method(_MD("set_variable_info","name","value"),&VisualScript::_set_variable_info); + ObjectTypeDB::bind_method(_MD("get_variable_info","name"),&VisualScript::_get_variable_info); + ObjectTypeDB::bind_method(_MD("set_variable_export","name","enable"),&VisualScript::set_variable_export); + ObjectTypeDB::bind_method(_MD("get_variable_export","name"),&VisualScript::get_variable_export); + ObjectTypeDB::bind_method(_MD("rename_variable","name","new_name"),&VisualScript::rename_variable); + + ObjectTypeDB::bind_method(_MD("add_custom_signal","name"),&VisualScript::add_custom_signal); + ObjectTypeDB::bind_method(_MD("has_custom_signal","name"),&VisualScript::has_custom_signal); + ObjectTypeDB::bind_method(_MD("custom_signal_add_argument","name","type","argname","index"),&VisualScript::custom_signal_add_argument,DEFVAL(-1)); + ObjectTypeDB::bind_method(_MD("custom_signal_set_argument_type","name","argidx","type"),&VisualScript::custom_signal_set_argument_type); + ObjectTypeDB::bind_method(_MD("custom_signal_get_argument_type","name","argidx"),&VisualScript::custom_signal_get_argument_type); + ObjectTypeDB::bind_method(_MD("custom_signal_set_argument_name","name","argidx","argname"),&VisualScript::custom_signal_set_argument_name); + ObjectTypeDB::bind_method(_MD("custom_signal_get_argument_name","name","argidx"),&VisualScript::custom_signal_get_argument_name); + ObjectTypeDB::bind_method(_MD("custom_signal_remove_argument","argidx"),&VisualScript::custom_signal_remove_argument); + ObjectTypeDB::bind_method(_MD("custom_signal_get_argument_count","name"),&VisualScript::custom_signal_get_argument_count); + ObjectTypeDB::bind_method(_MD("custom_signal_swap_argument","name","argidx","withidx"),&VisualScript::custom_signal_swap_argument); + ObjectTypeDB::bind_method(_MD("remove_custom_signal","name"),&VisualScript::remove_custom_signal); + ObjectTypeDB::bind_method(_MD("rename_custom_signal","name","new_name"),&VisualScript::rename_custom_signal); + + //ObjectTypeDB::bind_method(_MD("set_variable_info","name","info"),&VScript::set_variable_info); + //ObjectTypeDB::bind_method(_MD("get_variable_info","name"),&VScript::set_variable_info); + + ObjectTypeDB::bind_method(_MD("set_instance_base_type","type"),&VisualScript::set_instance_base_type); + + ObjectTypeDB::bind_method(_MD("_set_data","data"),&VisualScript::_set_data); + ObjectTypeDB::bind_method(_MD("_get_data"),&VisualScript::_get_data); + + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY,"data",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("_set_data"),_SCS("_get_data")); + + ADD_SIGNAL(MethodInfo("node_ports_changed",PropertyInfo(Variant::STRING,"function"),PropertyInfo(Variant::INT,"id"))); +} + +VisualScript::VisualScript() { + + base_type="Object"; + +} + +VisualScript::~VisualScript() { + + while(!functions.empty()) { + remove_function(functions.front()->key()); + } + +} + +//////////////////////////////////////////// + + + +bool VisualScriptInstance::set(const StringName& p_name, const Variant& p_value) { + + + Map<StringName,Variant>::Element *E=variables.find(p_name); + if (!E) + return false; + + E->get()=p_value; + + return true; +} + + +bool VisualScriptInstance::get(const StringName& p_name, Variant &r_ret) const { + + const Map<StringName,Variant>::Element *E=variables.find(p_name); + if (!E) + return false; + + return E->get(); +} +void VisualScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const{ + + for (const Map<StringName,VisualScript::Variable>::Element *E=script->variables.front();E;E=E->next()) { + + if (!E->get()._export) + continue; + PropertyInfo p = E->get().info; + p.name=String(E->key()); + p_properties->push_back(p); + + } +} +Variant::Type VisualScriptInstance::get_property_type(const StringName& p_name,bool *r_is_valid) const{ + + + const Map<StringName,VisualScript::Variable>::Element *E=script->variables.find(p_name); + if (!E) { + if (r_is_valid) + *r_is_valid=false; + ERR_FAIL_V(Variant::NIL); + } + + if (r_is_valid) + *r_is_valid=true; + + return E->get().info.type; + +} + +void VisualScriptInstance::get_method_list(List<MethodInfo> *p_list) const{ + + for (const Map<StringName,VisualScript::Function>::Element *E=script->functions.front();E;E=E->next()) { + + MethodInfo mi; + mi.name=E->key(); + if (E->get().function_id>=0 && E->get().nodes.has(E->get().function_id)) { + + Ref<VisualScriptFunction> vsf = E->get().nodes[E->get().function_id].node; + if (vsf.is_valid()) { + + for(int i=0;i<vsf->get_argument_count();i++) { + PropertyInfo arg; + arg.name=vsf->get_argument_name(i); + arg.type=vsf->get_argument_type(i); + + mi.arguments.push_back(arg); + } + + //vsf->Get_ for now at least it does not return.. + } + } + + p_list->push_back(mi); + } + +} +bool VisualScriptInstance::has_method(const StringName& p_method) const{ + + return script->functions.has(p_method); +} + + +//#define VSDEBUG(m_text) print_line(m_text) +#define VSDEBUG(m_text) + +void VisualScriptInstance::_dependency_step(VisualScriptNodeInstance* node,int p_pass,int *pass_stack,const Variant **input_args,Variant **output_args,Variant *variant_stack,Variant::CallError& r_error,String& error_str,VisualScriptNodeInstance** r_error_node) { + + ERR_FAIL_COND(node->pass_idx==-1); + + if (pass_stack[node->pass_idx]==p_pass) + return; + + pass_stack[node->pass_idx]=p_pass; + + if (!node->dependencies.empty()) { + + int dc = node->dependencies.size(); + VisualScriptNodeInstance **deps=node->dependencies.ptr(); + + for(int i=0;i<dc;i++) { + + _dependency_step(deps[i],p_pass,pass_stack,input_args,output_args,variant_stack,r_error,error_str,r_error_node); + if (r_error.error!=Variant::CallError::CALL_OK) + return; + + } + } + + + for(int i=0;i<node->input_port_count;i++) { + + int index = node->input_ports[i] & VisualScriptNodeInstance::INPUT_MASK; + + + if (node->input_ports[i] & VisualScriptNodeInstance::INPUT_DEFAULT_VALUE_BIT) { + //is a default value (unassigned input port) + input_args[i]=&default_values[index]; + } else { + //regular temporary in stack + input_args[i]=&variant_stack[index]; + + } + } + for(int i=0 ; i<node->output_port_count ; i++) { + output_args[i] = &variant_stack[ node->output_ports[i] ]; + } + + Variant *working_mem=node->working_mem_idx>=0 ? &variant_stack[node->working_mem_idx] : (Variant*)NULL; + + node->step(input_args,output_args,VisualScriptNodeInstance::START_MODE_BEGIN_SEQUENCE,working_mem,r_error,error_str); + //ignore return + if (r_error.error!=Variant::CallError::CALL_OK) { + *r_error_node=node; + } + +} + +Variant VisualScriptInstance::_call_internal(const StringName& p_method, void* p_stack, int p_stack_size, VisualScriptNodeInstance* p_node, int p_flow_stack_pos, int p_pass, bool p_resuming_yield, Variant::CallError &r_error) { + + Map<StringName,Function>::Element *F = functions.find(p_method); + ERR_FAIL_COND_V(!F,Variant()); + Function *f=&F->get(); + + //this call goes separate, so it can e yielded and suspended + Variant *variant_stack=(Variant*)p_stack; + bool *sequence_bits = (bool*)(variant_stack + f->max_stack); + const Variant **input_args=(const Variant**)(sequence_bits+f->node_count); + Variant **output_args=(Variant**)(input_args + max_input_args); + int flow_max = f->flow_stack_size; + int* flow_stack = flow_max? (int*)(output_args + max_output_args) : (int*)NULL; + int *pass_stack = flow_stack + flow_max; + + String error_str; + + VisualScriptNodeInstance* node=p_node; + bool error=false; + int current_node_id=f->node; + Variant return_value; + Variant *working_mem=NULL; + + int flow_stack_pos=p_flow_stack_pos; + +#ifdef DEBUG_ENABLED + if (ScriptDebugger::get_singleton()) { + VisualScriptLanguage::singleton->enter_function(this,&p_method,variant_stack,&working_mem,¤t_node_id); + } +#endif + + while(true) { + + p_pass++; //increment pass + current_node_id=node->get_id(); + + VSDEBUG("==========AT NODE: "+itos(current_node_id)+" base: "+node->get_base_node()->get_type()); + VSDEBUG("AT STACK POS: "+itos(flow_stack_pos)); + + + //setup working mem + working_mem=node->working_mem_idx>=0 ? &variant_stack[node->working_mem_idx] : (Variant*)NULL; + + VSDEBUG("WORKING MEM: "+itos(node->working_mem_idx)); + + if (current_node_id==f->node) { + //if function node, set up function arguments from begining of stack + + for(int i=0;i<f->argument_count;i++) { + input_args[i]=&variant_stack[i]; + } + } else { + + //run dependencies first + + + if (!node->dependencies.empty()) { + + int dc = node->dependencies.size(); + VisualScriptNodeInstance **deps=node->dependencies.ptr(); + + for(int i=0;i<dc;i++) { + + _dependency_step(deps[i],p_pass,pass_stack,input_args,output_args,variant_stack,r_error,error_str,&node); + if (r_error.error!=Variant::CallError::CALL_OK) { + error=true; + current_node_id=node->id; + break; + } + } + } + + if (!error) { + + //setup input pointers normally + VSDEBUG("INPUT PORTS: "+itos(node->input_port_count)); + + for(int i=0 ; i<node->input_port_count ; i++) { + + + int index = node->input_ports[i] & VisualScriptNodeInstance::INPUT_MASK; + + if (node->input_ports[i] & VisualScriptNodeInstance::INPUT_DEFAULT_VALUE_BIT) { + //is a default value (unassigned input port) + input_args[i]=&default_values[index]; + VSDEBUG("\tPORT "+itos(i)+" DEFAULT VAL"); + } else { + //regular temporary in stack + input_args[i]=&variant_stack[index]; + VSDEBUG("PORT "+itos(i)+" AT STACK "+itos(index)); + + } + } + } + } + + if (error) + break; + + //setup output pointers + + VSDEBUG("OUTPUT PORTS: "+itos(node->output_port_count)); + for(int i=0 ; i<node->output_port_count ; i++) { + output_args[i] = &variant_stack[ node->output_ports[i] ]; + VSDEBUG("PORT "+itos(i)+" AT STACK "+itos(node->output_ports[i])); + } + + //do step + + VisualScriptNodeInstance::StartMode start_mode; + { + if (p_resuming_yield) + start_mode=VisualScriptNodeInstance::START_MODE_RESUME_YIELD; + else if (!flow_stack || !(flow_stack[flow_stack_pos] & VisualScriptNodeInstance::FLOW_STACK_PUSHED_BIT)) //if there is a push bit, it means we are continuing a sequence + start_mode=VisualScriptNodeInstance::START_MODE_BEGIN_SEQUENCE; + else + start_mode=VisualScriptNodeInstance::START_MODE_CONTINUE_SEQUENCE; + } + + VSDEBUG("STEP - STARTSEQ: "+itos(start_mode)); + + int ret = node->step(input_args,output_args,start_mode,working_mem,r_error,error_str); + + if (r_error.error!=Variant::CallError::CALL_OK) { + //use error from step + error=true; + break; + } + + if (ret&VisualScriptNodeInstance::STEP_YIELD_BIT) { + //yielded! + if (node->get_working_memory_size()==0) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + error_str=RTR("A node yielded without working memory, please read the docs on how to yield properly!"); + error=true; + break; + + } else { + Ref<VisualScriptFunctionState> state = *working_mem; + if (!state.is_valid()) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + error_str=RTR("Node yielded, but did not return a function state in the first working memory."); + error=true; + break; + + } + + //step 1, capture all state + state->instance_id=get_owner_ptr()->get_instance_ID(); + state->script_id=get_script()->get_instance_ID(); + state->instance=this; + state->function=p_method; + state->working_mem_index=node->working_mem_idx; + state->variant_stack_size=f->max_stack; + state->node=node; + state->flow_stack_pos=flow_stack_pos; + state->stack.resize(p_stack_size); + state->pass=p_pass; + copymem(state->stack.ptr(),p_stack,p_stack_size); + //step 2, run away, return directly + r_error.error=Variant::CallError::CALL_OK; + + +#ifdef DEBUG_ENABLED + //will re-enter later, so exiting + if (ScriptDebugger::get_singleton()) { + VisualScriptLanguage::singleton->exit_function(); + } +#endif + + return state; + + } + } + +#ifdef DEBUG_ENABLED + if (ScriptDebugger::get_singleton()) { + // line + bool do_break=false; + + if (ScriptDebugger::get_singleton()->get_lines_left()>0) { + + if (ScriptDebugger::get_singleton()->get_depth()<=0) + ScriptDebugger::get_singleton()->set_lines_left( ScriptDebugger::get_singleton()->get_lines_left() -1 ); + if (ScriptDebugger::get_singleton()->get_lines_left()<=0) + do_break=true; + } + + if (ScriptDebugger::get_singleton()->is_breakpoint(current_node_id,source)) + do_break=true; + + if (do_break) { + VisualScriptLanguage::singleton->debug_break("Breakpoint",true); + } + + ScriptDebugger::get_singleton()->line_poll(); + + } +#endif + int output = ret & VisualScriptNodeInstance::STEP_MASK; + + VSDEBUG("STEP RETURN: "+itos(ret)); + + if (ret & VisualScriptNodeInstance::STEP_EXIT_FUNCTION_BIT) { + if (node->get_working_memory_size()==0) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + error_str=RTR("Return value must be assigned to first element of node working memory! Fix your node please."); + error=true; + } else { + //assign from working memory, first element + return_value=*working_mem; + } + + VSDEBUG("EXITING FUNCTION - VALUE "+String(return_value)); + break; //exit function requested, bye + } + + VisualScriptNodeInstance *next=NULL; //next node + + if ( (ret==output || ret&VisualScriptNodeInstance::STEP_FLAG_PUSH_STACK_BIT) && node->sequence_output_count) { + //if no exit bit was set, and has sequence outputs, guess next node + if (output<0 || output>=node->sequence_output_count) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + error_str=RTR("Node returned an invalid sequence output: ")+itos(output); + error=true; + break; + } + + next = node->sequence_outputs[output]; + if (next) { + VSDEBUG("GOT NEXT NODE - "+itos(next->get_id())); + } else { + VSDEBUG("GOT NEXT NODE - NULL"); + } + } + + if (flow_stack) { + + //update flow stack pos (may have changed) + flow_stack[flow_stack_pos] = current_node_id; + + //add stack push bit if requested + if (ret & VisualScriptNodeInstance::STEP_FLAG_PUSH_STACK_BIT) { + + flow_stack[flow_stack_pos] |= VisualScriptNodeInstance::FLOW_STACK_PUSHED_BIT; + sequence_bits[ node ->sequence_index ]=true; //remember sequence bit + VSDEBUG("NEXT SEQ - FLAG BIT"); + } else { + sequence_bits[ node ->sequence_index ]=false; //forget sequence bit + VSDEBUG("NEXT SEQ - NORMAL"); + } + + + if (ret & VisualScriptNodeInstance::STEP_FLAG_GO_BACK_BIT) { + //go back request + + if (flow_stack_pos>0) { + flow_stack_pos--; + node = instances[ flow_stack[flow_stack_pos] & VisualScriptNodeInstance::FLOW_STACK_MASK ]; + VSDEBUG("NEXT IS GO BACK"); + } else { + VSDEBUG("NEXT IS GO BACK, BUT NO NEXT SO EXIT"); + break; //simply exit without value or error + } + } else if (next) { + + + if (sequence_bits[next->sequence_index]) { + // what happened here is that we are entering a node that is in the middle of doing a sequence (pushed stack) from the front + // because each node has a working memory, we can't really do a sub-sequence + // as a result, the sequence will be restarted and the stack will roll back to find where this node + // started the sequence + + bool found = false; + + for(int i=flow_stack_pos;i>=0;i--) { + + + if ( (flow_stack[i] & VisualScriptNodeInstance::FLOW_STACK_MASK ) == next->get_id() ) { + flow_stack_pos=i; //roll back and remove bit + flow_stack[i]=next->get_id(); + sequence_bits[next->sequence_index]=false; + found=true; + } + } + + if (!found) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + error_str=RTR("Found sequence bit but not the node in the stack, report bug!"); + error=true; + break; + } + + node=next; + VSDEBUG("RE-ENTERED A LOOP, RETURNED STACK POS TO - "+itos(flow_stack_pos)); + + } else { + // check for stack overflow + if (flow_stack_pos+1 >= flow_max) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + error_str=RTR("Stack overflow with stack depth: ")+itos(output); + error=true; + break; + } + + node = next; + + flow_stack_pos++; + flow_stack[flow_stack_pos]=node->get_id(); + + VSDEBUG("INCREASE FLOW STACK"); + + } + + } else { + //no next node, try to go back in stack to pushed bit + + bool found = false; + + for(int i=flow_stack_pos;i>=0;i--) { + + VSDEBUG("FS "+itos(i)+" - "+itos(flow_stack[i])); + if (flow_stack[i] & VisualScriptNodeInstance::FLOW_STACK_PUSHED_BIT) { + + node = instances[ flow_stack[i] & VisualScriptNodeInstance::FLOW_STACK_MASK ]; + flow_stack_pos=i; + found=true; + break; + } + } + + if (!found) { + VSDEBUG("NO NEXT NODE, NO GO BACK, EXITING"); + break; //done, couldn't find a push stack bit + } + + VSDEBUG("NO NEXT NODE, GO BACK TO: "+itos(flow_stack_pos)); + + } + } else { + + node=next; //stackless mode, simply assign next node + } + + } + + + + if (error) { + + //error + // function, file, line, error, explanation + String err_file = script->get_path(); + String err_func = p_method; + int err_line=current_node_id; //not a line but it works as one + + if (node && (r_error.error!=Variant::CallError::CALL_ERROR_INVALID_METHOD || error_str==String())) { + + if (error_str!=String()) { + error_str+=" "; + } + + if (r_error.error==Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) { + int errorarg=r_error.argument; + error_str+="Cannot convert argument "+itos(errorarg+1)+" to "+Variant::get_type_name(r_error.expected)+"."; + } else if (r_error.error==Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { + error_str+="Expected "+itos(r_error.argument)+" arguments."; + } else if (r_error.error==Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { + error_str+="Expected "+itos(r_error.argument)+" arguments."; + } else if (r_error.error==Variant::CallError::CALL_ERROR_INVALID_METHOD) { + error_str+="Invalid Call."; + } else if (r_error.error==Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL) { + error_str+="Base Instance is null"; + } + } + + + //if (!GDScriptLanguage::get_singleton()->debug_break(err_text,false)) { + // debugger break did not happen + + if (!VisualScriptLanguage::singleton->debug_break(error_str,false)) { + + _err_print_error(err_func.utf8().get_data(),err_file.utf8().get_data(),err_line,error_str.utf8().get_data(),ERR_HANDLER_SCRIPT); + } + + //} + } else { + + + //return_value= + } + +#ifdef DEBUG_ENABLED + if (ScriptDebugger::get_singleton()) { + VisualScriptLanguage::singleton->exit_function(); + } +#endif + + //clean up variant stack + for(int i=0;i<f->max_stack;i++) { + variant_stack[i].~Variant(); + } + + + return return_value; +} + + +Variant VisualScriptInstance::call(const StringName& p_method, const Variant** p_args, int p_argcount, Variant::CallError &r_error){ + + r_error.error=Variant::CallError::CALL_OK; //ok by default + + Map<StringName,Function>::Element *F = functions.find(p_method); + if (!F) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return Variant(); + } + + VSDEBUG("CALLING: "+String(p_method)); + + Function *f=&F->get(); + + int total_stack_size=0; + + total_stack_size+=f->max_stack*sizeof(Variant); //variants + total_stack_size+=f->node_count*sizeof(bool); + total_stack_size+=(max_input_args+max_output_args)*sizeof(Variant*); //arguments + total_stack_size+=f->flow_stack_size*sizeof(int); //flow + total_stack_size+=f->pass_stack_size*sizeof(int); + + VSDEBUG("STACK SIZE: "+itos(total_stack_size)); + VSDEBUG("STACK VARIANTS: : "+itos(f->max_stack)); + VSDEBUG("SEQBITS: : "+itos(f->node_count)); + VSDEBUG("MAX INPUT: "+itos(max_input_args)); + VSDEBUG("MAX OUTPUT: "+itos(max_output_args)); + VSDEBUG("FLOW STACK SIZE: "+itos(f->flow_stack_size)); + VSDEBUG("PASS STACK SIZE: "+itos(f->pass_stack_size)); + + void *stack = alloca(total_stack_size); + + Variant *variant_stack=(Variant*)stack; + bool *sequence_bits = (bool*)(variant_stack + f->max_stack); + const Variant **input_args=(const Variant**)(sequence_bits+f->node_count); + Variant **output_args=(Variant**)(input_args + max_input_args); + int flow_max = f->flow_stack_size; + int* flow_stack = flow_max? (int*)(output_args + max_output_args) : (int*)NULL; + int *pass_stack = flow_stack + flow_max; + + for(int i=0;i<f->node_count;i++) { + sequence_bits[i]=false; //all starts as false + } + + zeromem(pass_stack,f->pass_stack_size*sizeof(int)); + + Map<int,VisualScriptNodeInstance*>::Element *E = instances.find(f->node); + if (!E) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + + ERR_EXPLAIN("No VisualScriptFunction node in function!"); + ERR_FAIL_V(Variant()); + } + + VisualScriptNodeInstance *node = E->get(); + + + if (flow_stack) { + flow_stack[0]=node->get_id(); + } + + VSDEBUG("ARGUMENTS: "+itos(f->argument_count)=" RECEIVED: "+itos(p_argcount)); + + if (p_argcount<f->argument_count) { + r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument=node->get_input_port_count(); + + return Variant(); + } + + if (p_argcount>f->argument_count) { + r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.argument=node->get_input_port_count(); + + return Variant(); + } + + //allocate variant stack + for(int i=0;i<f->max_stack;i++) { + memnew_placement(&variant_stack[i],Variant); + } + + //allocate function arguments (must be copied for yield to work properly) + for(int i=0;i<p_argcount;i++) { + variant_stack[i]=*p_args[i]; + } + + return _call_internal(p_method,stack,total_stack_size,node,0,0,false,r_error); + + +} + +void VisualScriptInstance::notification(int p_notification){ + + //do nothing as this is called using virtual + + Variant what=p_notification; + const Variant*whatp=&what; + Variant::CallError ce; + call(VisualScriptLanguage::singleton->notification,&whatp,1,ce); //do as call + +} + +Ref<Script> VisualScriptInstance::get_script() const{ + + return script; +} + +ScriptInstance::RPCMode VisualScriptInstance::get_rpc_mode(const StringName& p_method) const { + + const Map<StringName,VisualScript::Function>::Element *E = script->functions.find(p_method); + if (!E) { + return RPC_MODE_DISABLED; + } + + if (E->get().function_id>=0 && E->get().nodes.has(E->get().function_id)) { + + Ref<VisualScriptFunction> vsf = E->get().nodes[E->get().function_id].node; + if (vsf.is_valid()) { + + return vsf->get_rpc_mode(); + } + } + + return RPC_MODE_DISABLED; +} + +ScriptInstance::RPCMode VisualScriptInstance::get_rset_mode(const StringName& p_variable) const { + + return RPC_MODE_DISABLED; +} + + +void VisualScriptInstance::create(const Ref<VisualScript>& p_script,Object *p_owner) { + + script=p_script; + owner=p_owner; + source=p_script->get_path(); + + max_input_args = 0; + max_output_args = 0; + + if (p_owner->cast_to<Node>()) { + //turn on these if they exist and base is a node + Node* node = p_owner->cast_to<Node>(); + if (p_script->functions.has("_process")) + node->set_process(true); + if (p_script->functions.has("_fixed_process")) + node->set_fixed_process(true); + if (p_script->functions.has("_input")) + node->set_process_input(true); + if (p_script->functions.has("_unhandled_input")) + node->set_process_unhandled_input(true); + if (p_script->functions.has("_unhandled_key_input")) + node->set_process_unhandled_key_input(true); + } + + for(const Map<StringName,VisualScript::Variable>::Element *E=script->variables.front();E;E=E->next()) { + variables[E->key()]=E->get().default_value; + //no hacer que todo exporte, solo las que queres! + } + + + for(const Map<StringName,VisualScript::Function>::Element *E=script->functions.front();E;E=E->next()) { + + Function function; + function.node=E->get().function_id; + function.max_stack=0; + function.flow_stack_size=0; + function.pass_stack_size=0; + function.node_count=0; + Map<StringName,int> local_var_indices; + + if (function.node<0) { + VisualScriptLanguage::singleton->debug_break_parse(get_script()->get_path(),0,"No start node in function: "+String(E->key())); + + ERR_CONTINUE( function.node < 0 ); + } + + { + Ref<VisualScriptFunction> func_node = script->get_node(E->key(),E->get().function_id); + + if (func_node.is_null()) { + VisualScriptLanguage::singleton->debug_break_parse(get_script()->get_path(),0,"No VisualScriptFunction typed start node in function: "+String(E->key())); + } + + ERR_CONTINUE( !func_node.is_valid() ); + + function.argument_count=func_node->get_argument_count(); + function.max_stack+=function.argument_count; + function.flow_stack_size= func_node->is_stack_less() ? 0 : func_node->get_stack_size(); + + } + + //multiple passes are required to set up this complex thing.. + + + + + //first create the nodes + for (const Map<int,VisualScript::Function::NodeData>::Element *F=E->get().nodes.front();F;F=F->next()) { + + Ref<VisualScriptNode> node = F->get().node; + VisualScriptNodeInstance *instance = node->instance(this); //create instance + ERR_FAIL_COND(!instance); + + instance->base=node.ptr(); + + instance->id=F->key(); + instance->input_port_count = node->get_input_value_port_count(); + instance->input_ports=NULL; + instance->output_port_count = node->get_output_value_port_count(); + instance->output_ports=NULL; + instance->sequence_output_count = node->get_output_sequence_port_count(); + instance->sequence_index=function.node_count++; + instance->sequence_outputs=NULL; + instance->pass_idx=-1; + + if (instance->input_port_count) { + instance->input_ports = memnew_arr(int,instance->input_port_count); + for(int i=0;i<instance->input_port_count;i++) { + + instance->input_ports[i]=-1; //if not assigned, will become default value + } + } + + if (instance->output_port_count) { + instance->output_ports = memnew_arr(int,instance->output_port_count); + for(int i=0;i<instance->output_port_count;i++) { + instance->output_ports[i]=-1; //if not assigned, will output to trash + } + } + + if (instance->sequence_output_count) { + instance->sequence_outputs = memnew_arr(VisualScriptNodeInstance*,instance->sequence_output_count); + for(int i=0;i<instance->sequence_output_count;i++) { + instance->sequence_outputs[i]=NULL; //if it remains null, flow ends here + } + } + + if (node->cast_to<VisualScriptLocalVar>() || node->cast_to<VisualScriptLocalVarSet>()) { + //working memory is shared only for this node, for the same variables + Ref<VisualScriptLocalVar> vslv = node; + + StringName var_name; + + if (node->cast_to<VisualScriptLocalVar>()) + var_name = String(node->cast_to<VisualScriptLocalVar>()->get_var_name()).strip_edges(); + else + var_name = String(node->cast_to<VisualScriptLocalVarSet>()->get_var_name()).strip_edges(); + + if (!local_var_indices.has(var_name)) { + local_var_indices[var_name]=function.max_stack; + function.max_stack++; + } + + instance->working_mem_idx=local_var_indices[var_name]; + + } else if (instance->get_working_memory_size()) { + instance->working_mem_idx = function.max_stack; + function.max_stack+=instance->get_working_memory_size(); + } else { + instance->working_mem_idx=-1; //no working mem + } + + max_input_args = MAX( max_input_args, instance->input_port_count ); + max_output_args = MAX( max_output_args, instance->output_port_count ); + + instances[F->key()]=instance; + + + } + + function.trash_pos = function.max_stack++; //create pos for trash + + //second pass, do data connections + + for(const Set<VisualScript::DataConnection>::Element *F=E->get().data_connections.front();F;F=F->next()) { + + VisualScript::DataConnection dc = F->get(); + ERR_CONTINUE(!instances.has(dc.from_node)); + VisualScriptNodeInstance *from = instances[dc.from_node]; + ERR_CONTINUE(!instances.has(dc.to_node)); + VisualScriptNodeInstance *to = instances[dc.to_node]; + ERR_CONTINUE(dc.from_port >= from->output_port_count); + ERR_CONTINUE(dc.to_port >= to->input_port_count); + + if (from->output_ports[dc.from_port]==-1) { + + int stack_pos = function.max_stack++; + from->output_ports[dc.from_port] = stack_pos; + } + + + if (from->get_sequence_output_count()==0 && to->dependencies.find(from)==-1) { + //if the node we are reading from has no output sequence, we must call step() before reading from it. + if (from->pass_idx==-1) { + from->pass_idx=function.pass_stack_size; + function.pass_stack_size++; + } + to->dependencies.push_back(from); + } + + to->input_ports[dc.to_port] = from->output_ports[dc.from_port]; //read from wherever the stack is + + } + + //third pass, do sequence connections + + for(const Set<VisualScript::SequenceConnection>::Element *F=E->get().sequence_connections.front();F;F=F->next()) { + + VisualScript::SequenceConnection sc = F->get(); + ERR_CONTINUE(!instances.has(sc.from_node)); + VisualScriptNodeInstance *from = instances[sc.from_node]; + ERR_CONTINUE(!instances.has(sc.to_node)); + VisualScriptNodeInstance *to = instances[sc.to_node]; + ERR_CONTINUE(sc.from_output >= from->sequence_output_count); + + from->sequence_outputs[sc.from_output]=to; + + } + + //fourth pass: + // 1) unassigned input ports to default values + // 2) connect unassigned output ports to trash + + + for (const Map<int,VisualScript::Function::NodeData>::Element *F=E->get().nodes.front();F;F=F->next()) { + + ERR_CONTINUE(!instances.has(F->key())); + + Ref<VisualScriptNode> node = F->get().node; + VisualScriptNodeInstance *instance = instances[F->key()]; + + // conect to default values + for(int i=0;i<instance->input_port_count;i++) { + if (instance->input_ports[i]==-1) { + + //unassigned, connect to default val + instance->input_ports[i] = default_values.size() | VisualScriptNodeInstance::INPUT_DEFAULT_VALUE_BIT; + default_values.push_back( node->get_default_input_value(i) ); + } + } + + // conect to trash + for(int i=0;i<instance->output_port_count;i++) { + if (instance->output_ports[i]==-1) { + instance->output_ports[i] = function.trash_pos; //trash is same for all + } + } + } + + + functions[E->key()]=function; + } +} + +ScriptLanguage *VisualScriptInstance::get_language(){ + + return VisualScriptLanguage::singleton; +} + + +VisualScriptInstance::VisualScriptInstance() { + + +} + +VisualScriptInstance::~VisualScriptInstance() { + + if (VisualScriptLanguage::singleton->lock) + VisualScriptLanguage::singleton->lock->lock(); + + script->instances.erase(owner); + + if (VisualScriptLanguage::singleton->lock) + VisualScriptLanguage::singleton->lock->unlock(); + + for (Map<int,VisualScriptNodeInstance*>::Element *E=instances.front();E;E=E->next()) { + memdelete(E->get()); + } +} + + + +///////////////////////////////////////////// + + +///////////////////// + + +Variant VisualScriptFunctionState::_signal_callback(const Variant** p_args, int p_argcount, Variant::CallError& r_error) { + + ERR_FAIL_COND_V(function==StringName(),Variant()); + +#ifdef DEBUG_ENABLED + if (instance_id && !ObjectDB::get_instance(instance_id)) { + ERR_EXPLAIN("Resumed after yield, but class instance is gone"); + ERR_FAIL_V(Variant()); + } + + if (script_id && !ObjectDB::get_instance(script_id)) { + ERR_EXPLAIN("Resumed after yield, but script is gone"); + ERR_FAIL_V(Variant()); + } +#endif + + r_error.error=Variant::CallError::CALL_OK; + + Array args; + + if (p_argcount==0) { + r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument=1; + return Variant(); + } else if (p_argcount==1) { + //noooneee, reserved for me, me and only me. + } else { + + for(int i=0;i<p_argcount-1;i++) { + args.push_back(*p_args[i]); + } + } + + Ref<VisualScriptFunctionState> self = *p_args[p_argcount-1]; //hi, I'm myself, needed this to remain alive. + + if (self.is_null()) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=p_argcount-1; + r_error.expected=Variant::OBJECT; + return Variant(); + } + + r_error.error=Variant::CallError::CALL_OK; + + Variant *working_mem = ((Variant*)stack.ptr()) + working_mem_index; + + *working_mem=args; //arguments go to working mem. + + Variant ret = instance->_call_internal(function,stack.ptr(),stack.size(),node,flow_stack_pos,pass,true,r_error); + function=StringName(); //invalidate + return ret; +} + +void VisualScriptFunctionState::connect_to_signal(Object* p_obj, const String& p_signal, Array p_binds) { + + Vector<Variant> binds; + for(int i=0;i<p_binds.size();i++) { + binds.push_back(p_binds[i]); + } + binds.push_back(Ref<VisualScriptFunctionState>(this)); //add myself on the back to avoid dying from unreferencing + p_obj->connect(p_signal,this,"_signal_callback",binds); +} + +bool VisualScriptFunctionState::is_valid() const { + + return function!=StringName(); +} + +Variant VisualScriptFunctionState::resume(Array p_args) { + + ERR_FAIL_COND_V(function==StringName(),Variant()); +#ifdef DEBUG_ENABLED + if (instance_id && !ObjectDB::get_instance(instance_id)) { + ERR_EXPLAIN("Resumed after yield, but class instance is gone"); + ERR_FAIL_V(Variant()); + } + + if (script_id && !ObjectDB::get_instance(script_id)) { + ERR_EXPLAIN("Resumed after yield, but script is gone"); + ERR_FAIL_V(Variant()); + } +#endif + + Variant::CallError r_error; + r_error.error=Variant::CallError::CALL_OK; + + Variant *working_mem = ((Variant*)stack.ptr()) + working_mem_index; + + *working_mem=p_args; //arguments go to working mem. + + Variant ret= instance->_call_internal(function,stack.ptr(),stack.size(),node,flow_stack_pos,pass,true,r_error); + function=StringName(); //invalidate + return ret; +} + + +void VisualScriptFunctionState::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("connect_to_signal","obj","signals","args"),&VisualScriptFunctionState::connect_to_signal); + ObjectTypeDB::bind_method(_MD("resume:Array","args"),&VisualScriptFunctionState::resume,DEFVAL(Variant())); + ObjectTypeDB::bind_method(_MD("is_valid"),&VisualScriptFunctionState::is_valid); + ObjectTypeDB::bind_native_method(METHOD_FLAGS_DEFAULT,"_signal_callback",&VisualScriptFunctionState::_signal_callback,MethodInfo("_signal_callback")); +} + +VisualScriptFunctionState::VisualScriptFunctionState() { + +} + +VisualScriptFunctionState::~VisualScriptFunctionState() { + + if (function!=StringName()) { + Variant *s = ((Variant*)stack.ptr()); + for(int i=0;i<variant_stack_size;i++) { + s[i].~Variant(); + } + } +} + + + + + +/////////////////////////////////////////////// + +String VisualScriptLanguage::get_name() const { + + return "VisualScript"; +} + +/* LANGUAGE FUNCTIONS */ +void VisualScriptLanguage::init() { + + +} +String VisualScriptLanguage::get_type() const { + + return "VisualScript"; +} +String VisualScriptLanguage::get_extension() const { + + return "vs"; +} +Error VisualScriptLanguage::execute_file(const String& p_path) { + + return OK; +} +void VisualScriptLanguage::finish() { + + +} + +/* EDITOR FUNCTIONS */ +void VisualScriptLanguage::get_reserved_words(List<String> *p_words) const { + + +} +void VisualScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const { + + +} +void VisualScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { + + +} +Ref<Script> VisualScriptLanguage::get_template(const String& p_class_name, const String& p_base_class_name) const { + + Ref<VisualScript> script; + script.instance(); + script->set_instance_base_type(p_base_class_name); + return script; +} +bool VisualScriptLanguage::validate(const String& p_script, int &r_line_error,int &r_col_error,String& r_test_error, const String& p_path,List<String> *r_functions) const { + + return false; +} +Script *VisualScriptLanguage::create_script() const { + + return memnew( VisualScript ); +} +bool VisualScriptLanguage::has_named_classes() const { + + return false; +} +int VisualScriptLanguage::find_function(const String& p_function,const String& p_code) const { + + return -1; +} +String VisualScriptLanguage::make_function(const String& p_class,const String& p_name,const StringArray& p_args) const { + + return String(); +} + +void VisualScriptLanguage::auto_indent_code(String& p_code,int p_from_line,int p_to_line) const { + + +} +void VisualScriptLanguage::add_global_constant(const StringName& p_variable,const Variant& p_value) { + + +} + + +/* DEBUGGER FUNCTIONS */ + + + +bool VisualScriptLanguage::debug_break_parse(const String& p_file, int p_node,const String& p_error) { + //break because of parse error + + if (ScriptDebugger::get_singleton() && Thread::get_caller_ID()==Thread::get_main_ID()) { + + _debug_parse_err_node=p_node; + _debug_parse_err_file=p_file; + _debug_error=p_error; + ScriptDebugger::get_singleton()->debug(this,false); + return true; + } else { + return false; + } + +} + +bool VisualScriptLanguage::debug_break(const String& p_error,bool p_allow_continue) { + + if (ScriptDebugger::get_singleton() && Thread::get_caller_ID()==Thread::get_main_ID()) { + + _debug_parse_err_node=-1; + _debug_parse_err_file=""; + _debug_error=p_error; + ScriptDebugger::get_singleton()->debug(this,p_allow_continue); + return true; + } else { + return false; + } + +} + + +String VisualScriptLanguage::debug_get_error() const { + + return _debug_error; +} + +int VisualScriptLanguage::debug_get_stack_level_count() const { + + if (_debug_parse_err_node>=0) + return 1; + + + return _debug_call_stack_pos; +} +int VisualScriptLanguage::debug_get_stack_level_line(int p_level) const { + + if (_debug_parse_err_node>=0) + return _debug_parse_err_node; + + ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,-1); + + int l = _debug_call_stack_pos - p_level -1; + + return *(_call_stack[l].current_id); + +} +String VisualScriptLanguage::debug_get_stack_level_function(int p_level) const { + + if (_debug_parse_err_node>=0) + return ""; + + ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,""); + int l = _debug_call_stack_pos - p_level -1; + return *_call_stack[l].function; +} +String VisualScriptLanguage::debug_get_stack_level_source(int p_level) const { + + if (_debug_parse_err_node>=0) + return _debug_parse_err_file; + + ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,""); + int l = _debug_call_stack_pos - p_level -1; + return _call_stack[l].instance->get_script_ptr()->get_path(); + +} +void VisualScriptLanguage::debug_get_stack_level_locals(int p_level,List<String> *p_locals, List<Variant> *p_values, int p_max_subitems,int p_max_depth) { + + if (_debug_parse_err_node>=0) + return; + + ERR_FAIL_INDEX(p_level,_debug_call_stack_pos); + + int l = _debug_call_stack_pos - p_level -1; + const StringName *f = _call_stack[l].function; + + ERR_FAIL_COND(!_call_stack[l].instance->functions.has(*f)); +// VisualScriptInstance::Function *func = &_call_stack[l].instance->functions[*f]; + + VisualScriptNodeInstance *node =_call_stack[l].instance->instances[*_call_stack[l].current_id]; + ERR_FAIL_COND(!node); + + p_locals->push_back("node_name"); + p_values->push_back(node->get_base_node()->get_text()); + + for(int i=0;i<node->input_port_count;i++) { + String name = node->get_base_node()->get_input_value_port_info(i).name; + if (name==String()) { + name="in_"+itos(i); + } + + p_locals->push_back("input/"+name); + + //value is trickier + + int in_from = node->input_ports[i]; + int in_value = in_from&VisualScriptNodeInstance::INPUT_MASK; + + if (in_from&VisualScriptNodeInstance::INPUT_DEFAULT_VALUE_BIT) { + p_values->push_back(_call_stack[l].instance->default_values[in_value]); + } else { + p_values->push_back( _call_stack[l].stack[ in_value] ); + } + } + + for(int i=0;i<node->output_port_count;i++) { + + String name = node->get_base_node()->get_output_value_port_info(i).name; + if (name==String()) { + name="out_"+itos(i); + } + + p_locals->push_back("output/"+name); + + //value is trickier + + int in_from = node->output_ports[i]; + p_values->push_back( _call_stack[l].stack[ in_from] ); + + } + + for(int i=0;i<node->get_working_memory_size();i++) { + p_locals->push_back("working_mem/mem_"+itos(i)); + p_values->push_back( (*_call_stack[l].work_mem)[i]); + } + +/* + ERR_FAIL_INDEX(p_level,_debug_call_stack_pos); + + + VisualFunction *f = _call_stack[l].function; + + List<Pair<StringName,int> > locals; + + f->debug_get_stack_member_state(*_call_stack[l].line,&locals); + for( List<Pair<StringName,int> >::Element *E = locals.front();E;E=E->next() ) { + + p_locals->push_back(E->get().first); + p_values->push_back(_call_stack[l].stack[E->get().second]); + } +*/ +} +void VisualScriptLanguage::debug_get_stack_level_members(int p_level,List<String> *p_members, List<Variant> *p_values, int p_max_subitems,int p_max_depth) { + + if (_debug_parse_err_node>=0) + return; + + ERR_FAIL_INDEX(p_level,_debug_call_stack_pos); + int l = _debug_call_stack_pos - p_level -1; + + + Ref<VisualScript> vs = _call_stack[l].instance->get_script(); + if (vs.is_null()) + return; + + List<StringName> vars; + vs->get_variable_list(&vars); + for (List<StringName>::Element *E=vars.front();E;E=E->next()) { + Variant v; + if (_call_stack[l].instance->get_variable(E->get(),&v)) { + p_members->push_back("variables/"+E->get()); + p_values->push_back(v); + } + } +} + +void VisualScriptLanguage::debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems,int p_max_depth) { + + //no globals are really reachable in gdscript +} +String VisualScriptLanguage::debug_parse_stack_level_expression(int p_level,const String& p_expression,int p_max_subitems,int p_max_depth) { + + if (_debug_parse_err_node>=0) + return ""; + return ""; +} + + + +void VisualScriptLanguage::reload_all_scripts() { + + +} +void VisualScriptLanguage::reload_tool_script(const Ref<Script>& p_script,bool p_soft_reload) { + + +} +/* LOADER FUNCTIONS */ + +void VisualScriptLanguage::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("vs"); + +} +void VisualScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const { + + +} +void VisualScriptLanguage::get_public_constants(List<Pair<String,Variant> > *p_constants) const { + + +} + +void VisualScriptLanguage::profiling_start() { + + +} +void VisualScriptLanguage::profiling_stop() { + + +} + +int VisualScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr,int p_info_max) { + + return 0; +} + +int VisualScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr,int p_info_max) { + + return 0; +} + + +VisualScriptLanguage* VisualScriptLanguage::singleton=NULL; + + +void VisualScriptLanguage::add_register_func(const String& p_name,VisualScriptNodeRegisterFunc p_func) { + + ERR_FAIL_COND(register_funcs.has(p_name)); + register_funcs[p_name]=p_func; +} + +Ref<VisualScriptNode> VisualScriptLanguage::create_node_from_name(const String& p_name) { + + ERR_FAIL_COND_V(!register_funcs.has(p_name),Ref<VisualScriptNode>()); + + return register_funcs[p_name](p_name); +} + +void VisualScriptLanguage::get_registered_node_names(List<String> *r_names) { + + for (Map<String,VisualScriptNodeRegisterFunc>::Element *E=register_funcs.front();E;E=E->next()) { + r_names->push_back(E->key()); + } +} + + +VisualScriptLanguage::VisualScriptLanguage() { + + notification="_notification"; + _step="_step"; + _subcall="_subcall"; + singleton=this; +#ifndef NO_THREADS + lock = Mutex::create(); +#endif + + + _debug_parse_err_node=-1; + _debug_parse_err_file=""; + _debug_call_stack_pos=0; + int dmcs=GLOBAL_DEF("debug/script_max_call_stack",1024); + if (ScriptDebugger::get_singleton()) { + //debugging enabled! + _debug_max_call_stack = dmcs; + if (_debug_max_call_stack<1024) + _debug_max_call_stack=1024; + _call_stack = memnew_arr( CallLevel, _debug_max_call_stack+1 ); + + } else { + _debug_max_call_stack=0; + _call_stack=NULL; + } + +} + +VisualScriptLanguage::~VisualScriptLanguage() { + + if (lock) + memdelete(lock); + + if (_call_stack) { + memdelete_arr(_call_stack); + } + singleton=NULL; +} diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h new file mode 100644 index 0000000000..63b018b0c2 --- /dev/null +++ b/modules/visual_script/visual_script.h @@ -0,0 +1,618 @@ +#ifndef VSCRIPT_H +#define VSCRIPT_H + +#include "script_language.h" +#include "os/thread.h" + +class VisualScriptInstance; +class VisualScriptNodeInstance; +class VisualScript; + +class VisualScriptNode : public Resource { + OBJ_TYPE(VisualScriptNode,Resource) + +friend class VisualScript; + + Set<VisualScript*> scripts_used; + + Array default_input_values; + bool breakpoint; + + void _set_default_input_values(Array p_values); + Array _get_default_input_values() const; + + void validate_input_default_values(); +protected: + + virtual bool _use_builtin_script() const { return false; } + + void _notification(int p_what); + void ports_changed_notify(); + static void _bind_methods(); +public: + + Ref<VisualScript> get_visual_script() const; + + virtual int get_output_sequence_port_count() const=0; + virtual bool has_input_sequence_port() const=0; + + virtual String get_output_sequence_port_text(int p_port) const=0; + + virtual bool has_mixed_input_and_sequence_ports() const { return false; } + + virtual int get_input_value_port_count() const=0; + virtual int get_output_value_port_count() const=0; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const=0; + virtual PropertyInfo get_output_value_port_info(int p_idx) const=0; + + void set_default_input_value(int p_port,const Variant& p_value); + Variant get_default_input_value(int p_port) const; + + virtual String get_caption() const=0; + virtual String get_text() const=0; + virtual String get_category() const=0; + + //used by editor, this is not really saved + void set_breakpoint(bool p_breakpoint); + bool is_breakpoint() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance)=0; + + struct TypeGuess { + + Variant::Type type; + InputEvent::Type ev_type; + StringName obj_type; + Ref<Script> script; + + TypeGuess() { type=Variant::NIL; ev_type=InputEvent::NONE; } + }; + + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + VisualScriptNode(); +}; + + +class VisualScriptNodeInstance { +friend class VisualScriptInstance; +friend class VisualScriptLanguage; //for debugger + + + enum { //input argument addressing + INPUT_SHIFT=1<<24, + INPUT_MASK=INPUT_SHIFT-1, + INPUT_DEFAULT_VALUE_BIT=INPUT_SHIFT, // from unassigned input port, using default value (edited by user) + }; + + + int id; + int sequence_index; + VisualScriptNodeInstance **sequence_outputs; + int sequence_output_count; + Vector<VisualScriptNodeInstance*> dependencies; + int *input_ports; + int input_port_count; + int *output_ports; + int output_port_count; + int working_mem_idx; + int pass_idx; + + VisualScriptNode *base; + +public: + + enum StartMode { + START_MODE_BEGIN_SEQUENCE, + START_MODE_CONTINUE_SEQUENCE, + START_MODE_RESUME_YIELD + }; + + enum { + STEP_SHIFT=1<<24, + STEP_MASK=STEP_SHIFT-1, + STEP_FLAG_PUSH_STACK_BIT=STEP_SHIFT, //push bit to stack + STEP_FLAG_GO_BACK_BIT=STEP_SHIFT<<1, //go back to previous node + STEP_NO_ADVANCE_BIT=STEP_SHIFT<<2, //do not advance past this node + STEP_EXIT_FUNCTION_BIT=STEP_SHIFT<<3, //return from function + STEP_YIELD_BIT=STEP_SHIFT<<4, //yield (will find VisualScriptFunctionState state in first working memory) + + FLOW_STACK_PUSHED_BIT=1<<30, //in flow stack, means bit was pushed (must go back here if end of sequence) + FLOW_STACK_MASK=FLOW_STACK_PUSHED_BIT-1 + + }; + + _FORCE_INLINE_ int get_input_port_count() const { return input_port_count; } + _FORCE_INLINE_ int get_output_port_count() const { return output_port_count; } + _FORCE_INLINE_ int get_sequence_output_count() const { return sequence_output_count; } + + _FORCE_INLINE_ int get_id() const { return id; } + + virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str)=0; //do a step, return which sequence port to go out + + Ref<VisualScriptNode> get_base_node() { return Ref<VisualScriptNode>( base ); } + + VisualScriptNodeInstance(); + virtual ~VisualScriptNodeInstance(); +}; + + +class VisualScript : public Script { + + OBJ_TYPE( VisualScript, Script ) + + RES_BASE_EXTENSION("vs"); + +public: + + struct SequenceConnection { + + union { + + struct { + uint64_t from_node : 24; + uint64_t from_output : 16; + uint64_t to_node : 24; + }; + uint64_t id; + }; + + bool operator<(const SequenceConnection& p_connection) const { + + return id<p_connection.id; + } + }; + + struct DataConnection { + + union { + + struct { + uint64_t from_node : 24; + uint64_t from_port : 8; + uint64_t to_node : 24; + uint64_t to_port : 8; + }; + uint64_t id; + }; + + bool operator<(const DataConnection& p_connection) const { + + return id<p_connection.id; + } + }; + + +private: +friend class VisualScriptInstance; + + StringName base_type; + struct Argument { + String name; + Variant::Type type; + }; + + struct Function { + struct NodeData { + Point2 pos; + Ref<VisualScriptNode> node; + }; + + Map<int,NodeData> nodes; + + Set<SequenceConnection> sequence_connections; + + Set<DataConnection> data_connections; + + int function_id; + + Vector2 scroll; + + + Function() { function_id=-1; } + + }; + + struct Variable { + PropertyInfo info; + Variant default_value; + bool _export; + }; + + + + Map<StringName,Function> functions; + Map<StringName,Variable> variables; + Map<StringName,Vector<Argument> > custom_signals; + + Map<Object*,VisualScriptInstance*> instances; + +#ifdef TOOLS_ENABLED + Set<PlaceHolderScriptInstance*> placeholders; + //void _update_placeholder(PlaceHolderScriptInstance *p_placeholder); + virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder); + void _update_placeholders(); +#endif + + void _set_variable_info(const StringName& p_name,const Dictionary& p_info); + Dictionary _get_variable_info(const StringName& p_name) const; + + + void _set_data(const Dictionary& p_data); + Dictionary _get_data() const; + +protected: + + void _node_ports_changed(int p_id); + static void _bind_methods(); +public: + + + void add_function(const StringName& p_name); + bool has_function(const StringName& p_name) const; + void remove_function(const StringName& p_name); + void rename_function(const StringName& p_name,const StringName& p_new_name); + void set_function_scroll(const StringName& p_name, const Vector2& p_scroll); + Vector2 get_function_scroll(const StringName& p_name) const; + void get_function_list(List<StringName> *r_functions) const; + int get_function_node_id(const StringName& p_name) const; + + + void add_node(const StringName& p_func,int p_id,const Ref<VisualScriptNode>& p_node,const Point2& p_pos=Point2()); + void remove_node(const StringName& p_func,int p_id); + bool has_node(const StringName& p_func,int p_id) const; + Ref<VisualScriptNode> get_node(const StringName& p_func,int p_id) const; + void set_node_pos(const StringName& p_func,int p_id,const Point2& p_pos); + Point2 get_node_pos(const StringName& p_func,int p_id) const; + void get_node_list(const StringName& p_func,List<int> *r_nodes) const; + + void sequence_connect(const StringName& p_func,int p_from_node,int p_from_output,int p_to_node); + void sequence_disconnect(const StringName& p_func,int p_from_node,int p_from_output,int p_to_node); + bool has_sequence_connection(const StringName& p_func,int p_from_node,int p_from_output,int p_to_node) const; + void get_sequence_connection_list(const StringName& p_func,List<SequenceConnection> *r_connection) const; + + void data_connect(const StringName& p_func,int p_from_node,int p_from_port,int p_to_node,int p_to_port); + void data_disconnect(const StringName& p_func,int p_from_node,int p_from_port,int p_to_node,int p_to_port); + bool has_data_connection(const StringName& p_func,int p_from_node,int p_from_port,int p_to_node,int p_to_port) const; + void get_data_connection_list(const StringName& p_func,List<DataConnection> *r_connection) const; + bool is_input_value_port_connected(const StringName& p_name,int p_node,int p_port) const; + bool get_input_value_port_connection_source(const StringName& p_name,int p_node,int p_port,int *r_node,int *r_port) const; + + void add_variable(const StringName& p_name,const Variant& p_default_value=Variant(),bool p_export=false); + bool has_variable(const StringName& p_name) const; + void remove_variable(const StringName& p_name); + void set_variable_default_value(const StringName& p_name,const Variant& p_value); + Variant get_variable_default_value(const StringName& p_name) const; + void set_variable_info(const StringName& p_name,const PropertyInfo& p_info); + PropertyInfo get_variable_info(const StringName& p_name) const; + void set_variable_export(const StringName& p_name,bool p_export); + bool get_variable_export(const StringName& p_name) const; + void get_variable_list(List<StringName> *r_variables) const; + void rename_variable(const StringName& p_name,const StringName& p_new_name); + + + void add_custom_signal(const StringName& p_name); + bool has_custom_signal(const StringName& p_name) const; + void custom_signal_add_argument(const StringName& p_func,Variant::Type p_type,const String& p_name,int p_index=-1); + void custom_signal_set_argument_type(const StringName& p_func,int p_argidx,Variant::Type p_type); + Variant::Type custom_signal_get_argument_type(const StringName& p_func,int p_argidx) const; + void custom_signal_set_argument_name(const StringName& p_func,int p_argidx,const String& p_name); + String custom_signal_get_argument_name(const StringName& p_func,int p_argidx) const; + void custom_signal_remove_argument(const StringName& p_func,int p_argidx); + int custom_signal_get_argument_count(const StringName& p_func) const; + void custom_signal_swap_argument(const StringName& p_func,int p_argidx,int p_with_argidx); + void remove_custom_signal(const StringName& p_name); + void rename_custom_signal(const StringName& p_name,const StringName& p_new_name); + + void get_custom_signal_list(List<StringName> *r_custom_signals) const; + + int get_available_id() const; + + void set_instance_base_type(const StringName& p_type); + + virtual bool can_instance() const; + + virtual Ref<Script> get_base_script() const; + virtual StringName get_instance_base_type() const; + virtual ScriptInstance* instance_create(Object *p_this); + virtual bool instance_has(const Object *p_this) const; + + + virtual bool has_source_code() const; + virtual String get_source_code() const; + virtual void set_source_code(const String& p_code); + virtual Error reload(bool p_keep_state=false); + + virtual bool is_tool() const; + + virtual String get_node_type() const; + + virtual ScriptLanguage *get_language() const; + + virtual bool has_script_signal(const StringName& p_signal) const; + virtual void get_script_signal_list(List<MethodInfo> *r_signals) const; + + virtual bool get_property_default_value(const StringName& p_property,Variant& r_value) const; + virtual void get_script_method_list(List<MethodInfo> *p_list) const; + + virtual bool has_method(const StringName& p_method) const; + virtual MethodInfo get_method_info(const StringName& p_method) const; + + virtual void get_script_property_list(List<PropertyInfo> *p_list) const; + +#ifdef TOOLS_ENABLED + virtual bool are_subnodes_edited() const; +#endif + + VisualScript(); + ~VisualScript(); +}; + + +class VisualScriptInstance : public ScriptInstance { + Object *owner; + Ref<VisualScript> script; + + Map<StringName,Variant> variables; //using variable path, not script + Map<int,VisualScriptNodeInstance*> instances; + + struct Function { + int node; + int max_stack; + int trash_pos; + int return_pos; + int flow_stack_size; + int pass_stack_size; + int node_count; + int argument_count; + bool valid; + }; + + Map<StringName,Function> functions; + + Vector<Variant> default_values; + int max_input_args,max_output_args; + + StringName source; + + void _dependency_step(VisualScriptNodeInstance* node, int p_pass, int *pass_stack, const Variant **input_args, Variant **output_args, Variant *variant_stack, Variant::CallError& r_error, String& error_str, VisualScriptNodeInstance **r_error_node); + Variant _call_internal(const StringName& p_method, void* p_stack,int p_stack_size, VisualScriptNodeInstance* p_node, int p_flow_stack_pos, int p_pass, bool p_resuming_yield,Variant::CallError &r_error); + + + //Map<StringName,Function> functions; +friend class VisualScriptFunctionState; //for yield +friend class VisualScriptLanguage; //for debugger +public: + virtual bool set(const StringName& p_name, const Variant& p_value); + virtual bool get(const StringName& p_name, Variant &r_ret) const; + virtual void get_property_list(List<PropertyInfo> *p_properties) const; + virtual Variant::Type get_property_type(const StringName& p_name,bool *r_is_valid=NULL) const; + + virtual void get_method_list(List<MethodInfo> *p_list) const; + virtual bool has_method(const StringName& p_method) const; + virtual Variant call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error); + virtual void notification(int p_notification); + + bool set_variable(const StringName& p_variable,const Variant& p_value) { + + Map<StringName,Variant>::Element *E=variables.find(p_variable); + if (!E) + return false; + + E->get()=p_value; + return true; + } + + bool get_variable(const StringName& p_variable,Variant* r_variable) const { + + const Map<StringName,Variant>::Element *E=variables.find(p_variable); + if (!E) + return false; + + *r_variable=E->get(); + return true; + + } + + virtual Ref<Script> get_script() const; + + _FORCE_INLINE_ VisualScript *get_script_ptr() { return script.ptr(); } + _FORCE_INLINE_ Object *get_owner_ptr() { return owner; } + + void create(const Ref<VisualScript>& p_script,Object *p_owner); + + virtual ScriptLanguage *get_language(); + + virtual RPCMode get_rpc_mode(const StringName& p_method) const; + virtual RPCMode get_rset_mode(const StringName& p_variable) const; + + VisualScriptInstance(); + ~VisualScriptInstance(); +}; + + +class VisualScriptFunctionState : public Reference { + + OBJ_TYPE(VisualScriptFunctionState,Reference); +friend class VisualScriptInstance; + + ObjectID instance_id; + ObjectID script_id; + VisualScriptInstance *instance; + StringName function; + Vector<uint8_t> stack; + int working_mem_index; + int variant_stack_size; + VisualScriptNodeInstance *node; + int flow_stack_pos; + int pass; + + Variant _signal_callback(const Variant** p_args, int p_argcount, Variant::CallError& r_error); +protected: + static void _bind_methods(); +public: + + void connect_to_signal(Object* p_obj,const String& p_signal,Array p_binds); + bool is_valid() const; + Variant resume(Array p_args); + VisualScriptFunctionState(); + ~VisualScriptFunctionState(); +}; + + +typedef Ref<VisualScriptNode> (*VisualScriptNodeRegisterFunc)(const String& p_type); + +class VisualScriptLanguage : public ScriptLanguage { + + Map<String,VisualScriptNodeRegisterFunc> register_funcs; + + struct CallLevel { + + Variant *stack; + Variant **work_mem; + const StringName *function; + VisualScriptInstance *instance; + int *current_id; + + }; + + + int _debug_parse_err_node; + String _debug_parse_err_file; + String _debug_error; + int _debug_call_stack_pos; + int _debug_max_call_stack; + CallLevel *_call_stack; + +public: + StringName notification; + StringName _get_output_port_unsequenced; + StringName _step; + StringName _subcall; + + static VisualScriptLanguage* singleton; + + Mutex *lock; + + bool debug_break(const String& p_error,bool p_allow_continue=true); + bool debug_break_parse(const String& p_file, int p_node,const String& p_error); + + _FORCE_INLINE_ void enter_function(VisualScriptInstance *p_instance,const StringName* p_function, Variant *p_stack, Variant **p_work_mem,int *current_id) { + + if (Thread::get_main_ID()!=Thread::get_caller_ID()) + return; //no support for other threads than main for now + + if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) + ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() +1 ); + + if (_debug_call_stack_pos >= _debug_max_call_stack) { + //stack overflow + _debug_error="Stack Overflow (Stack Size: "+itos(_debug_max_call_stack)+")"; + ScriptDebugger::get_singleton()->debug(this); + return; + } + + _call_stack[_debug_call_stack_pos].stack=p_stack; + _call_stack[_debug_call_stack_pos].instance=p_instance; + _call_stack[_debug_call_stack_pos].function=p_function; + _call_stack[_debug_call_stack_pos].work_mem=p_work_mem; + _call_stack[_debug_call_stack_pos].current_id=current_id; + _debug_call_stack_pos++; + } + + _FORCE_INLINE_ void exit_function() { + + if (Thread::get_main_ID()!=Thread::get_caller_ID()) + return; //no support for other threads than main for now + + if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) + ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() -1 ); + + if (_debug_call_stack_pos==0) { + + _debug_error="Stack Underflow (Engine Bug)"; + ScriptDebugger::get_singleton()->debug(this); + return; + } + + _debug_call_stack_pos--; + } + + ////////////////////////////////////// + + virtual String get_name() const; + + /* LANGUAGE FUNCTIONS */ + virtual void init(); + virtual String get_type() const; + virtual String get_extension() const; + virtual Error execute_file(const String& p_path) ; + virtual void finish(); + + /* EDITOR FUNCTIONS */ + virtual void get_reserved_words(List<String> *p_words) const; + virtual void get_comment_delimiters(List<String> *p_delimiters) const; + virtual void get_string_delimiters(List<String> *p_delimiters) const; + virtual Ref<Script> get_template(const String& p_class_name, const String& p_base_class_name) const; + virtual bool validate(const String& p_script, int &r_line_error,int &r_col_error,String& r_test_error, const String& p_path="",List<String> *r_functions=NULL) const; + virtual Script *create_script() const; + virtual bool has_named_classes() const; + virtual int find_function(const String& p_function,const String& p_code) const; + virtual String make_function(const String& p_class,const String& p_name,const StringArray& p_args) const; + virtual void auto_indent_code(String& p_code,int p_from_line,int p_to_line) const; + virtual void add_global_constant(const StringName& p_variable,const Variant& p_value); + + + /* DEBUGGER FUNCTIONS */ + + virtual String debug_get_error() const; + virtual int debug_get_stack_level_count() const; + virtual int debug_get_stack_level_line(int p_level) const; + virtual String debug_get_stack_level_function(int p_level) const; + virtual String debug_get_stack_level_source(int p_level) const; + virtual void debug_get_stack_level_locals(int p_level,List<String> *p_locals, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1); + virtual void debug_get_stack_level_members(int p_level,List<String> *p_members, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1); + virtual void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1); + virtual String debug_parse_stack_level_expression(int p_level,const String& p_expression,int p_max_subitems=-1,int p_max_depth=-1); + + + virtual void reload_all_scripts(); + virtual void reload_tool_script(const Ref<Script>& p_script,bool p_soft_reload); + /* LOADER FUNCTIONS */ + + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual void get_public_functions(List<MethodInfo> *p_functions) const; + virtual void get_public_constants(List<Pair<String,Variant> > *p_constants) const; + + virtual void profiling_start(); + virtual void profiling_stop(); + + virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr,int p_info_max); + virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr,int p_info_max); + + void add_register_func(const String& p_name,VisualScriptNodeRegisterFunc p_func); + Ref<VisualScriptNode> create_node_from_name(const String& p_name); + void get_registered_node_names(List<String> *r_names); + + + VisualScriptLanguage(); + ~VisualScriptLanguage(); + +}; + +//aid for registering +template<class T> +static Ref<VisualScriptNode> create_node_generic(const String& p_name) { + + Ref<T> node; + node.instance(); + return node; +} + + + +#endif // VSCRIPT_H diff --git a/modules/visual_script/visual_script_builtin_funcs.cpp b/modules/visual_script/visual_script_builtin_funcs.cpp new file mode 100644 index 0000000000..1d0bca0b30 --- /dev/null +++ b/modules/visual_script/visual_script_builtin_funcs.cpp @@ -0,0 +1,1197 @@ +#include "visual_script_builtin_funcs.h" +#include "math_funcs.h" +#include "object_type_db.h" +#include "reference.h" +#include "func_ref.h" +#include "os/os.h" +#include "variant_parser.h" +#include "io/marshalls.h" + +const char* VisualScriptBuiltinFunc::func_name[VisualScriptBuiltinFunc::FUNC_MAX]={ + "sin", + "cos", + "tan", + "sinh", + "cosh", + "tanh", + "asin", + "acos", + "atan", + "atan2", + "sqrt", + "fmod", + "fposmod", + "floor", + "ceil", + "round", + "abs", + "sign", + "pow", + "log", + "exp", + "is_nan", + "is_inf", + "ease", + "decimals", + "stepify", + "lerp", + "dectime", + "randomize", + "randi", + "randf", + "rand_range", + "seed", + "rand_seed", + "deg2rad", + "rad2deg", + "linear2db", + "db2linear", + "max", + "min", + "clamp", + "nearest_po2", + "weakref", + "funcref", + "convert", + "typeof", + "type_exists", + "str", + "print", + "printerr", + "printraw", + "var2str", + "str2var", + "var2bytes", + "bytes2var", +}; + + + +int VisualScriptBuiltinFunc::get_output_sequence_port_count() const { + + return has_input_sequence_port() ? 1 : 0; +} + +bool VisualScriptBuiltinFunc::has_input_sequence_port() const{ + + switch(func) { + + case MATH_RANDOMIZE: + case TEXT_PRINT: + case TEXT_PRINTERR: + case TEXT_PRINTRAW: + return true; + default: + return false; + + } + +} + +int VisualScriptBuiltinFunc::get_input_value_port_count() const{ + + + switch(func) { + + case MATH_RANDOMIZE: + case MATH_RAND: + case MATH_RANDF: + return 0; + case MATH_SIN: + case MATH_COS: + case MATH_TAN: + case MATH_SINH: + case MATH_COSH: + case MATH_TANH: + case MATH_ASIN: + case MATH_ACOS: + case MATH_ATAN: + case MATH_SQRT: + case MATH_FLOOR: + case MATH_CEIL: + case MATH_ROUND: + case MATH_ABS: + case MATH_SIGN: + case MATH_LOG: + case MATH_EXP: + case MATH_ISNAN: + case MATH_ISINF: + case MATH_DECIMALS: + case MATH_SEED: + case MATH_RANDSEED: + case MATH_DEG2RAD: + case MATH_RAD2DEG: + case MATH_LINEAR2DB: + case MATH_DB2LINEAR: + case LOGIC_NEAREST_PO2: + case OBJ_WEAKREF: + case TYPE_OF: + case TEXT_STR: + case TEXT_PRINT: + case TEXT_PRINTERR: + case TEXT_PRINTRAW: + case VAR_TO_STR: + case STR_TO_VAR: + case VAR_TO_BYTES: + case BYTES_TO_VAR: + case TYPE_EXISTS: + return 1; + case MATH_ATAN2: + case MATH_FMOD: + case MATH_FPOSMOD: + case MATH_POW: + case MATH_EASE: + case MATH_STEPIFY: + case MATH_RANDOM: + case LOGIC_MAX: + case LOGIC_MIN: + case FUNC_FUNCREF: + case TYPE_CONVERT: + return 2; + case MATH_LERP: + case MATH_DECTIME: + case LOGIC_CLAMP: + return 3; + case FUNC_MAX:{} + + } + return 0; +} +int VisualScriptBuiltinFunc::get_output_value_port_count() const{ + + switch(func) { + case MATH_RANDOMIZE: + case TEXT_PRINT: + case TEXT_PRINTERR: + case TEXT_PRINTRAW: + case MATH_SEED: + return 0; + case MATH_RANDSEED: + return 2; + default: + return 1; + } + + return 1; +} + +String VisualScriptBuiltinFunc::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const{ + + switch(func) { + + case MATH_SIN: + case MATH_COS: + case MATH_TAN: + case MATH_SINH: + case MATH_COSH: + case MATH_TANH: + case MATH_ASIN: + case MATH_ACOS: + case MATH_ATAN: + case MATH_ATAN2: + case MATH_SQRT: { + return PropertyInfo(Variant::REAL,"num"); + } break; + case MATH_FMOD: + case MATH_FPOSMOD: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"x"); + else + return PropertyInfo(Variant::REAL,"y"); + } break; + case MATH_FLOOR: + case MATH_CEIL: + case MATH_ROUND: + case MATH_ABS: + case MATH_SIGN: { + return PropertyInfo(Variant::REAL,"num"); + + } break; + + case MATH_POW: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"x"); + else + return PropertyInfo(Variant::REAL,"y"); + } break; + case MATH_LOG: + case MATH_EXP: + case MATH_ISNAN: + case MATH_ISINF: { + return PropertyInfo(Variant::REAL,"num"); + } break; + case MATH_EASE: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"s"); + else + return PropertyInfo(Variant::REAL,"curve"); + } break; + case MATH_DECIMALS: { + return PropertyInfo(Variant::REAL,"step"); + } break; + case MATH_STEPIFY: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"s"); + else + return PropertyInfo(Variant::REAL,"steps"); + } break; + case MATH_LERP: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"from"); + else if (p_idx==1) + return PropertyInfo(Variant::REAL,"to"); + else + return PropertyInfo(Variant::REAL,"weight"); + + } break; + case MATH_DECTIME: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"value"); + else if (p_idx==1) + return PropertyInfo(Variant::REAL,"amount"); + else + return PropertyInfo(Variant::REAL,"step"); + } break; + case MATH_RANDOMIZE: { + + } break; + case MATH_RAND: { + + } break; + case MATH_RANDF: { + + } break; + case MATH_RANDOM: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"from"); + else + return PropertyInfo(Variant::REAL,"to"); + } break; + case MATH_SEED: { + return PropertyInfo(Variant::INT,"seed"); + } break; + case MATH_RANDSEED: { + return PropertyInfo(Variant::INT,"seed"); + } break; + case MATH_DEG2RAD: { + return PropertyInfo(Variant::REAL,"deg"); + } break; + case MATH_RAD2DEG: { + return PropertyInfo(Variant::REAL,"rad"); + } break; + case MATH_LINEAR2DB: { + return PropertyInfo(Variant::REAL,"nrg"); + } break; + case MATH_DB2LINEAR: { + return PropertyInfo(Variant::REAL,"db"); + } break; + case LOGIC_MAX: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"a"); + else + return PropertyInfo(Variant::REAL,"b"); + } break; + case LOGIC_MIN: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"a"); + else + return PropertyInfo(Variant::REAL,"b"); + } break; + case LOGIC_CLAMP: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"a"); + else if (p_idx==0) + return PropertyInfo(Variant::REAL,"min"); + else + return PropertyInfo(Variant::REAL,"max"); + } break; + case LOGIC_NEAREST_PO2: { + return PropertyInfo(Variant::INT,"num"); + } break; + case OBJ_WEAKREF: { + + return PropertyInfo(Variant::OBJECT,"source"); + + } break; + case FUNC_FUNCREF: { + + if (p_idx==0) + return PropertyInfo(Variant::OBJECT,"instance"); + else + return PropertyInfo(Variant::STRING,"funcname"); + + } break; + case TYPE_CONVERT: { + + if (p_idx==0) + return PropertyInfo(Variant::NIL,"what"); + else + return PropertyInfo(Variant::STRING,"type"); + } break; + case TYPE_OF: { + return PropertyInfo(Variant::NIL,"what"); + + } break; + case TYPE_EXISTS: { + + return PropertyInfo(Variant::STRING,"type"); + + } break; + case TEXT_STR: { + + return PropertyInfo(Variant::NIL,"value"); + + + } break; + case TEXT_PRINT: { + + return PropertyInfo(Variant::NIL,"value"); + + } break; + case TEXT_PRINTERR: { + return PropertyInfo(Variant::NIL,"value"); + + } break; + case TEXT_PRINTRAW: { + + return PropertyInfo(Variant::NIL,"value"); + + } break; + case VAR_TO_STR: { + return PropertyInfo(Variant::NIL,"var"); + + } break; + case STR_TO_VAR: { + + return PropertyInfo(Variant::STRING,"string"); + } break; + case VAR_TO_BYTES: { + return PropertyInfo(Variant::NIL,"var"); + + } break; + case BYTES_TO_VAR: { + + return PropertyInfo(Variant::RAW_ARRAY,"bytes"); + } break; + case FUNC_MAX:{} + } + + return PropertyInfo(); +} + +PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) const{ + + Variant::Type t=Variant::NIL; + switch(func) { + + case MATH_SIN: + case MATH_COS: + case MATH_TAN: + case MATH_SINH: + case MATH_COSH: + case MATH_TANH: + case MATH_ASIN: + case MATH_ACOS: + case MATH_ATAN: + case MATH_ATAN2: + case MATH_SQRT: + case MATH_FMOD: + case MATH_FPOSMOD: + case MATH_FLOOR: + case MATH_CEIL: { + t=Variant::REAL; + } break; + case MATH_ROUND: { + t=Variant::INT; + } break; + case MATH_ABS: { + t=Variant::NIL; + } break; + case MATH_SIGN: { + t=Variant::NIL; + } break; + case MATH_POW: + case MATH_LOG: + case MATH_EXP: { + t=Variant::REAL; + } break; + case MATH_ISNAN: + case MATH_ISINF: { + t=Variant::BOOL; + } break; + case MATH_EASE: { + t=Variant::REAL; + } break; + case MATH_DECIMALS: { + t=Variant::INT; + } break; + case MATH_STEPIFY: + case MATH_LERP: + case MATH_DECTIME: { + t=Variant::REAL; + + } break; + case MATH_RANDOMIZE: { + + } break; + case MATH_RAND: { + + t=Variant::INT; + } break; + case MATH_RANDF: + case MATH_RANDOM: { + t=Variant::REAL; + } break; + case MATH_SEED: { + + } break; + case MATH_RANDSEED: { + + if (p_idx==0) + return PropertyInfo(Variant::INT,"rnd"); + else + return PropertyInfo(Variant::INT,"seed"); + } break; + case MATH_DEG2RAD: + case MATH_RAD2DEG: + case MATH_LINEAR2DB: + case MATH_DB2LINEAR: { + t=Variant::REAL; + } break; + case LOGIC_MAX: + case LOGIC_MIN: + case LOGIC_CLAMP: { + + + } break; + + case LOGIC_NEAREST_PO2: { + t=Variant::NIL; + } break; + case OBJ_WEAKREF: { + + t=Variant::OBJECT; + + } break; + case FUNC_FUNCREF: { + + t=Variant::OBJECT; + + } break; + case TYPE_CONVERT: { + + + + } break; + case TYPE_OF: { + t=Variant::INT; + + } break; + case TYPE_EXISTS: { + + t=Variant::BOOL; + + } break; + case TEXT_STR: { + + t=Variant::STRING; + + } break; + case TEXT_PRINT: { + + + } break; + case TEXT_PRINTERR: { + + } break; + case TEXT_PRINTRAW: { + + } break; + case VAR_TO_STR: { + t=Variant::STRING; + } break; + case STR_TO_VAR: { + + } break; + case VAR_TO_BYTES: { + t=Variant::RAW_ARRAY; + + } break; + case BYTES_TO_VAR: { + + + } break; + case FUNC_MAX:{} + } + + return PropertyInfo(t,""); +} + +String VisualScriptBuiltinFunc::get_caption() const { + + return "BuiltinFunc"; +} + +String VisualScriptBuiltinFunc::get_text() const { + + return func_name[func]; +} + +void VisualScriptBuiltinFunc::set_func(BuiltinFunc p_which) { + + ERR_FAIL_INDEX(p_which,FUNC_MAX); + func=p_which; + _change_notify(); + ports_changed_notify(); +} + +VisualScriptBuiltinFunc::BuiltinFunc VisualScriptBuiltinFunc::get_func() { + return func; +} + + +#define VALIDATE_ARG_NUM(m_arg) \ + if (!p_inputs[m_arg]->is_num()) {\ + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;\ + r_error.argument=m_arg;\ + r_error.expected=Variant::REAL;\ + return 0;\ + } + +class VisualScriptNodeInstanceBuiltinFunc : public VisualScriptNodeInstance { +public: + + VisualScriptBuiltinFunc *node; + VisualScriptInstance *instance; + + VisualScriptBuiltinFunc::BuiltinFunc func; + + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + switch(func) { + case VisualScriptBuiltinFunc::MATH_SIN: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::sin(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_COS: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::cos(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_TAN: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::tan(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_SINH: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::sinh(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_COSH: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::cosh(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_TANH: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::tanh(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ASIN: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::asin(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ACOS: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::acos(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ATAN: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::atan(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ATAN2: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *p_outputs[0]=Math::atan2(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_SQRT: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::sqrt(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_FMOD: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *p_outputs[0]=Math::fmod(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_FPOSMOD: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *p_outputs[0]=Math::fposmod(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_FLOOR: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::floor(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_CEIL: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::ceil(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ROUND: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::round(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ABS: { + + if (p_inputs[0]->get_type()==Variant::INT) { + + int64_t i = *p_inputs[0]; + *p_outputs[0]=ABS(i); + } else if (p_inputs[0]->get_type()==Variant::REAL) { + + real_t r = *p_inputs[0]; + *p_outputs[0]=Math::abs(r); + } else { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::REAL; + + } + } break; + case VisualScriptBuiltinFunc::MATH_SIGN: { + + if (p_inputs[0]->get_type()==Variant::INT) { + + int64_t i = *p_inputs[0]; + *p_outputs[0]= i < 0 ? -1 : ( i > 0 ? +1 : 0); + } else if (p_inputs[0]->get_type()==Variant::REAL) { + + real_t r = *p_inputs[0]; + *p_outputs[0]= r < 0.0 ? -1.0 : ( r > 0.0 ? +1.0 : 0.0); + } else { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::REAL; + + } + } break; + case VisualScriptBuiltinFunc::MATH_POW: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *p_outputs[0]=Math::pow(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_LOG: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::log(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_EXP: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::exp(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ISNAN: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::is_nan(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ISINF: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::is_inf(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_EASE: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *p_outputs[0]=Math::ease(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_DECIMALS: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::step_decimals(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_STEPIFY: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *p_outputs[0]=Math::stepify(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_LERP: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + *p_outputs[0]=Math::lerp(*p_inputs[0],*p_inputs[1],*p_inputs[2]); + } break; + case VisualScriptBuiltinFunc::MATH_DECTIME: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + *p_outputs[0]=Math::dectime(*p_inputs[0],*p_inputs[1],*p_inputs[2]); + } break; + case VisualScriptBuiltinFunc::MATH_RANDOMIZE: { + Math::randomize(); + + } break; + case VisualScriptBuiltinFunc::MATH_RAND: { + *p_outputs[0]=Math::rand(); + } break; + case VisualScriptBuiltinFunc::MATH_RANDF: { + *p_outputs[0]=Math::randf(); + } break; + case VisualScriptBuiltinFunc::MATH_RANDOM: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *p_outputs[0]=Math::random(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_SEED: { + + VALIDATE_ARG_NUM(0); + uint32_t seed=*p_inputs[0]; + Math::seed(seed); + + } break; + case VisualScriptBuiltinFunc::MATH_RANDSEED: { + + VALIDATE_ARG_NUM(0); + uint32_t seed=*p_inputs[0]; + int ret = Math::rand_from_seed(&seed); + Array reta; + reta.push_back(ret); + reta.push_back(seed); + *p_outputs[0]=reta; + + } break; + case VisualScriptBuiltinFunc::MATH_DEG2RAD: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::deg2rad(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_RAD2DEG: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::rad2deg(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_LINEAR2DB: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::linear2db(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_DB2LINEAR: { + + VALIDATE_ARG_NUM(0); + *p_outputs[0]=Math::db2linear(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::LOGIC_MAX: { + + if (p_inputs[0]->get_type()==Variant::INT && p_inputs[1]->get_type()==Variant::INT) { + + int64_t a = *p_inputs[0]; + int64_t b = *p_inputs[1]; + *p_outputs[0]=MAX(a,b); + } else { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + + real_t a = *p_inputs[0]; + real_t b = *p_inputs[1]; + + *p_outputs[0]=MAX(a,b); + } + + } break; + case VisualScriptBuiltinFunc::LOGIC_MIN: { + + if (p_inputs[0]->get_type()==Variant::INT && p_inputs[1]->get_type()==Variant::INT) { + + int64_t a = *p_inputs[0]; + int64_t b = *p_inputs[1]; + *p_outputs[0]=MIN(a,b); + } else { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + + real_t a = *p_inputs[0]; + real_t b = *p_inputs[1]; + + *p_outputs[0]=MIN(a,b); + } + } break; + case VisualScriptBuiltinFunc::LOGIC_CLAMP: { + + if (p_inputs[0]->get_type()==Variant::INT && p_inputs[1]->get_type()==Variant::INT && p_inputs[2]->get_type()==Variant::INT) { + + int64_t a = *p_inputs[0]; + int64_t b = *p_inputs[1]; + int64_t c = *p_inputs[2]; + *p_outputs[0]=CLAMP(a,b,c); + } else { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + + real_t a = *p_inputs[0]; + real_t b = *p_inputs[1]; + real_t c = *p_inputs[2]; + + *p_outputs[0]=CLAMP(a,b,c); + } + } break; + case VisualScriptBuiltinFunc::LOGIC_NEAREST_PO2: { + + VALIDATE_ARG_NUM(0); + int64_t num = *p_inputs[0]; + *p_outputs[0] = nearest_power_of_2(num); + } break; + case VisualScriptBuiltinFunc::OBJ_WEAKREF: { + + if (p_inputs[0]->get_type()!=Variant::OBJECT) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::OBJECT; + + return 0; + + } + + if (p_inputs[0]->is_ref()) { + + REF r = *p_inputs[0]; + if (!r.is_valid()) { + + return 0; + } + + Ref<WeakRef> wref = memnew( WeakRef ); + wref->set_ref(r); + *p_outputs[0]=wref; + } else { + Object *obj = *p_inputs[0]; + if (!obj) { + + return 0; + } + Ref<WeakRef> wref = memnew( WeakRef ); + wref->set_obj(obj); + *p_outputs[0]=wref; + } + + + + + } break; + case VisualScriptBuiltinFunc::FUNC_FUNCREF: { + + if (p_inputs[0]->get_type()!=Variant::OBJECT) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::OBJECT; + + return 0; + + } + if (p_inputs[1]->get_type()!=Variant::STRING && p_inputs[1]->get_type()!=Variant::NODE_PATH) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=1; + r_error.expected=Variant::STRING; + + return 0; + + } + + Ref<FuncRef> fr = memnew( FuncRef); + + fr->set_instance(*p_inputs[0]); + fr->set_function(*p_inputs[1]); + + *p_outputs[0]=fr; + + } break; + case VisualScriptBuiltinFunc::TYPE_CONVERT: { + + VALIDATE_ARG_NUM(1); + int type=*p_inputs[1]; + if (type<0 || type>=Variant::VARIANT_MAX) { + + *p_outputs[0]=RTR("Invalid type argument to convert(), use TYPE_* constants."); + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::INT; + return 0; + + } else { + + + *p_outputs[0]=Variant::construct(Variant::Type(type),p_inputs,1,r_error); + } + } break; + case VisualScriptBuiltinFunc::TYPE_OF: { + + + *p_outputs[0] = p_inputs[0]->get_type(); + + } break; + case VisualScriptBuiltinFunc::TYPE_EXISTS: { + + + *p_outputs[0] = ObjectTypeDB::type_exists(*p_inputs[0]); + + } break; + case VisualScriptBuiltinFunc::TEXT_STR: { + + String str = *p_inputs[0]; + + *p_outputs[0]=str; + + } break; + case VisualScriptBuiltinFunc::TEXT_PRINT: { + + String str = *p_inputs[0]; + print_line(str); + + + } break; + + case VisualScriptBuiltinFunc::TEXT_PRINTERR: { + + String str = *p_inputs[0]; + + //str+="\n"; + OS::get_singleton()->printerr("%s\n",str.utf8().get_data()); + + + } break; + case VisualScriptBuiltinFunc::TEXT_PRINTRAW: { + String str = *p_inputs[0]; + + //str+="\n"; + OS::get_singleton()->print("%s",str.utf8().get_data()); + + + } break; + case VisualScriptBuiltinFunc::VAR_TO_STR: { + + String vars; + VariantWriter::write_to_string(*p_inputs[0],vars); + *p_outputs[0]=vars; + } break; + case VisualScriptBuiltinFunc::STR_TO_VAR: { + + if (p_inputs[0]->get_type()!=Variant::STRING) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::STRING; + + return 0; + } + + VariantParser::StreamString ss; + ss.s=*p_inputs[0]; + + String errs; + int line; + Error err = VariantParser::parse(&ss,*p_outputs[0],errs,line); + + if (err!=OK) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::STRING; + *p_outputs[0]="Parse error at line "+itos(line)+": "+errs; + return 0; + } + + } break; + case VisualScriptBuiltinFunc::VAR_TO_BYTES: { + + + ByteArray barr; + int len; + Error err = encode_variant(*p_inputs[0],NULL,len); + if (err) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::NIL; + *p_outputs[0]="Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."; + return 0; + } + + barr.resize(len); + { + ByteArray::Write w = barr.write(); + encode_variant(*p_inputs[0],w.ptr(),len); + + } + *p_outputs[0]=barr; + } break; + case VisualScriptBuiltinFunc::BYTES_TO_VAR: { + + if (p_inputs[0]->get_type()!=Variant::RAW_ARRAY) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::RAW_ARRAY; + + return 0; + } + + ByteArray varr=*p_inputs[0]; + Variant ret; + { + ByteArray::Read r=varr.read(); + Error err = decode_variant(ret,r.ptr(),varr.size(),NULL); + if (err!=OK) { + *p_outputs[0]=RTR("Not enough bytes for decoding bytes, or invalid format."); + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::RAW_ARRAY; + return 0; + } + + } + + *p_outputs[0]=ret; + + } break; + default: {} + } + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptBuiltinFunc::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceBuiltinFunc * instance = memnew(VisualScriptNodeInstanceBuiltinFunc ); + instance->node=this; + instance->instance=p_instance; + instance->func=func; + return instance; +} + + +void VisualScriptBuiltinFunc::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_func","which"),&VisualScriptBuiltinFunc::set_func); + ObjectTypeDB::bind_method(_MD("get_func"),&VisualScriptBuiltinFunc::get_func); + + String cc; + + for(int i=0;i<FUNC_MAX;i++) { + + if (i>0) + cc+=","; + cc+=func_name[i]; + } + ADD_PROPERTY(PropertyInfo(Variant::INT,"function",PROPERTY_HINT_ENUM,cc),_SCS("set_func"),_SCS("get_func")); +} + +VisualScriptBuiltinFunc::VisualScriptBuiltinFunc() { + + func=MATH_SIN; +} + +template<VisualScriptBuiltinFunc::BuiltinFunc func> +static Ref<VisualScriptNode> create_builtin_func_node(const String& p_name) { + + Ref<VisualScriptBuiltinFunc> node; + node.instance(); + node->set_func(func); + return node; +} + +void register_visual_script_builtin_func_node() { + + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/sin",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_SIN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/cos",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_COS>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/tan",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_TAN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/sinh",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_SINH>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/cosh",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_COSH>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/tanh",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_TANH>); + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/asin",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ASIN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/acos",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ACOS>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/atan",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ATAN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/atan2",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ATAN2>); + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/sqrt",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_SQRT>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/fmod",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_FMOD>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/fposmod",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_FPOSMOD>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/floor",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_FLOOR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/ceil",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_CEIL>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/round",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ROUND>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/abs",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ABS>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/sign",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_SIGN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/pow",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_POW>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/log",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_LOG>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/exp",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_EXP>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/isnan",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ISNAN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/isinf",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ISINF>); + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/ease",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_EASE>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/decimals",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_DECIMALS>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/stepify",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_STEPIFY>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/lerp",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_LERP>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/dectime",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_DECTIME>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/randomize",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RANDOMIZE>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/rand",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RAND>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/randf",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RANDF>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/random",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RANDOM>); + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/seed",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_SEED>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/randseed",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RANDSEED>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/deg2rad",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_DEG2RAD>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/rad2deg",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RAD2DEG>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/linear2db",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_LINEAR2DB>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/db2linear",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_DB2LINEAR>); + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/max",create_builtin_func_node<VisualScriptBuiltinFunc::LOGIC_MAX>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/min",create_builtin_func_node<VisualScriptBuiltinFunc::LOGIC_MIN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/clamp",create_builtin_func_node<VisualScriptBuiltinFunc::LOGIC_CLAMP>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/nearest_po2",create_builtin_func_node<VisualScriptBuiltinFunc::LOGIC_NEAREST_PO2>); + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/weakref",create_builtin_func_node<VisualScriptBuiltinFunc::OBJ_WEAKREF>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/funcref",create_builtin_func_node<VisualScriptBuiltinFunc::FUNC_FUNCREF>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/convert",create_builtin_func_node<VisualScriptBuiltinFunc::TYPE_CONVERT>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/typeof",create_builtin_func_node<VisualScriptBuiltinFunc::TYPE_OF>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/type_exists",create_builtin_func_node<VisualScriptBuiltinFunc::TYPE_EXISTS>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/str",create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_STR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/print",create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_PRINT>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/printerr",create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_PRINTERR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/printraw",create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_PRINTRAW>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/var2str",create_builtin_func_node<VisualScriptBuiltinFunc::VAR_TO_STR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/str2var",create_builtin_func_node<VisualScriptBuiltinFunc::STR_TO_VAR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/var2bytes",create_builtin_func_node<VisualScriptBuiltinFunc::VAR_TO_BYTES>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/bytes2var",create_builtin_func_node<VisualScriptBuiltinFunc::BYTES_TO_VAR>); + +} diff --git a/modules/visual_script/visual_script_builtin_funcs.h b/modules/visual_script/visual_script_builtin_funcs.h new file mode 100644 index 0000000000..ebf227a192 --- /dev/null +++ b/modules/visual_script/visual_script_builtin_funcs.h @@ -0,0 +1,110 @@ +#ifndef VISUAL_SCRIPT_BUILTIN_FUNCS_H +#define VISUAL_SCRIPT_BUILTIN_FUNCS_H + +#include "visual_script.h" + + +class VisualScriptBuiltinFunc : public VisualScriptNode { + + OBJ_TYPE(VisualScriptBuiltinFunc,VisualScriptNode) +public: + + enum BuiltinFunc { + MATH_SIN, + MATH_COS, + MATH_TAN, + MATH_SINH, + MATH_COSH, + MATH_TANH, + MATH_ASIN, + MATH_ACOS, + MATH_ATAN, + MATH_ATAN2, + MATH_SQRT, + MATH_FMOD, + MATH_FPOSMOD, + MATH_FLOOR, + MATH_CEIL, + MATH_ROUND, + MATH_ABS, + MATH_SIGN, + MATH_POW, + MATH_LOG, + MATH_EXP, + MATH_ISNAN, + MATH_ISINF, + MATH_EASE, + MATH_DECIMALS, + MATH_STEPIFY, + MATH_LERP, + MATH_DECTIME, + MATH_RANDOMIZE, + MATH_RAND, + MATH_RANDF, + MATH_RANDOM, + MATH_SEED, + MATH_RANDSEED, + MATH_DEG2RAD, + MATH_RAD2DEG, + MATH_LINEAR2DB, + MATH_DB2LINEAR, + LOGIC_MAX, + LOGIC_MIN, + LOGIC_CLAMP, + LOGIC_NEAREST_PO2, + OBJ_WEAKREF, + FUNC_FUNCREF, + TYPE_CONVERT, + TYPE_OF, + TYPE_EXISTS, + TEXT_STR, + TEXT_PRINT, + TEXT_PRINTERR, + TEXT_PRINTRAW, + VAR_TO_STR, + STR_TO_VAR, + VAR_TO_BYTES, + BYTES_TO_VAR, + FUNC_MAX + }; + +private: + static const char* func_name[FUNC_MAX]; + BuiltinFunc func; +protected: + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_func(BuiltinFunc p_which); + BuiltinFunc get_func(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptBuiltinFunc(); +}; + +VARIANT_ENUM_CAST(VisualScriptBuiltinFunc::BuiltinFunc) + + +void register_visual_script_builtin_func_node(); + + +#endif // VISUAL_SCRIPT_BUILTIN_FUNCS_H diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp new file mode 100644 index 0000000000..14708799af --- /dev/null +++ b/modules/visual_script/visual_script_editor.cpp @@ -0,0 +1,3495 @@ +#include "visual_script_editor.h" +#include "tools/editor/editor_node.h" +#include "visual_script_nodes.h" +#include "visual_script_flow_control.h" +#include "visual_script_func_nodes.h" +#include "visual_script_expression.h" +#include "os/input.h" +#include "tools/editor/editor_resource_preview.h" +#include "os/keyboard.h" + +#ifdef TOOLS_ENABLED +class VisualScriptEditorSignalEdit : public Object { + + OBJ_TYPE(VisualScriptEditorSignalEdit,Object) + + StringName sig; +public: + UndoRedo *undo_redo; + Ref<VisualScript> script; + + +protected: + + static void _bind_methods() { + ObjectTypeDB::bind_method("_sig_changed",&VisualScriptEditorSignalEdit::_sig_changed); + } + + void _sig_changed() { + + _change_notify(); + } + + bool _set(const StringName& p_name, const Variant& p_value) { + + if (sig==StringName()) + return false; + + if (p_name=="argument_count") { + + int new_argc=p_value; + int argc = script->custom_signal_get_argument_count(sig); + if (argc==new_argc) + return true; + + undo_redo->create_action("Change Signal Arguments"); + + + + if (new_argc < argc) { + for(int i=new_argc;i<argc;i++) { + undo_redo->add_do_method(script.ptr(),"custom_signal_remove_argument",sig,new_argc); + undo_redo->add_undo_method(script.ptr(),"custom_signal_add_argument",sig,script->custom_signal_get_argument_name(sig,i),script->custom_signal_get_argument_type(sig,i),-1); + } + } else if (new_argc>argc) { + + for(int i=argc;i<new_argc;i++) { + + undo_redo->add_do_method(script.ptr(),"custom_signal_add_argument",sig,Variant::NIL,"arg"+itos(i+1),-1); + undo_redo->add_undo_method(script.ptr(),"custom_signal_remove_argument",sig,argc); + } + } + + undo_redo->add_do_method(this,"_sig_changed"); + undo_redo->add_undo_method(this,"_sig_changed"); + + undo_redo->commit_action(); + + return true; + } + if (String(p_name).begins_with("argument/")) { + int idx = String(p_name).get_slice("/",1).to_int()-1; + ERR_FAIL_INDEX_V(idx,script->custom_signal_get_argument_count(sig),false); + String what = String(p_name).get_slice("/",2); + if (what=="type") { + + int old_type = script->custom_signal_get_argument_type(sig,idx); + int new_type=p_value; + undo_redo->create_action("Change Argument Type"); + undo_redo->add_do_method(script.ptr(),"custom_signal_set_argument_type",sig,idx,new_type); + undo_redo->add_undo_method(script.ptr(),"custom_signal_set_argument_type",sig,idx,old_type); + undo_redo->commit_action(); + + return true; + } + + if (what=="name") { + + String old_name = script->custom_signal_get_argument_name(sig,idx); + String new_name=p_value; + undo_redo->create_action("Change Argument name"); + undo_redo->add_do_method(script.ptr(),"custom_signal_set_argument_name",sig,idx,new_name); + undo_redo->add_undo_method(script.ptr(),"custom_signal_set_argument_name",sig,idx,old_name); + undo_redo->commit_action(); + return true; + } + + + } + + + return false; + } + + bool _get(const StringName& p_name,Variant &r_ret) const { + + if (sig==StringName()) + return false; + + if (p_name=="argument_count") { + r_ret = script->custom_signal_get_argument_count(sig); + return true; + } + if (String(p_name).begins_with("argument/")) { + int idx = String(p_name).get_slice("/",1).to_int()-1; + ERR_FAIL_INDEX_V(idx,script->custom_signal_get_argument_count(sig),false); + String what = String(p_name).get_slice("/",2); + if (what=="type") { + r_ret = script->custom_signal_get_argument_type(sig,idx); + return true; + } + if (what=="name") { + r_ret = script->custom_signal_get_argument_name(sig,idx); + return true; + } + + + + } + + return false; + } + void _get_property_list( List<PropertyInfo> *p_list) const { + + if (sig==StringName()) + return; + + p_list->push_back(PropertyInfo(Variant::INT,"argument_count",PROPERTY_HINT_RANGE,"0,256")); + String argt="Variant"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + for(int i=0;i<script->custom_signal_get_argument_count(sig);i++) { + p_list->push_back(PropertyInfo(Variant::INT,"argument/"+itos(i+1)+"/type",PROPERTY_HINT_ENUM,argt)); + p_list->push_back(PropertyInfo(Variant::STRING,"argument/"+itos(i+1)+"/name")); + } + } + +public: + + + void edit(const StringName& p_sig) { + + sig=p_sig; + _change_notify(); + } + + VisualScriptEditorSignalEdit() { undo_redo=NULL; } +}; + +class VisualScriptEditorVariableEdit : public Object { + + OBJ_TYPE(VisualScriptEditorVariableEdit,Object) + + StringName var; +public: + UndoRedo *undo_redo; + Ref<VisualScript> script; + + +protected: + + static void _bind_methods() { + ObjectTypeDB::bind_method("_var_changed",&VisualScriptEditorVariableEdit::_var_changed); + ObjectTypeDB::bind_method("_var_value_changed",&VisualScriptEditorVariableEdit::_var_value_changed); + } + + void _var_changed() { + + _change_notify(); + } + void _var_value_changed() { + + _change_notify("value"); //so the whole tree is not redrawn, makes editing smoother in general + } + + bool _set(const StringName& p_name, const Variant& p_value) { + + if (var==StringName()) + return false; + + + + if (String(p_name)=="value") { + undo_redo->create_action("Set Variable Default Value"); + Variant current=script->get_variable_default_value(var); + undo_redo->add_do_method(script.ptr(),"set_variable_default_value",var,p_value); + undo_redo->add_undo_method(script.ptr(),"set_variable_default_value",var,current); + undo_redo->add_do_method(this,"_var_value_changed"); + undo_redo->add_undo_method(this,"_var_value_changed"); + undo_redo->commit_action(); + return true; + } + + Dictionary d = script->call("get_variable_info",var); + + if (String(p_name)=="type") { + + Dictionary dc=d.copy(); + dc["type"]=p_value; + undo_redo->create_action("Set Variable Type"); + undo_redo->add_do_method(script.ptr(),"set_variable_info",var,dc); + undo_redo->add_undo_method(script.ptr(),"set_variable_info",var,d); + undo_redo->add_do_method(this,"_var_changed"); + undo_redo->add_undo_method(this,"_var_changed"); + undo_redo->commit_action(); + return true; + } + + if (String(p_name)=="hint") { + + Dictionary dc=d.copy(); + dc["hint"]=p_value; + undo_redo->create_action("Set Variable Type"); + undo_redo->add_do_method(script.ptr(),"set_variable_info",var,dc); + undo_redo->add_undo_method(script.ptr(),"set_variable_info",var,d); + undo_redo->add_do_method(this,"_var_changed"); + undo_redo->add_undo_method(this,"_var_changed"); + undo_redo->commit_action(); + return true; + } + + if (String(p_name)=="hint_string") { + + Dictionary dc=d.copy(); + dc["hint_string"]=p_value; + undo_redo->create_action("Set Variable Type"); + undo_redo->add_do_method(script.ptr(),"set_variable_info",var,dc); + undo_redo->add_undo_method(script.ptr(),"set_variable_info",var,d); + undo_redo->add_do_method(this,"_var_changed"); + undo_redo->add_undo_method(this,"_var_changed"); + undo_redo->commit_action(); + return true; + } + + if (String(p_name)=="export") { + script->set_variable_export(var,p_value); + return true; + } + + + return false; + } + + bool _get(const StringName& p_name,Variant &r_ret) const { + + if (var==StringName()) + return false; + + if (String(p_name)=="value") { + r_ret=script->get_variable_default_value(var); + return true; + } + + PropertyInfo pinfo = script->get_variable_info(var); + + if (String(p_name)=="type") { + r_ret=pinfo.type; + return true; + } + if (String(p_name)=="hint") { + r_ret=pinfo.hint; + return true; + } + if (String(p_name)=="hint_string") { + r_ret=pinfo.hint_string; + return true; + } + + if (String(p_name)=="export") { + r_ret=script->get_variable_export(var); + return true; + } + + return false; + } + void _get_property_list( List<PropertyInfo> *p_list) const { + + if (var==StringName()) + return; + + String argt="Variant"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + p_list->push_back(PropertyInfo(Variant::INT,"type",PROPERTY_HINT_ENUM,argt)); + p_list->push_back(PropertyInfo(script->get_variable_info(var).type,"value",script->get_variable_info(var).hint,script->get_variable_info(var).hint_string,PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::INT,"hint",PROPERTY_HINT_ENUM,"None,Range,ExpRange,Enum,ExpEasing,Length,SpriteFrame,KeyAccel,BitFlags,AllFlags,File,Dir,GlobalFile,GlobalDir,ResourceType,MultilineText")); + p_list->push_back(PropertyInfo(Variant::STRING,"hint_string")); + p_list->push_back(PropertyInfo(Variant::BOOL,"export")); + + } + +public: + + + void edit(const StringName& p_var) { + + var=p_var; + _change_notify(); + } + + VisualScriptEditorVariableEdit() { undo_redo=NULL; } +}; + +static Color _color_from_type(Variant::Type p_type) { + Color color; + switch(p_type) { + case Variant::NIL: color = Color::html("69ecbd"); break; + + case Variant::BOOL: color = Color::html("8da6f0"); break; + case Variant::INT: color = Color::html("7dc6ef"); break; + case Variant::REAL: color = Color::html("61daf4"); break; + case Variant::STRING: color = Color::html("6ba7ec"); break; + + case Variant::VECTOR2: color = Color::html("bd91f1"); break; + case Variant::RECT2: color = Color::html("f191a5"); break; + case Variant::VECTOR3: color = Color::html("d67dee"); break; + case Variant::MATRIX32: color = Color::html("c4ec69"); break; + case Variant::PLANE: color = Color::html("f77070"); break; + case Variant::QUAT: color = Color::html("ec69a3"); break; + case Variant::_AABB: color = Color::html("ee7991"); break; + case Variant::MATRIX3: color = Color::html("e3ec69"); break; + case Variant::TRANSFORM: color = Color::html("ecd669"); break; + + case Variant::COLOR: color = Color::html("9dff70"); break; + case Variant::IMAGE: color = Color::html("93f1b9"); break; + case Variant::NODE_PATH: color = Color::html("6993ec"); break; + case Variant::_RID: color = Color::html("69ec9a"); break; + case Variant::OBJECT: color = Color::html("79f3e8"); break; + case Variant::INPUT_EVENT: color = Color::html("adf18f"); break; + case Variant::DICTIONARY: color = Color::html("77edb1"); break; + + case Variant::ARRAY: color = Color::html("e0e0e0"); break; + case Variant::RAW_ARRAY: color = Color::html("aaf4c8"); break; + case Variant::INT_ARRAY: color = Color::html("afdcf5"); break; + case Variant::REAL_ARRAY: color = Color::html("97e7f8"); break; + case Variant::STRING_ARRAY: color = Color::html("9dc4f2"); break; + case Variant::VECTOR2_ARRAY: color = Color::html("d1b3f5"); break; + case Variant::VECTOR3_ARRAY: color = Color::html("df9bf2"); break; + case Variant::COLOR_ARRAY: color = Color::html("e9ff97"); break; + + default: + color.set_hsv(p_type/float(Variant::VARIANT_MAX),0.7,0.7); + } + return color; +} + + + +void VisualScriptEditor::_update_graph_connections() { + + graph->clear_connections(); + + List<VisualScript::SequenceConnection> sequence_conns; + script->get_sequence_connection_list(edited_func,&sequence_conns); + + + for (List<VisualScript::SequenceConnection>::Element *E=sequence_conns.front();E;E=E->next()) { + + graph->connect_node(itos(E->get().from_node),E->get().from_output,itos(E->get().to_node),0); + } + + List<VisualScript::DataConnection> data_conns; + script->get_data_connection_list(edited_func,&data_conns); + + for (List<VisualScript::DataConnection>::Element *E=data_conns.front();E;E=E->next()) { + + VisualScript::DataConnection dc=E->get(); + + + Ref<VisualScriptNode> from_node = script->get_node(edited_func,E->get().from_node); + Ref<VisualScriptNode> to_node = script->get_node(edited_func,E->get().to_node); + + if (to_node->has_input_sequence_port()) { + dc.to_port++; + } + + dc.from_port+=from_node->get_output_sequence_port_count(); + + graph->connect_node(itos(E->get().from_node),dc.from_port,itos(E->get().to_node),dc.to_port); + } + +} + + +void VisualScriptEditor::_update_graph(int p_only_id) { + + if (updating_graph) + return; + + updating_graph=true; + + //byebye all nodes + if (p_only_id>=0) { + if (graph->has_node(itos(p_only_id))) { + Node* gid = graph->get_node(itos(p_only_id)); + if (gid) + memdelete(gid); + } + } else { + + for(int i=0;i<graph->get_child_count();i++) { + + if (graph->get_child(i)->cast_to<GraphNode>()) { + memdelete(graph->get_child(i)); + i--; + } + } + } + + if (!script->has_function(edited_func)) { + graph->hide(); + select_func_text->show(); + updating_graph=false; + return; + } + + graph->show(); + select_func_text->hide(); + + Ref<Texture> type_icons[Variant::VARIANT_MAX]={ + Control::get_icon("MiniVariant","EditorIcons"), + Control::get_icon("MiniBoolean","EditorIcons"), + Control::get_icon("MiniInteger","EditorIcons"), + Control::get_icon("MiniFloat","EditorIcons"), + Control::get_icon("MiniString","EditorIcons"), + Control::get_icon("MiniVector2","EditorIcons"), + Control::get_icon("MiniRect2","EditorIcons"), + Control::get_icon("MiniVector3","EditorIcons"), + Control::get_icon("MiniMatrix32","EditorIcons"), + Control::get_icon("MiniPlane","EditorIcons"), + Control::get_icon("MiniQuat","EditorIcons"), + Control::get_icon("MiniAabb","EditorIcons"), + Control::get_icon("MiniMatrix3","EditorIcons"), + Control::get_icon("MiniTransform","EditorIcons"), + Control::get_icon("MiniColor","EditorIcons"), + Control::get_icon("MiniImage","EditorIcons"), + Control::get_icon("MiniPath","EditorIcons"), + Control::get_icon("MiniRid","EditorIcons"), + Control::get_icon("MiniObject","EditorIcons"), + Control::get_icon("MiniInput","EditorIcons"), + Control::get_icon("MiniDictionary","EditorIcons"), + Control::get_icon("MiniArray","EditorIcons"), + Control::get_icon("MiniRawArray","EditorIcons"), + Control::get_icon("MiniIntArray","EditorIcons"), + Control::get_icon("MiniFloatArray","EditorIcons"), + Control::get_icon("MiniStringArray","EditorIcons"), + Control::get_icon("MiniVector2Array","EditorIcons"), + Control::get_icon("MiniVector3Array","EditorIcons"), + Control::get_icon("MiniColorArray","EditorIcons") + }; + + + + Ref<Texture> seq_port = Control::get_icon("VisualShaderPort","EditorIcons"); + + List<int> ids; + script->get_node_list(edited_func,&ids); + StringName editor_icons="EditorIcons"; + + for(List<int>::Element *E=ids.front();E;E=E->next()) { + + if (p_only_id>=0 && p_only_id!=E->get()) + continue; + + Ref<VisualScriptNode> node = script->get_node(edited_func,E->get()); + Vector2 pos = script->get_node_pos(edited_func,E->get()); + + GraphNode *gnode = memnew( GraphNode ); + gnode->set_title(node->get_caption()); + if (error_line==E->get()) { + gnode->set_overlay(GraphNode::OVERLAY_POSITION); + } else if (node->is_breakpoint()) { + gnode->set_overlay(GraphNode::OVERLAY_BREAKPOINT); + } + + if (EditorSettings::get_singleton()->has("visual_script_editor/color_"+node->get_category())) { + gnode->set_modulate(EditorSettings::get_singleton()->get("visual_script_editor/color_"+node->get_category())); + } + + + + gnode->set_meta("__vnode",node); + gnode->set_name(itos(E->get())); + gnode->connect("dragged",this,"_node_moved",varray(E->get())); + gnode->connect("close_request",this,"_remove_node",varray(E->get()),CONNECT_DEFERRED); + + + if (E->get()!=script->get_function_node_id(edited_func)) { + //function can't be erased + gnode->set_show_close_button(true); + } + + + Label *text = memnew( Label ); + text->set_text(node->get_text()); + gnode->add_child(text); + if (node->cast_to<VisualScriptExpression>()) { + text->add_font_override("font",get_font("source","EditorFonts")); + } + + if (node->cast_to<VisualScriptComment>()) { + Ref<VisualScriptComment> vsc=node; + gnode->set_comment(true); + gnode->set_resizeable(true); + gnode->set_custom_minimum_size(vsc->get_size()*EDSCALE); + gnode->connect("resize_request",this,"_comment_node_resized",varray(E->get())); + + } + + + int slot_idx=0; + + bool single_seq_output = node->get_output_sequence_port_count()==1 && node->get_output_sequence_port_text(0)==String(); + gnode->set_slot(0,node->has_input_sequence_port(),TYPE_SEQUENCE,Color(1,1,1,1),single_seq_output,TYPE_SEQUENCE,Color(1,1,1,1),seq_port,seq_port); + gnode->set_offset(pos*EDSCALE); + slot_idx++; + + + int mixed_seq_ports=0; + + if (!single_seq_output) { + + if (node->has_mixed_input_and_sequence_ports()) { + mixed_seq_ports=node->get_output_sequence_port_count(); + } else { + for(int i=0;i<node->get_output_sequence_port_count();i++) { + + Label *text2 = memnew( Label ); + text2->set_text(node->get_output_sequence_port_text(i)); + text2->set_align(Label::ALIGN_RIGHT); + gnode->add_child(text2); + gnode->set_slot(slot_idx,false,0,Color(),true,TYPE_SEQUENCE,Color(1,1,1,1),seq_port,seq_port); + slot_idx++; + } + } + } + + for(int i=0;i<MAX(node->get_output_value_port_count(),MAX(mixed_seq_ports,node->get_input_value_port_count()));i++) { + + bool left_ok=false; + Variant::Type left_type=Variant::NIL; + String left_name; + + + + if (i<node->get_input_value_port_count()) { + PropertyInfo pi = node->get_input_value_port_info(i); + left_ok=true; + left_type=pi.type; + left_name=pi.name; + } + + bool right_ok=false; + Variant::Type right_type=Variant::NIL; + String right_name; + + if (i>=mixed_seq_ports && i<node->get_output_value_port_count()+mixed_seq_ports) { + PropertyInfo pi = node->get_output_value_port_info(i-mixed_seq_ports); + right_ok=true; + right_type=pi.type; + right_name=pi.name; + } + + HBoxContainer *hbc = memnew( HBoxContainer); + + if (left_ok) { + + Ref<Texture> t; + if (left_type>=0 && left_type<Variant::VARIANT_MAX) { + t=type_icons[left_type]; + } + if (t.is_valid()) { + TextureFrame *tf = memnew(TextureFrame); + tf->set_texture(t); + tf->set_stretch_mode(TextureFrame::STRETCH_KEEP_CENTERED); + hbc->add_child(tf); + } + + hbc->add_child(memnew(Label(left_name))); + + if (left_type!=Variant::NIL && !script->is_input_value_port_connected(edited_func,E->get(),i)) { + + PropertyInfo pi = node->get_input_value_port_info(i); + Button *button = memnew( Button ); + Variant value = node->get_default_input_value(i); + if (value.get_type()!=left_type) { + //different type? for now convert + //not the same, reconvert + Variant::CallError ce; + const Variant *existingp=&value; + value = Variant::construct(left_type,&existingp,1,ce,false); + } + + if (left_type==Variant::COLOR) { + button->set_custom_minimum_size(Size2(30,0)*EDSCALE); + button->connect("draw",this,"_draw_color_over_button",varray(button,value)); + } else if (left_type==Variant::OBJECT && Ref<Resource>(value).is_valid()) { + + Ref<Resource> res = value; + Array arr; + arr.push_back(button->get_instance_ID()); + arr.push_back(String(value)); + EditorResourcePreview::get_singleton()->queue_edited_resource_preview(res,this,"_button_resource_previewed",arr); + + } else if (pi.type==Variant::INT && pi.hint==PROPERTY_HINT_ENUM){ + + button->set_text(pi.hint_string.get_slice(",",value)); + } else { + + button->set_text(value); + } + button->connect("pressed",this,"_default_value_edited",varray(button,E->get(),i)); + hbc->add_child(button); + } + } else { + Control *c = memnew(Control); + c->set_custom_minimum_size(Size2(10,0)*EDSCALE); + hbc->add_child(c); + } + + hbc->add_spacer(); + + if (i<mixed_seq_ports) { + + Label *text2 = memnew( Label ); + text2->set_text(node->get_output_sequence_port_text(i)); + text2->set_align(Label::ALIGN_RIGHT); + hbc->add_child(text2); + } + + if (right_ok) { + + hbc->add_child(memnew(Label(right_name))); + + Ref<Texture> t; + if (right_type>=0 && right_type<Variant::VARIANT_MAX) { + t=type_icons[right_type]; + } + if (t.is_valid()) { + TextureFrame *tf = memnew(TextureFrame); + tf->set_texture(t); + tf->set_stretch_mode(TextureFrame::STRETCH_KEEP_CENTERED); + hbc->add_child(tf); + } + + } + + gnode->add_child(hbc); + + if (i<mixed_seq_ports) { + gnode->set_slot(slot_idx,left_ok,left_type,_color_from_type(left_type),true,TYPE_SEQUENCE,Color(1,1,1,1),Ref<Texture>(),seq_port); + } else { + gnode->set_slot(slot_idx,left_ok,left_type,_color_from_type(left_type),right_ok,right_type,_color_from_type(right_type)); + } + + slot_idx++; + } + + graph->add_child(gnode); + + if (gnode->is_comment()) { + graph->move_child(gnode,0); + } + } + + _update_graph_connections(); + graph->call_deferred("set_scroll_ofs",script->get_function_scroll(edited_func)*EDSCALE); //may need to adapt a bit, let it do so + updating_graph=false; + +} + +void VisualScriptEditor::_update_members() { + + + updating_members=true; + + members->clear(); + TreeItem *root = members->create_item(); + + TreeItem *functions = members->create_item(root); + functions->set_selectable(0,false); + functions->set_text(0,TTR("Functions:")); + functions->add_button(0,Control::get_icon("Override","EditorIcons"),1); + functions->add_button(0,Control::get_icon("Add","EditorIcons"),0); + functions->set_custom_bg_color(0,Control::get_color("prop_section","Editor")); + + List<StringName> func_names; + script->get_function_list(&func_names); + for (List<StringName>::Element *E=func_names.front();E;E=E->next()) { + TreeItem *ti = members->create_item(functions) ; + ti->set_text(0,E->get()); + ti->set_selectable(0,true); + ti->set_editable(0,true); + //ti->add_button(0,Control::get_icon("Edit","EditorIcons"),0); function arguments are in the node now + ti->add_button(0,Control::get_icon("Del","EditorIcons"),1); + ti->set_metadata(0,E->get()); + if (E->get()==edited_func) { + ti->set_custom_bg_color(0,get_color("prop_category","Editor")); + ti->set_custom_color(0,Color(1,1,1,1)); + } + if (selected==E->get()) + ti->select(0); + } + + TreeItem *variables = members->create_item(root); + variables->set_selectable(0,false); + variables->set_text(0,TTR("Variables:")); + variables->add_button(0,Control::get_icon("Add","EditorIcons")); + variables->set_custom_bg_color(0,Control::get_color("prop_section","Editor")); + + Ref<Texture> type_icons[Variant::VARIANT_MAX]={ + Control::get_icon("MiniVariant","EditorIcons"), + Control::get_icon("MiniBoolean","EditorIcons"), + Control::get_icon("MiniInteger","EditorIcons"), + Control::get_icon("MiniFloat","EditorIcons"), + Control::get_icon("MiniString","EditorIcons"), + Control::get_icon("MiniVector2","EditorIcons"), + Control::get_icon("MiniRect2","EditorIcons"), + Control::get_icon("MiniVector3","EditorIcons"), + Control::get_icon("MiniMatrix32","EditorIcons"), + Control::get_icon("MiniPlane","EditorIcons"), + Control::get_icon("MiniQuat","EditorIcons"), + Control::get_icon("MiniAabb","EditorIcons"), + Control::get_icon("MiniMatrix3","EditorIcons"), + Control::get_icon("MiniTransform","EditorIcons"), + Control::get_icon("MiniColor","EditorIcons"), + Control::get_icon("MiniImage","EditorIcons"), + Control::get_icon("MiniPath","EditorIcons"), + Control::get_icon("MiniRid","EditorIcons"), + Control::get_icon("MiniObject","EditorIcons"), + Control::get_icon("MiniInput","EditorIcons"), + Control::get_icon("MiniDictionary","EditorIcons"), + Control::get_icon("MiniArray","EditorIcons"), + Control::get_icon("MiniRawArray","EditorIcons"), + Control::get_icon("MiniIntArray","EditorIcons"), + Control::get_icon("MiniFloatArray","EditorIcons"), + Control::get_icon("MiniStringArray","EditorIcons"), + Control::get_icon("MiniVector2Array","EditorIcons"), + Control::get_icon("MiniVector3Array","EditorIcons"), + Control::get_icon("MiniColorArray","EditorIcons") + }; + + List<StringName> var_names; + script->get_variable_list(&var_names); + for (List<StringName>::Element *E=var_names.front();E;E=E->next()) { + TreeItem *ti = members->create_item(variables); + + ti->set_text(0,E->get()); + Variant var = script->get_variable_default_value(E->get()); + ti->set_suffix(0,"="+String(var)); + ti->set_icon(0,type_icons[script->get_variable_info(E->get()).type]); + + ti->set_selectable(0,true); + ti->set_editable(0,true); + ti->add_button(0,Control::get_icon("Edit","EditorIcons"),0); + ti->add_button(0,Control::get_icon("Del","EditorIcons"),1); + ti->set_metadata(0,E->get()); + if (selected==E->get()) + ti->select(0); + } + + TreeItem *_signals = members->create_item(root); + _signals->set_selectable(0,false); + _signals->set_text(0,TTR("Signals:")); + _signals->add_button(0,Control::get_icon("Add","EditorIcons")); + _signals->set_custom_bg_color(0,Control::get_color("prop_section","Editor")); + + List<StringName> signal_names; + script->get_custom_signal_list(&signal_names); + for (List<StringName>::Element *E=signal_names.front();E;E=E->next()) { + TreeItem *ti = members->create_item(_signals); + ti->set_text(0,E->get()); + ti->set_selectable(0,true); + ti->set_editable(0,true); + ti->add_button(0,Control::get_icon("Edit","EditorIcons"),0); + ti->add_button(0,Control::get_icon("Del","EditorIcons"),1); + ti->set_metadata(0,E->get()); + if (selected==E->get()) + ti->select(0); + } + + String base_type=script->get_instance_base_type(); + String icon_type=base_type; + if (!Control::has_icon(base_type,"EditorIcons")) { + icon_type="Object"; + } + + base_type_select->set_text(base_type); + base_type_select->set_icon(Control::get_icon(icon_type,"EditorIcons")); + + updating_members=false; + +} + +void VisualScriptEditor::_member_selected() { + + if (updating_members) + return; + + TreeItem *ti=members->get_selected(); + ERR_FAIL_COND(!ti); + + + selected=ti->get_metadata(0); +// print_line("selected: "+String(selected)); + + + if (ti->get_parent()==members->get_root()->get_children()) { + + if (edited_func!=selected) { + + revert_on_drag=edited_func; + edited_func=selected; + _update_members(); + _update_graph(); + } + + return; //or crash because it will become invalid + + } + + + +} + +void VisualScriptEditor::_member_edited() { + + if (updating_members) + return; + + TreeItem *ti=members->get_edited(); + ERR_FAIL_COND(!ti); + + String name = ti->get_metadata(0); + String new_name = ti->get_text(0); + + if (name==new_name) + return; + + if (!new_name.is_valid_identifier()) { + + EditorNode::get_singleton()->show_warning(TTR("Name is not a valid identifier:")+" "+new_name); + updating_members=true; + ti->set_text(0,name); + updating_members=false; + return; + + } + + if (script->has_function(new_name) || script->has_variable(new_name) || script->has_custom_signal(new_name)) { + + EditorNode::get_singleton()->show_warning(TTR("Name already in use by another func/var/signal:")+" "+new_name); + updating_members=true; + ti->set_text(0,name); + updating_members=false; + return; + } + + TreeItem *root=members->get_root(); + + if (ti->get_parent()==root->get_children()) { + + if (edited_func==selected) { + edited_func=new_name; + } + selected=new_name; + + + _update_graph(); + + undo_redo->create_action(TTR("Rename Function")); + undo_redo->add_do_method(script.ptr(),"rename_function",name,new_name); + undo_redo->add_undo_method(script.ptr(),"rename_function",new_name,name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + + return; //or crash because it will become invalid + + } + + if (ti->get_parent()==root->get_children()->get_next()) { + + selected=new_name; + undo_redo->create_action(TTR("Rename Variable")); + undo_redo->add_do_method(script.ptr(),"rename_variable",name,new_name); + undo_redo->add_undo_method(script.ptr(),"rename_variable",new_name,name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + + return; //or crash because it will become invalid + } + + if (ti->get_parent()==root->get_children()->get_next()->get_next()) { + + selected=new_name; + undo_redo->create_action(TTR("Rename Signal")); + undo_redo->add_do_method(script.ptr(),"rename_custom_signal",name,new_name); + undo_redo->add_undo_method(script.ptr(),"rename_custom_signal",new_name,name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + + return; //or crash because it will become invalid + } +} + +void VisualScriptEditor::_override_pressed(int p_id) { + + //override a virtual function or method from base type + + ERR_FAIL_COND(!virtuals_in_menu.has(p_id)); + + VirtualInMenu vim=virtuals_in_menu[p_id]; + + String name = _validate_name(vim.name); + selected=name; + edited_func=selected; + Ref<VisualScriptFunction> func_node; + func_node.instance(); + func_node->set_name(vim.name); + + undo_redo->create_action(TTR("Add Function")); + undo_redo->add_do_method(script.ptr(),"add_function",name); + for(int i=0;i<vim.args.size();i++) { + func_node->add_argument(vim.args[i].first,vim.args[i].second); + } + + + undo_redo->add_do_method(script.ptr(),"add_node",name,script->get_available_id(),func_node); + if (vim.ret!=Variant::NIL || vim.ret_variant) { + Ref<VisualScriptReturn> ret_node; + ret_node.instance(); + ret_node->set_return_type(vim.ret); + ret_node->set_enable_return_value(true); + ret_node->set_name(vim.name); + undo_redo->add_do_method(script.ptr(),"add_node",name,script->get_available_id()+1,ret_node,Vector2(500,0)); + + } + + undo_redo->add_undo_method(script.ptr(),"remove_function",name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + + undo_redo->commit_action(); + + + _update_graph(); +} + +void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_button) { + + TreeItem *ti=p_item->cast_to<TreeItem>(); + + TreeItem *root=members->get_root(); + + if (ti->get_parent()==root) { + //main buttons + if (ti==root->get_children()) { + //add function, this one uses menu + + if (p_button==1) { + new_function_menu->clear(); + new_function_menu->set_size(Size2(0,0)); + int idx=0; + + virtuals_in_menu.clear(); + + List<MethodInfo> mi; + ObjectTypeDB::get_method_list(script->get_instance_base_type(),&mi); + for (List<MethodInfo>::Element *E=mi.front();E;E=E->next()) { + MethodInfo mi=E->get(); + if (mi.flags&METHOD_FLAG_VIRTUAL) { + + VirtualInMenu vim; + vim.name=mi.name; + vim.ret=mi.return_val.type; + if (mi.return_val.name!=String()) + vim.ret_variant=true; + else + vim.ret_variant=false; + + String desc; + + if (mi.return_val.type==Variant::NIL) + desc="var"; + else + desc=Variant::get_type_name(mi.return_val.type); + desc+=" "+mi.name+" ( "; + + + for(int i=0;i<mi.arguments.size();i++) { + + if (i>0) + desc+=", "; + + if (mi.arguments[i].type==Variant::NIL) + desc+="var "; + else + desc+=Variant::get_type_name(mi.arguments[i].type)+" "; + + desc+=mi.arguments[i].name; + + Pair<Variant::Type,String> p; + p.first=mi.arguments[i].type; + p.second=mi.arguments[i].name; + vim.args.push_back( p ); + + } + + desc+=" )"; + + virtuals_in_menu[idx]=vim; + + new_function_menu->add_item(desc,idx); + idx++; + } + } + + Rect2 pos = members->get_item_rect(ti); + new_function_menu->set_pos(members->get_global_pos()+pos.pos+Vector2(0,pos.size.y)); + new_function_menu->popup(); + return; + } else if (p_button==0) { + + + String name = _validate_name("new_function"); + selected=name; + edited_func=selected; + + Ref<VisualScriptFunction> func_node; + func_node.instance(); + func_node->set_name(name); + + undo_redo->create_action(TTR("Add Function")); + undo_redo->add_do_method(script.ptr(),"add_function",name); + undo_redo->add_do_method(script.ptr(),"add_node",name,script->get_available_id(),func_node); + undo_redo->add_undo_method(script.ptr(),"remove_function",name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + + undo_redo->commit_action(); + + _update_graph(); + } + + return; //or crash because it will become invalid + + } + + if (ti==root->get_children()->get_next()) { + //add variable + String name = _validate_name("new_variable"); + selected=name; + + undo_redo->create_action(TTR("Add Variable")); + undo_redo->add_do_method(script.ptr(),"add_variable",name); + undo_redo->add_undo_method(script.ptr(),"remove_variable",name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + return; //or crash because it will become invalid + + } + + if (ti==root->get_children()->get_next()->get_next()) { + //add variable + String name = _validate_name("new_signal"); + selected=name; + + undo_redo->create_action(TTR("Add Signal")); + undo_redo->add_do_method(script.ptr(),"add_custom_signal",name); + undo_redo->add_undo_method(script.ptr(),"remove_custom_signal",name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + return; //or crash because it will become invalid + + } + + } else { + + if (ti->get_parent()==root->get_children()) { + //edit/remove function + String name = ti->get_metadata(0); + + if (p_button==1) { + //delete the function + undo_redo->create_action(TTR("Remove Function")); + undo_redo->add_do_method(script.ptr(),"remove_function",name); + undo_redo->add_undo_method(script.ptr(),"add_function",name); + List<int> nodes; + script->get_node_list(name,&nodes); + for (List<int>::Element *E=nodes.front();E;E=E->next()) { + undo_redo->add_undo_method(script.ptr(),"add_node",name,E->get(),script->get_node(name,E->get()),script->get_node_pos(name,E->get())); + } + + List<VisualScript::SequenceConnection> seq_connections; + + script->get_sequence_connection_list(name,&seq_connections); + + for (List<VisualScript::SequenceConnection>::Element *E=seq_connections.front();E;E=E->next()) { + undo_redo->add_undo_method(script.ptr(),"sequence_connect",name,E->get().from_node,E->get().from_output,E->get().to_node); + } + + List<VisualScript::DataConnection> data_connections; + + script->get_data_connection_list(name,&data_connections); + + for (List<VisualScript::DataConnection>::Element *E=data_connections.front();E;E=E->next()) { + undo_redo->add_undo_method(script.ptr(),"data_connect",name,E->get().from_node,E->get().from_port,E->get().to_node,E->get().to_port); + } + + //for(int i=0;i<script->function_get_argument_count(name);i++) { + //// undo_redo->add_undo_method(script.ptr(),"function_add_argument",name,script->function_get_argument_name(name,i),script->function_get_argument_type(name,i)); + //} + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + } else if (p_button==0) { + + } + return; //or crash because it will become invalid + + } + + if (ti->get_parent()==root->get_children()->get_next()) { + //edit/remove variable + + String name = ti->get_metadata(0); + + if (p_button==1) { + + + undo_redo->create_action(TTR("Remove Variable")); + undo_redo->add_do_method(script.ptr(),"remove_variable",name); + undo_redo->add_undo_method(script.ptr(),"add_variable",name,script->get_variable_default_value(name)); + undo_redo->add_undo_method(script.ptr(),"set_variable_info",name,script->call("get_variable_info",name)); //return as dict + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + return; //or crash because it will become invalid + } else if (p_button==0) { + + variable_editor->edit(name); + edit_variable_dialog->set_title(TTR("Editing Variable:")+" "+name); + edit_variable_dialog->popup_centered_minsize(Size2(400,200)*EDSCALE); + } + + } + + if (ti->get_parent()==root->get_children()->get_next()->get_next()) { + //edit/remove variable + String name = ti->get_metadata(0); + + if (p_button==1) { + + undo_redo->create_action(TTR("Remove Signal")); + undo_redo->add_do_method(script.ptr(),"remove_custom_signal",name); + undo_redo->add_undo_method(script.ptr(),"add_custom_signal",name); + + for(int i=0;i<script->custom_signal_get_argument_count(name);i++) { + undo_redo->add_undo_method(script.ptr(),"custom_signal_add_argument",name,script->custom_signal_get_argument_name(name,i),script->custom_signal_get_argument_type(name,i)); + } + + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + } else if (p_button==0) { + + signal_editor->edit(name); + edit_signal_dialog->set_title(TTR("Editing Signal:")+" "+name); + edit_signal_dialog->popup_centered_minsize(Size2(400,300)*EDSCALE); + } + + return; //or crash because it will become invalid + + } + + + } +} + +void VisualScriptEditor::_available_node_doubleclicked() { + + TreeItem *item = nodes->get_selected(); + + if (!item) + return; + + String which = item->get_metadata(0); + if (which==String()) + return; + + Vector2 ofs = graph->get_scroll_ofs() + graph->get_size() * 0.5; + + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; + + while(true) { + bool exists=false; + List<int> existing; + script->get_node_list(edited_func,&existing); + for (List<int>::Element *E=existing.front();E;E=E->next()) { + Point2 pos = script->get_node_pos(edited_func,E->get()); + if (pos.distance_to(ofs)<15) { + ofs+=Vector2(graph->get_snap(),graph->get_snap()); + exists=true; + break; + } + } + + if (exists) + continue; + break; + + } + + + Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(which); + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,vnode,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + Node* node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + +} + +void VisualScriptEditor::_update_available_nodes() { + + nodes->clear(); + + TreeItem *root = nodes->create_item(); + + Map<String,TreeItem*> path_cache; + + String filter = node_filter->get_text(); + + List<String> fnodes; + VisualScriptLanguage::singleton->get_registered_node_names(&fnodes); + + for (List<String>::Element *E=fnodes.front();E;E=E->next()) { + + + Vector<String> path = E->get().split("/"); + + if (filter!=String() && path.size() && path[path.size()-1].findn(filter)==-1) + continue; + + String sp; + TreeItem* parent=root; + + for(int i=0;i<path.size()-1;i++) { + + if (i>0) + sp+=","; + sp+=path[i]; + if (!path_cache.has(sp)) { + TreeItem* pathn = nodes->create_item(parent); + pathn->set_selectable(0,false); + pathn->set_text(0,path[i].capitalize()); + path_cache[sp]=pathn; + parent=pathn; + if (filter==String()) { + pathn->set_collapsed(true); //should remember state + } + } else { + parent=path_cache[sp]; + } + } + + TreeItem *item = nodes->create_item(parent); + item->set_text(0,path[path.size()-1].capitalize()); + item->set_selectable(0,true); + item->set_metadata(0,E->get()); + } + +} + +String VisualScriptEditor::_validate_name(const String& p_name) const { + + String valid=p_name; + + int counter=1; + while(true) { + + bool exists = script->has_function(valid) || script->has_variable(valid) || script->has_custom_signal(valid); + + if (exists) { + counter++; + valid=p_name+"_"+itos(counter); + continue; + } + + break; + } + + return valid; +} + +void VisualScriptEditor::_on_nodes_delete() { + + + List<int> to_erase; + + for(int i=0;i<graph->get_child_count();i++) { + GraphNode *gn = graph->get_child(i)->cast_to<GraphNode>(); + if (gn) { + if (gn->is_selected() && gn->is_close_button_visible()) { + to_erase.push_back(gn->get_name().operator String().to_int()); + } + } + } + + if (to_erase.empty()) + return; + + undo_redo->create_action("Remove VisualScript Nodes"); + + for(List<int>::Element*F=to_erase.front();F;F=F->next()) { + + + undo_redo->add_do_method(script.ptr(),"remove_node",edited_func,F->get()); + undo_redo->add_undo_method(script.ptr(),"add_node",edited_func,F->get(),script->get_node(edited_func,F->get()),script->get_node_pos(edited_func,F->get())); + + + List<VisualScript::SequenceConnection> sequence_conns; + script->get_sequence_connection_list(edited_func,&sequence_conns); + + + for (List<VisualScript::SequenceConnection>::Element *E=sequence_conns.front();E;E=E->next()) { + + if (E->get().from_node==F->get() || E->get().to_node==F->get()) { + undo_redo->add_undo_method(script.ptr(),"sequence_connect",edited_func,E->get().from_node,E->get().from_output,E->get().to_node); + } + } + + List<VisualScript::DataConnection> data_conns; + script->get_data_connection_list(edited_func,&data_conns); + + for (List<VisualScript::DataConnection>::Element *E=data_conns.front();E;E=E->next()) { + + if (E->get().from_node==F->get() || E->get().to_node==F->get()) { + undo_redo->add_undo_method(script.ptr(),"data_connect",edited_func,E->get().from_node,E->get().from_port,E->get().to_node,E->get().to_port); + } + } + + } + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + + undo_redo->commit_action(); +} + + +void VisualScriptEditor::_on_nodes_duplicate() { + + + List<int> to_duplicate; + + for(int i=0;i<graph->get_child_count();i++) { + GraphNode *gn = graph->get_child(i)->cast_to<GraphNode>(); + if (gn) { + if (gn->is_selected() && gn->is_close_button_visible()) { + to_duplicate.push_back(gn->get_name().operator String().to_int()); + } + } + } + + if (to_duplicate.empty()) + return; + + undo_redo->create_action("Duplicate VisualScript Nodes"); + int idc=script->get_available_id()+1; + + Set<int> to_select; + + for(List<int>::Element*F=to_duplicate.front();F;F=F->next()) { + + Ref<VisualScriptNode> node = script->get_node(edited_func,F->get()); + + Ref<VisualScriptNode> dupe = node->duplicate(); + + int new_id = idc++; + to_select.insert(new_id); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,dupe,script->get_node_pos(edited_func,F->get())+Vector2(20,20)); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + + } + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + + undo_redo->commit_action(); + + for(int i=0;i<graph->get_child_count();i++) { + GraphNode *gn = graph->get_child(i)->cast_to<GraphNode>(); + if (gn) { + int id = gn->get_name().operator String().to_int(); + gn->set_selected(to_select.has(id)); + + } + } + + if (to_select.size()) { + EditorNode::get_singleton()->push_item(script->get_node(edited_func,to_select.front()->get()).ptr()); + } + +} + +void VisualScriptEditor::_input(const InputEvent& p_event) { + + if (p_event.type==InputEvent::MOUSE_BUTTON && !p_event.mouse_button.pressed && p_event.mouse_button.button_index==BUTTON_LEFT) { + revert_on_drag=String(); //so we can still drag functions + } +} + +Variant VisualScriptEditor::get_drag_data_fw(const Point2& p_point,Control* p_from) { + + + if (p_from==nodes) { + + TreeItem *it = nodes->get_item_at_pos(p_point); + if (!it) + return Variant(); + String type=it->get_metadata(0); + if (type==String()) + return Variant(); + + Dictionary dd; + dd["type"]="visual_script_node_drag"; + dd["node_type"]=type; + + Label *label = memnew(Label); + label->set_text(it->get_text(0)); + set_drag_preview(label); + return dd; + } + + if (p_from==members) { + + + TreeItem *it = members->get_item_at_pos(p_point); + if (!it) + return Variant(); + + String type=it->get_metadata(0); + + if (type==String()) + return Variant(); + + + Dictionary dd; + TreeItem *root=members->get_root(); + + if (it->get_parent()==root->get_children()) { + + dd["type"]="visual_script_function_drag"; + dd["function"]=type; + if (revert_on_drag!=String()) { + edited_func=revert_on_drag; //revert so function does not change + revert_on_drag=String(); + _update_graph(); + } + } else if (it->get_parent()==root->get_children()->get_next()) { + + dd["type"]="visual_script_variable_drag"; + dd["variable"]=type; + } else if (it->get_parent()==root->get_children()->get_next()->get_next()) { + + dd["type"]="visual_script_signal_drag"; + dd["signal"]=type; + + } else { + return Variant(); + } + + + + + + + Label *label = memnew(Label); + label->set_text(it->get_text(0)); + set_drag_preview(label); + return dd; + } + return Variant(); +} + +bool VisualScriptEditor::can_drop_data_fw(const Point2& p_point,const Variant& p_data,Control* p_from) const{ + + if (p_from==graph) { + + + Dictionary d = p_data; + if (d.has("type") && + ( + String(d["type"])=="visual_script_node_drag" || + String(d["type"])=="visual_script_function_drag" || + String(d["type"])=="visual_script_variable_drag" || + String(d["type"])=="visual_script_signal_drag" || + String(d["type"])=="obj_property" || + String(d["type"])=="resource" || + String(d["type"])=="files" || + String(d["type"])=="nodes" + ) ) { + + + if (String(d["type"])=="obj_property") { + +#ifdef OSX_ENABLED + const_cast<VisualScriptEditor*>(this)->_show_hint(TTR("Hold Meta to drop a Getter. Hold Shift to drop a generic signature.")); +#else + const_cast<VisualScriptEditor*>(this)->_show_hint(TTR("Hold Ctrl to drop a Getter. Hold Shift to drop a generic signature.")); +#endif + } + + if (String(d["type"])=="nodes") { + +#ifdef OSX_ENABLED + const_cast<VisualScriptEditor*>(this)->_show_hint(TTR("Hold Meta to drop a simple reference to the node.")); +#else + const_cast<VisualScriptEditor*>(this)->_show_hint(TTR("Hold Ctrl to drop a simple reference to the node.")); +#endif + } + + if (String(d["type"])=="visual_script_variable_drag") { + +#ifdef OSX_ENABLED + const_cast<VisualScriptEditor*>(this)->_show_hint(TTR("Hold Meta to drop a Variable Setter.")); +#else + const_cast<VisualScriptEditor*>(this)->_show_hint(TTR("Hold Ctrl to drop a Variable Setter.")); +#endif + } + + return true; + + } + + + + } + + + return false; +} + +#ifdef TOOLS_ENABLED + +static Node* _find_script_node(Node* p_edited_scene,Node* p_current_node,const Ref<Script> &script) { + + if (p_edited_scene!=p_current_node && p_current_node->get_owner()!=p_edited_scene) + return NULL; + + Ref<Script> scr = p_current_node->get_script(); + + if (scr.is_valid() && scr==script) + return p_current_node; + + for(int i=0;i<p_current_node->get_child_count();i++) { + Node *n = _find_script_node(p_edited_scene,p_current_node->get_child(i),script); + if (n) + return n; + } + + return NULL; +} + +#endif + + + +void VisualScriptEditor::drop_data_fw(const Point2& p_point,const Variant& p_data,Control* p_from){ + + if (p_from==graph) { + + Dictionary d = p_data; + if (d.has("type") && String(d["type"])=="visual_script_node_drag") { + + Vector2 ofs = graph->get_scroll_ofs() + p_point; + + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + + ofs/=EDSCALE; + + Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(d["node_type"]); + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,vnode,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + Node* node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + + if (d.has("type") && String(d["type"])=="visual_script_variable_drag") { + +#ifdef OSX_ENABLED + bool use_set = Input::get_singleton()->is_key_pressed(KEY_META); +#else + bool use_set = Input::get_singleton()->is_key_pressed(KEY_CONTROL); +#endif + Vector2 ofs = graph->get_scroll_ofs() + p_point; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; + + Ref<VisualScriptNode> vnode; + if (use_set) { + Ref<VisualScriptVariableSet> vnodes; + vnodes.instance(); + vnodes->set_variable(d["variable"]); + vnode=vnodes; + } else { + + Ref<VisualScriptVariableGet> vnodeg; + vnodeg.instance(); + vnodeg->set_variable(d["variable"]); + vnode=vnodeg; + } + + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,vnode,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + Node* node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + + if (d.has("type") && String(d["type"])=="visual_script_function_drag") { + + Vector2 ofs = graph->get_scroll_ofs() + p_point; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; + + Ref<VisualScriptFunctionCall> vnode; + vnode.instance(); + vnode->set_call_mode(VisualScriptFunctionCall::CALL_MODE_SELF); + + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,vnode,ofs); + undo_redo->add_do_method(vnode.ptr(),"set_base_type",script->get_instance_base_type()); + undo_redo->add_do_method(vnode.ptr(),"set_function",d["function"]); + + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + Node* node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + + } + + + if (d.has("type") && String(d["type"])=="visual_script_signal_drag") { + + Vector2 ofs = graph->get_scroll_ofs() + p_point; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; + + Ref<VisualScriptEmitSignal> vnode; + vnode.instance(); + vnode->set_signal(d["signal"]); + + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,vnode,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + Node* node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + + if (d.has("type") && String(d["type"])=="resource") { + + Vector2 ofs = graph->get_scroll_ofs() + p_point; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; + + Ref<VisualScriptPreload> prnode; + prnode.instance(); + prnode->set_preload(d["resource"]); + + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Preload Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,prnode,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + Node* node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + + if (d.has("type") && String(d["type"])=="files") { + + Vector2 ofs = graph->get_scroll_ofs() + p_point; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; + + Array files = d["files"]; + + List<int> new_ids; + int new_id = script->get_available_id(); + + if (files.size()) { + undo_redo->create_action(TTR("Add Preload Node")); + + for(int i=0;i<files.size();i++) { + + Ref<Resource> res = ResourceLoader::load(files[i]); + if (!res.is_valid()) + continue; + + Ref<VisualScriptPreload> prnode; + prnode.instance(); + prnode->set_preload(res); + + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,prnode,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + new_ids.push_back(new_id); + new_id++; + ofs+=Vector2(20,20)*EDSCALE; + } + + + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + } + + for(List<int>::Element *E=new_ids.front();E;E=E->next()) { + + Node* node = graph->get_node(itos(E->get())); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + } + + if (d.has("type") && String(d["type"])=="nodes") { + + Node* sn = _find_script_node(get_tree()->get_edited_scene_root(),get_tree()->get_edited_scene_root(),script); + + + if (!sn) { + EditorNode::get_singleton()->show_warning("Can't drop nodes because script '"+get_name()+"' is not used in this scene."); + return; + } + + +#ifdef OSX_ENABLED + bool use_node = Input::get_singleton()->is_key_pressed(KEY_META); +#else + bool use_node = Input::get_singleton()->is_key_pressed(KEY_CONTROL); +#endif + + + Array nodes = d["nodes"]; + + Vector2 ofs = graph->get_scroll_ofs() + p_point; + + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + ofs/=EDSCALE; + + undo_redo->create_action(TTR("Add Node(s) From Tree")); + int base_id = script->get_available_id(); + + if (nodes.size()>1) { + use_node=true; + } + + for(int i=0;i<nodes.size();i++) { + + NodePath np = nodes[i]; + Node *node = get_node(np); + if (!node) { + continue; + } + + Ref<VisualScriptNode> n; + + if (use_node) { + Ref<VisualScriptSceneNode> scene_node; + scene_node.instance(); + scene_node->set_node_path(sn->get_path_to(node)); + n=scene_node; + + + } else { + Ref<VisualScriptFunctionCall> call; + call.instance(); + call->set_call_mode(VisualScriptFunctionCall::CALL_MODE_NODE_PATH); + call->set_base_path(sn->get_path_to(node));; + call->set_base_type(node->get_type()); + n=call; + + method_select->select_method_from_instance(node); + selecting_method_id=base_id; + + } + + + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,base_id,n,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,base_id); + + base_id++; + ofs+=Vector2(25,25); + } + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + + } + + if (d.has("type") && String(d["type"])=="obj_property") { + + Node* sn = _find_script_node(get_tree()->get_edited_scene_root(),get_tree()->get_edited_scene_root(),script); + + + if (!sn && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + EditorNode::get_singleton()->show_warning("Can't drop properties because script '"+get_name()+"' is not used in this scene.\nDrop holding 'Shift' to just copy the signature."); + return; + } + + Object *obj=d["object"]; + + if (!obj) + return; + + Node *node = obj->cast_to<Node>(); + Vector2 ofs = graph->get_scroll_ofs() + p_point; + + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; +#ifdef OSX_ENABLED + bool use_get = Input::get_singleton()->is_key_pressed(KEY_META); +#else + bool use_get = Input::get_singleton()->is_key_pressed(KEY_CONTROL); +#endif + + if (!node || Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + + + if (use_get) + undo_redo->create_action(TTR("Add Getter Property")); + else + undo_redo->create_action(TTR("Add Setter Property")); + + int base_id = script->get_available_id(); + + Ref<VisualScriptNode> vnode; + + if (!use_get) { + + Ref<VisualScriptPropertySet> pset; + pset.instance(); + pset->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE); + pset->set_base_type(obj->get_type()); + /*if (use_value) { + pset->set_use_builtin_value(true); + pset->set_builtin_value(d["value"]); + }*/ + vnode=pset; + } else { + + Ref<VisualScriptPropertyGet> pget; + pget.instance(); + pget->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE); + pget->set_base_type(obj->get_type()); + + vnode=pget; + + } + + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,base_id,vnode,ofs); + undo_redo->add_do_method(vnode.ptr(),"set_property",d["property"]); + if (!use_get) { + undo_redo->add_do_method(vnode.ptr(),"set_default_input_value",0,d["value"]); + } + + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,base_id); + + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + } else { + + + + if (use_get) + undo_redo->create_action(TTR("Add Getter Property")); + else + undo_redo->create_action(TTR("Add Setter Property")); + + int base_id = script->get_available_id(); + + Ref<VisualScriptNode> vnode; + + if (!use_get) { + + Ref<VisualScriptPropertySet> pset; + pset.instance(); + if (sn==node) { + pset->set_call_mode(VisualScriptPropertySet::CALL_MODE_SELF); + } else { + pset->set_call_mode(VisualScriptPropertySet::CALL_MODE_NODE_PATH); + pset->set_base_path(sn->get_path_to(node)); + } + + vnode=pset; + } else { + + Ref<VisualScriptPropertyGet> pget; + pget.instance(); + if (sn==node) { + pget->set_call_mode(VisualScriptPropertyGet::CALL_MODE_SELF); + } else { + pget->set_call_mode(VisualScriptPropertyGet::CALL_MODE_NODE_PATH); + pget->set_base_path(sn->get_path_to(node)); + } + vnode=pget; + + } + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,base_id,vnode,ofs); + undo_redo->add_do_method(vnode.ptr(),"set_property",d["property"]); + if (!use_get) { + undo_redo->add_do_method(vnode.ptr(),"set_default_input_value",0,d["value"]); + } + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,base_id); + + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + } + + + } + + + } + + +} + + +void VisualScriptEditor::_selected_method(const String& p_method) { + + Ref<VisualScriptFunctionCall> vsfc = script->get_node(edited_func,selecting_method_id); + if (!vsfc.is_valid()) + return; + vsfc->set_function(p_method); + +} + +void VisualScriptEditor::_draw_color_over_button(Object* obj,Color p_color) { + + Button *button = obj->cast_to<Button>(); + if (!button) + return; + + Ref<StyleBox> normal = get_stylebox("normal","Button" ); + button->draw_rect(Rect2(normal->get_offset(),button->get_size()-normal->get_minimum_size()),p_color); + +} + +void VisualScriptEditor::_button_resource_previewed(const String& p_path,const Ref<Texture>& p_preview,Variant p_ud) { + + + Array ud=p_ud; + ERR_FAIL_COND(ud.size()!=2); + + ObjectID id = ud[0]; + Object *obj = ObjectDB::get_instance(id); + + if (!obj) + return; + + Button *b = obj->cast_to<Button>(); + ERR_FAIL_COND(!b); + + if (p_preview.is_null()) { + b->set_text(ud[1]); + } else { + + b->set_icon(p_preview); + } + +} + +///////////////////////// + + + +void VisualScriptEditor::apply_code() { + + +} + +Ref<Script> VisualScriptEditor::get_edited_script() const{ + + return script; +} + +Vector<String> VisualScriptEditor::get_functions(){ + + return Vector<String>(); +} + +void VisualScriptEditor::set_edited_script(const Ref<Script>& p_script){ + + script=p_script; + signal_editor->script=p_script; + signal_editor->undo_redo=undo_redo; + variable_editor->script=p_script; + variable_editor->undo_redo=undo_redo; + + + script->connect("node_ports_changed",this,"_node_ports_changed"); + + _update_members(); + _update_available_nodes(); +} + +void VisualScriptEditor::reload_text(){ + + +} + +String VisualScriptEditor::get_name(){ + + String name; + + if (script->get_path().find("local://")==-1 && script->get_path().find("::")==-1) { + name=script->get_path().get_file(); + if (is_unsaved()) { + name+="(*)"; + } + } else if (script->get_name()!="") + name=script->get_name(); + else + name=script->get_type()+"("+itos(script->get_instance_ID())+")"; + + return name; + +} + +Ref<Texture> VisualScriptEditor::get_icon(){ + + return Control::get_icon("VisualScript","EditorIcons"); +} + +bool VisualScriptEditor::is_unsaved(){ +#ifdef TOOLS_ENABLED + + return script->is_edited() || script->are_subnodes_edited(); +#else + return false; +#endif +} + +Variant VisualScriptEditor::get_edit_state(){ + + Dictionary d; + d["function"]=edited_func; + d["scroll"]=graph->get_scroll_ofs(); + d["zoom"]=graph->get_zoom(); + d["using_snap"]=graph->is_using_snap(); + d["snap"]=graph->get_snap(); + return d; +} + +void VisualScriptEditor::set_edit_state(const Variant& p_state){ + + Dictionary d = p_state; + if (d.has("function")) { + edited_func=p_state; + selected=edited_func; + + } + + _update_graph(); + _update_members(); + + if (d.has("scroll")) { + graph->set_scroll_ofs(d["scroll"]); + } + if (d.has("zoom")) { + graph->set_zoom(d["zoom"]); + } + if (d.has("snap")) { + graph->set_snap(d["snap"]); + } + if (d.has("snap_enabled")) { + graph->set_use_snap(d["snap_enabled"]); + } + +} + + +void VisualScriptEditor::_center_on_node(int p_id) { + + Node *n = graph->get_node(itos(p_id)); + if (!n) + return; + GraphNode *gn = n->cast_to<GraphNode>(); + if (gn) { + gn->set_selected(true); + Vector2 new_scroll = gn->get_offset() - graph->get_size()*0.5 + gn->get_size()*0.5; + graph->set_scroll_ofs( new_scroll ); + script->set_function_scroll(edited_func,new_scroll/EDSCALE); + script->set_edited(true); //so it's saved + + } +} + +void VisualScriptEditor::goto_line(int p_line, bool p_with_error){ + + p_line+=1; //add one because script lines begin from 0. + + if (p_with_error) + error_line=p_line; + + List<StringName> functions; + script->get_function_list(&functions); + for (List<StringName>::Element *E=functions.front();E;E=E->next()) { + + if (script->has_node(E->get(),p_line)) { + + edited_func=E->get(); + selected=edited_func; + _update_graph(); + _update_members(); + + call_deferred("_center_on_node",p_line); //editor might be just created and size might not exist yet + + return; + } + } +} + +void VisualScriptEditor::trim_trailing_whitespace(){ + + +} + +void VisualScriptEditor::ensure_focus(){ + + graph->grab_focus(); +} + +void VisualScriptEditor::tag_saved_version(){ + + +} + +void VisualScriptEditor::reload(bool p_soft){ + + +} + +void VisualScriptEditor::get_breakpoints(List<int> *p_breakpoints){ + + List<StringName> functions; + script->get_function_list(&functions); + for (List<StringName>::Element *E=functions.front();E;E=E->next()) { + + List<int> nodes; + script->get_node_list(E->get(),&nodes); + for (List<int>::Element *F=nodes.front();F;F=F->next()) { + + Ref<VisualScriptNode> vsn = script->get_node(E->get(),F->get()); + if (vsn->is_breakpoint()) { + p_breakpoints->push_back(F->get()-1); //subtract 1 because breakpoints in text start from zero + } + } + } +} + +bool VisualScriptEditor::goto_method(const String& p_method){ + + if (!script->has_function(p_method)) + return false; + + edited_func=p_method; + selected=edited_func; + _update_members(); + _update_graph(); + return true; +} + +void VisualScriptEditor::add_callback(const String& p_function,StringArray p_args){ + + if (script->has_function(p_function)) { + edited_func=p_function; + selected=edited_func; + _update_members(); + _update_graph(); + return; + } + + Ref<VisualScriptFunction> func; + func.instance(); + for(int i=0;i<p_args.size();i++) { + + String name = p_args[i]; + Variant::Type type=Variant::NIL; + + if (name.find(":")!=-1) { + String tt = name.get_slice(":",1); + name=name.get_slice(":",0); + for(int j=0;j<Variant::VARIANT_MAX;j++) { + + String tname = Variant::get_type_name(Variant::Type(j)); + if (tname==tt) { + type=Variant::Type(j); + break; + } + } + } + + func->add_argument(type,name); + } + + func->set_name(p_function); + script->add_function(p_function); + script->add_node(p_function,script->get_available_id(),func); + + edited_func=p_function; + selected=edited_func; + _update_members(); + _update_graph(); + graph->call_deferred("set_scroll_ofs",script->get_function_scroll(edited_func)); //for first time it might need to be later + + //undo_redo->clear_history(); + +} + +void VisualScriptEditor::update_settings(){ + + _update_graph(); +} + +void VisualScriptEditor::set_debugger_active(bool p_active) { + if (!p_active) { + error_line=-1; + _update_graph(); //clear line break + } +} + +void VisualScriptEditor::set_tooltip_request_func(String p_method,Object* p_obj){ + + +} + +Control *VisualScriptEditor::get_edit_menu(){ + + return edit_menu; +} + +void VisualScriptEditor::_change_base_type() { + + select_base_type->popup(true); +} + +void VisualScriptEditor::_change_base_type_callback() { + + String bt = select_base_type->get_selected_type(); + + ERR_FAIL_COND(bt==String()); + undo_redo->create_action("Change Base Type"); + undo_redo->add_do_method(script.ptr(),"set_instance_base_type",bt); + undo_redo->add_undo_method(script.ptr(),"set_instance_base_type",script->get_instance_base_type()); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + +} + +void VisualScriptEditor::_node_selected(Node* p_node) { + + Ref<VisualScriptNode> vnode = p_node->get_meta("__vnode"); + if (vnode.is_null()) + return; + + EditorNode::get_singleton()->push_item(vnode.ptr()); //edit node in inspector +} + +static bool _get_out_slot(const Ref<VisualScriptNode>& p_node,int p_slot,int& r_real_slot,bool& r_sequence) { + + if (p_slot<p_node->get_output_sequence_port_count()) { + r_sequence=true; + r_real_slot=p_slot; + + return true; + } + + r_real_slot=p_slot-p_node->get_output_sequence_port_count(); + r_sequence=false; + + return (r_real_slot<p_node->get_output_value_port_count()); + +} + +static bool _get_in_slot(const Ref<VisualScriptNode>& p_node,int p_slot,int& r_real_slot,bool& r_sequence) { + + if (p_slot==0 && p_node->has_input_sequence_port()) { + r_sequence=true; + r_real_slot=0; + return true; + } + + + r_real_slot=p_slot-(p_node->has_input_sequence_port()?1:0); + r_sequence=false; + + return r_real_slot<p_node->get_input_value_port_count(); + +} + + +void VisualScriptEditor::_begin_node_move() { + + undo_redo->create_action("Move Node(s)"); +} + +void VisualScriptEditor::_end_node_move() { + + undo_redo->commit_action(); +} + +void VisualScriptEditor::_move_node(String func,int p_id,const Vector2& p_to) { + + + + if (func==String(edited_func)) { + Node* node = graph->get_node(itos(p_id)); + if (node && node->cast_to<GraphNode>()) + node->cast_to<GraphNode>()->set_offset(p_to); + } + script->set_node_pos(edited_func,p_id,p_to/EDSCALE); +} + +void VisualScriptEditor::_node_moved(Vector2 p_from,Vector2 p_to, int p_id) { + + undo_redo->add_do_method(this,"_move_node",String(edited_func),p_id,p_to); + undo_redo->add_undo_method(this,"_move_node",String(edited_func),p_id,p_from); +} + +void VisualScriptEditor::_remove_node(int p_id) { + + + undo_redo->create_action("Remove VisualScript Node"); + + undo_redo->add_do_method(script.ptr(),"remove_node",edited_func,p_id); + undo_redo->add_undo_method(script.ptr(),"add_node",edited_func,p_id,script->get_node(edited_func,p_id),script->get_node_pos(edited_func,p_id)); + + + List<VisualScript::SequenceConnection> sequence_conns; + script->get_sequence_connection_list(edited_func,&sequence_conns); + + + for (List<VisualScript::SequenceConnection>::Element *E=sequence_conns.front();E;E=E->next()) { + + if (E->get().from_node==p_id || E->get().to_node==p_id) { + undo_redo->add_undo_method(script.ptr(),"sequence_connect",edited_func,E->get().from_node,E->get().from_output,E->get().to_node); + } + } + + List<VisualScript::DataConnection> data_conns; + script->get_data_connection_list(edited_func,&data_conns); + + for (List<VisualScript::DataConnection>::Element *E=data_conns.front();E;E=E->next()) { + + if (E->get().from_node==p_id || E->get().to_node==p_id) { + undo_redo->add_undo_method(script.ptr(),"data_connect",edited_func,E->get().from_node,E->get().from_port,E->get().to_node,E->get().to_port); + } + } + + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + + undo_redo->commit_action(); +} + + +void VisualScriptEditor::_node_ports_changed(const String& p_func,int p_id) { + + if (p_func!=String(edited_func)) + return; + + _update_graph(p_id); +} + +void VisualScriptEditor::_graph_connected(const String& p_from,int p_from_slot,const String& p_to,int p_to_slot) { + + Ref<VisualScriptNode> from_node = script->get_node(edited_func,p_from.to_int()); + ERR_FAIL_COND(!from_node.is_valid()); + + bool from_seq; + int from_port; + + if (!_get_out_slot(from_node,p_from_slot,from_port,from_seq)) + return; //can't connect this, it' s invalid + + Ref<VisualScriptNode> to_node = script->get_node(edited_func,p_to.to_int()); + ERR_FAIL_COND(!to_node.is_valid()); + + bool to_seq; + int to_port; + + if (!_get_in_slot(to_node,p_to_slot,to_port,to_seq)) + return; //can't connect this, it' s invalid + + + ERR_FAIL_COND(from_seq!=to_seq); + + + undo_redo->create_action("Connect Nodes"); + + if (from_seq) { + undo_redo->add_do_method(script.ptr(),"sequence_connect",edited_func,p_from.to_int(),from_port,p_to.to_int()); + undo_redo->add_undo_method(script.ptr(),"sequence_disconnect",edited_func,p_from.to_int(),from_port,p_to.to_int()); + } else { + undo_redo->add_do_method(script.ptr(),"data_connect",edited_func,p_from.to_int(),from_port,p_to.to_int(),to_port); + undo_redo->add_undo_method(script.ptr(),"data_disconnect",edited_func,p_from.to_int(),from_port,p_to.to_int(),to_port); + //update nodes in sgraph + undo_redo->add_do_method(this,"_update_graph",p_from.to_int()); + undo_redo->add_do_method(this,"_update_graph",p_to.to_int()); + undo_redo->add_undo_method(this,"_update_graph",p_from.to_int()); + undo_redo->add_undo_method(this,"_update_graph",p_to.to_int()); + } + + undo_redo->add_do_method(this,"_update_graph_connections"); + undo_redo->add_undo_method(this,"_update_graph_connections"); + + undo_redo->commit_action(); + +} + +void VisualScriptEditor::_graph_disconnected(const String& p_from,int p_from_slot,const String& p_to,int p_to_slot){ + + Ref<VisualScriptNode> from_node = script->get_node(edited_func,p_from.to_int()); + ERR_FAIL_COND(!from_node.is_valid()); + + bool from_seq; + int from_port; + + if (!_get_out_slot(from_node,p_from_slot,from_port,from_seq)) + return; //can't connect this, it' s invalid + + Ref<VisualScriptNode> to_node = script->get_node(edited_func,p_to.to_int()); + ERR_FAIL_COND(!to_node.is_valid()); + + bool to_seq; + int to_port; + + if (!_get_in_slot(to_node,p_to_slot,to_port,to_seq)) + return; //can't connect this, it' s invalid + + + ERR_FAIL_COND(from_seq!=to_seq); + + + undo_redo->create_action("Connect Nodes"); + + if (from_seq) { + undo_redo->add_do_method(script.ptr(),"sequence_disconnect",edited_func,p_from.to_int(),from_port,p_to.to_int()); + undo_redo->add_undo_method(script.ptr(),"sequence_connect",edited_func,p_from.to_int(),from_port,p_to.to_int()); + } else { + undo_redo->add_do_method(script.ptr(),"data_disconnect",edited_func,p_from.to_int(),from_port,p_to.to_int(),to_port); + undo_redo->add_undo_method(script.ptr(),"data_connect",edited_func,p_from.to_int(),from_port,p_to.to_int(),to_port); + //update nodes in sgraph + undo_redo->add_do_method(this,"_update_graph",p_from.to_int()); + undo_redo->add_do_method(this,"_update_graph",p_to.to_int()); + undo_redo->add_undo_method(this,"_update_graph",p_from.to_int()); + undo_redo->add_undo_method(this,"_update_graph",p_to.to_int()); + } + undo_redo->add_do_method(this,"_update_graph_connections"); + undo_redo->add_undo_method(this,"_update_graph_connections"); + + undo_redo->commit_action(); +} + + + +void VisualScriptEditor::_graph_connect_to_empty(const String& p_from,int p_from_slot,const Vector2& p_release_pos) { + + Node* node = graph->get_node(p_from); + if (!node) + return; + + GraphNode *gn = node->cast_to<GraphNode>(); + if (!gn) + return; + + Ref<VisualScriptNode> vsn = script->get_node(edited_func,p_from.to_int()); + if (!vsn.is_valid()) + return; + + if (p_from_slot<vsn->get_output_sequence_port_count()) { + + port_action_popup->clear(); + port_action_popup->add_item(TTR("Condition"),CREATE_COND); + port_action_popup->add_item(TTR("Sequence"),CREATE_SEQUENCE); + port_action_popup->add_item(TTR("Switch"),CREATE_SWITCH); + port_action_popup->add_item(TTR("Iterator"),CREATE_ITERATOR); + port_action_popup->add_item(TTR("While"),CREATE_WHILE); + port_action_popup->add_item(TTR("Return"),CREATE_RETURN); + + port_action_node=p_from.to_int(); + port_action_output=p_from_slot; + + } else { + port_action_popup->clear(); + port_action_popup->add_item(TTR("Call"),CREATE_CALL); + port_action_popup->add_item(TTR("Get"),CREATE_GET); + port_action_popup->add_item(TTR("Set"),CREATE_SET); + + + port_action_output=p_from_slot-vsn->get_output_sequence_port_count(); + port_action_node=p_from.to_int(); + + + } + + port_action_pos=p_release_pos; + port_action_popup->set_size(Size2(1,1)); + port_action_popup->set_pos(graph->get_global_pos()+p_release_pos); + port_action_popup->popup(); +} + +VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_node,int p_output,Set<int> &visited_nodes) { + + + VisualScriptNode::TypeGuess tg; + tg.type=Variant::NIL; + + if (visited_nodes.has(p_node)) + return tg; //no loop + + visited_nodes.insert(p_node); + + Ref<VisualScriptNode> node = script->get_node(edited_func,p_node); + + if (!node.is_valid()) { + + return tg; + } + + Vector<VisualScriptNode::TypeGuess> in_guesses; + + for(int i=0;i<node->get_input_value_port_count();i++) { + PropertyInfo pi = node->get_input_value_port_info(i); + VisualScriptNode::TypeGuess g; + g.type=pi.type; + + if (g.type==Variant::NIL || g.type==Variant::OBJECT) { + //any or object input, must further guess what this is + int from_node; + int from_port; + + if (script->get_input_value_port_connection_source(edited_func,p_node,i,&from_node,&from_port)) { + + g = _guess_output_type(from_node,from_port,visited_nodes); + } else { + Variant defval = node->get_default_input_value(i); + if (defval.get_type()==Variant::OBJECT) { + + Object *obj = defval; + + if (obj) { + + g.type=Variant::OBJECT; + g.obj_type=obj->get_type(); + g.script=obj->get_script(); + } + } + } + + } + + in_guesses.push_back(g); + } + + return node->guess_output_type(in_guesses.ptr(),p_output); +} + +void VisualScriptEditor::_port_action_menu(int p_option) { + + Vector2 ofs = graph->get_scroll_ofs() + port_action_pos; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + ofs/=EDSCALE; + + bool seq_connect=false; + + Ref<VisualScriptNode> vnode; + Set<int> vn; + + switch(p_option) { + + case CREATE_CALL: { + + Ref<VisualScriptFunctionCall> n; + n.instance(); + vnode=n; + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node,port_action_output,vn); + + if (tg.type==Variant::OBJECT) { + n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_INSTANCE); + + if (tg.obj_type!=StringName()) { + n->set_base_type(tg.obj_type); + } else { + n->set_base_type("Object"); + } + + if (tg.script.is_valid()) { + n->set_base_script(tg.script->get_path()); + new_connect_node_select->select_method_from_script(tg.script); + } else { + new_connect_node_select->select_method_from_base_type(n->get_base_type()); + } + + + } else { + n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_BASIC_TYPE); + n->set_basic_type(tg.type); + new_connect_node_select->select_method_from_basic_type(tg.type); + } + + + + } break; + case CREATE_SET: { + + Ref<VisualScriptPropertySet> n; + n.instance(); + vnode=n; + + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node,port_action_output,vn); + + if (tg.type==Variant::OBJECT) { + n->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE); + + if (tg.obj_type!=StringName()) { + n->set_base_type(tg.obj_type); + } else { + n->set_base_type("Object"); + } + + if (tg.script.is_valid()) { + n->set_base_script(tg.script->get_path()); + new_connect_node_select->select_property_from_script(tg.script); + } else { + new_connect_node_select->select_property_from_base_type(n->get_base_type()); + } + + + } else { + n->set_call_mode(VisualScriptPropertySet::CALL_MODE_BASIC_TYPE); + n->set_basic_type(tg.type); + new_connect_node_select->select_property_from_basic_type(tg.type,tg.ev_type); + } + } break; + case CREATE_GET: { + + Ref<VisualScriptPropertyGet> n; + n.instance(); + vnode=n; + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node,port_action_output,vn); + + if (tg.type==Variant::OBJECT) { + n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE); + + if (tg.obj_type!=StringName()) { + n->set_base_type(tg.obj_type); + } else { + n->set_base_type("Object"); + } + + if (tg.script.is_valid()) { + n->set_base_script(tg.script->get_path()); + new_connect_node_select->select_property_from_script(tg.script); + } else { + new_connect_node_select->select_property_from_base_type(n->get_base_type()); + } + + + } else { + n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_BASIC_TYPE); + n->set_basic_type(tg.type); + new_connect_node_select->select_property_from_basic_type(tg.type,tg.ev_type); + } + + } break; + case CREATE_COND: { + + Ref<VisualScriptCondition> n; + n.instance(); + vnode=n; + seq_connect=true; + + } break; + case CREATE_SEQUENCE: { + + Ref<VisualScriptSequence> n; + n.instance(); + vnode=n; + seq_connect=true; + + } break; + case CREATE_SWITCH: { + + Ref<VisualScriptSwitch> n; + n.instance(); + vnode=n; + seq_connect=true; + + } break; + case CREATE_ITERATOR: { + + Ref<VisualScriptIterator> n; + n.instance(); + vnode=n; + seq_connect=true; + + } break; + case CREATE_WHILE: { + + Ref<VisualScriptWhile> n; + n.instance(); + vnode=n; + seq_connect=true; + + } break; + case CREATE_RETURN: { + + Ref<VisualScriptReturn> n; + n.instance(); + vnode=n; + seq_connect=true; + + } break; + + } + + int new_id = script->get_available_id(); + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,vnode,ofs); + if (seq_connect) { + undo_redo->add_do_method(script.ptr(),"sequence_connect",edited_func,port_action_node,port_action_output,new_id); + } + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph",new_id); + undo_redo->add_undo_method(this,"_update_graph",new_id); + undo_redo->commit_action(); + + port_action_new_node=new_id; + +} + +void VisualScriptEditor::_selected_connect_node_method_or_setget(const String& p_text) { + + Ref<VisualScriptNode> vsn = script->get_node(edited_func,port_action_new_node); + + if (vsn->cast_to<VisualScriptFunctionCall>()) { + + Ref<VisualScriptFunctionCall> vsfc = vsn; + vsfc->set_function(p_text); + script->data_connect(edited_func,port_action_node,port_action_output,port_action_new_node,0); + } + + if (vsn->cast_to<VisualScriptPropertySet>()) { + + Ref<VisualScriptPropertySet> vsp = vsn; + vsp->set_property(p_text); + script->data_connect(edited_func,port_action_node,port_action_output,port_action_new_node,0); + } + + if (vsn->cast_to<VisualScriptPropertyGet>()) { + + Ref<VisualScriptPropertyGet> vsp = vsn; + vsp->set_property(p_text); + script->data_connect(edited_func,port_action_node,port_action_output,port_action_new_node,0); + } + + _update_graph(port_action_new_node); + _update_graph_connections(); + +} + + +void VisualScriptEditor::_default_value_changed() { + + + Ref<VisualScriptNode> vsn = script->get_node(edited_func,editing_id); + if (vsn.is_null()) + return; + + undo_redo->create_action("Change Input Value"); + undo_redo->add_do_method(vsn.ptr(),"set_default_input_value",editing_input,default_value_edit->get_variant()); + undo_redo->add_undo_method(vsn.ptr(),"set_default_input_value",editing_input,vsn->get_default_input_value(editing_input)); + + undo_redo->add_do_method(this,"_update_graph",editing_id); + undo_redo->add_undo_method(this,"_update_graph",editing_id); + undo_redo->commit_action(); + +} + +void VisualScriptEditor::_default_value_edited(Node * p_button,int p_id,int p_input_port) { + + Ref<VisualScriptNode> vsn = script->get_node(edited_func,p_id); + if (vsn.is_null()) + return; + + PropertyInfo pinfo = vsn->get_input_value_port_info(p_input_port); + Variant existing = vsn->get_default_input_value(p_input_port); + if (pinfo.type!=Variant::NIL && existing.get_type()!=pinfo.type) { + + Variant::CallError ce; + const Variant *existingp=&existing; + existing = Variant::construct(pinfo.type,&existingp,1,ce,false); + + } + + default_value_edit->set_pos(p_button->cast_to<Control>()->get_global_pos()+Vector2(0,p_button->cast_to<Control>()->get_size().y)); + default_value_edit->set_size(Size2(1,1)); + if (default_value_edit->edit(NULL,pinfo.name,pinfo.type,existing,pinfo.hint,pinfo.hint_string)) { + if (pinfo.hint==PROPERTY_HINT_MULTILINE_TEXT) + default_value_edit->popup_centered_ratio(); + else + default_value_edit->popup(); + } + + editing_id = p_id; + editing_input=p_input_port; + +} + +void VisualScriptEditor::_show_hint(const String& p_hint) { + + hint_text->set_text(p_hint); + hint_text->show(); + hint_text_timer->start(); +} + +void VisualScriptEditor::_hide_timer() { + + hint_text->hide(); +} + +void VisualScriptEditor::_node_filter_changed(const String& p_text) { + + _update_available_nodes(); +} + +void VisualScriptEditor::_notification(int p_what) { + + if (p_what==NOTIFICATION_READY) { + node_filter_icon->set_texture(Control::get_icon("Zoom","EditorIcons")); + } +} + +void VisualScriptEditor::_graph_ofs_changed(const Vector2& p_ofs) { + + if (updating_graph) + return; + + updating_graph=true; + + if (script->has_function(edited_func)) { + script->set_function_scroll(edited_func,graph->get_scroll_ofs()/EDSCALE); + script->set_edited(true); + } + updating_graph=false; +} + +void VisualScriptEditor::_comment_node_resized(const Vector2& p_new_size,int p_node) { + + if (updating_graph) + return; + + Ref<VisualScriptComment> vsc = script->get_node(edited_func,p_node); + if (vsc.is_null()) + return; + + Node *node = graph->get_node(itos(p_node)); + if (!node) + return; + GraphNode *gn = node->cast_to<GraphNode>(); + if (!gn) + return; + + updating_graph=true; + + graph->set_block_minimum_size_adjust(true); //faster resize + + undo_redo->create_action("Resize Comment",UndoRedo::MERGE_ENDS); + undo_redo->add_do_method(vsc.ptr(),"set_size",p_new_size/EDSCALE); + undo_redo->add_undo_method(vsc.ptr(),"set_size",vsc->get_size()); + undo_redo->commit_action(); + + gn->set_custom_minimum_size(p_new_size); //for this time since graph update is blocked + gn->set_size(Size2(1,1)); + graph->set_block_minimum_size_adjust(false); + updating_graph=false; + + +} + +void VisualScriptEditor::_menu_option(int p_what) { + + switch(p_what) { + case EDIT_DELETE_NODES: { + _on_nodes_delete(); + } break; + case EDIT_TOGGLE_BREAKPOINT: { + + List<String> reselect; + for(int i=0;i<graph->get_child_count();i++) { + GraphNode *gn = graph->get_child(i)->cast_to<GraphNode>(); + if (gn) { + if (gn->is_selected()) { + int id = String(gn->get_name()).to_int(); + Ref<VisualScriptNode> vsn = script->get_node(edited_func,id); + if (vsn.is_valid()) { + vsn->set_breakpoint(!vsn->is_breakpoint()); + reselect.push_back(gn->get_name()); + } + } + } + } + + _update_graph(); + + for(List<String>::Element *E=reselect.front();E;E=E->next()) { + GraphNode *gn = graph->get_node(E->get())->cast_to<GraphNode>(); + gn->set_selected(true); + } + + } break; + case EDIT_FIND_NODE_TYPE: { + //popup disappearing grabs focus to owner, so use call deferred + node_filter->call_deferred("grab_focus"); + node_filter->call_deferred("select_all"); + } break; + case EDIT_COPY_NODES: + case EDIT_CUT_NODES: { + + if (!script->has_function(edited_func)) + break; + + clipboard->nodes.clear(); + clipboard->data_connections.clear(); + clipboard->sequence_connections.clear(); + + for(int i=0;i<graph->get_child_count();i++) { + GraphNode *gn = graph->get_child(i)->cast_to<GraphNode>(); + if (gn) { + if (gn->is_selected()) { + + int id = String(gn->get_name()).to_int(); + Ref<VisualScriptNode> node = script->get_node(edited_func,id); + if (node->cast_to<VisualScriptFunction>()) { + EditorNode::get_singleton()->show_warning("Can't copy the function node."); + return; + } + if (node.is_valid()) { + clipboard->nodes[id]=node->duplicate(); + clipboard->nodes_positions[id]=script->get_node_pos(edited_func,id); + } + + } + } + } + + if (clipboard->nodes.empty()) + break; + + List<VisualScript::SequenceConnection> sequence_connections; + + script->get_sequence_connection_list(edited_func,&sequence_connections); + + for (List<VisualScript::SequenceConnection>::Element *E=sequence_connections.front();E;E=E->next()) { + + if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) { + + clipboard->sequence_connections.insert(E->get()); + } + } + + List<VisualScript::DataConnection> data_connections; + + script->get_data_connection_list(edited_func,&data_connections); + + for (List<VisualScript::DataConnection>::Element *E=data_connections.front();E;E=E->next()) { + + if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) { + + clipboard->data_connections.insert(E->get()); + } + } + + if (p_what==EDIT_CUT_NODES) { + _on_nodes_delete(); // oh yeah, also delete on cut + } + + + } break; + case EDIT_PASTE_NODES: { + if (!script->has_function(edited_func)) + break; + + if (clipboard->nodes.empty()) { + EditorNode::get_singleton()->show_warning("Clipboard is empty!"); + break; + } + + Map<int,int> remap; + + undo_redo->create_action("Paste VisualScript Nodes"); + int idc=script->get_available_id()+1; + + Set<int> to_select; + + Set<Vector2> existing_positions; + + { + List<int> nodes; + script->get_node_list(edited_func,&nodes); + for (List<int>::Element *E=nodes.front();E;E=E->next()) { + Vector2 pos = script->get_node_pos(edited_func,E->get()).snapped(Vector2(2,2)); + existing_positions.insert(pos); + } + } + + for (Map<int,Ref<VisualScriptNode> >::Element *E=clipboard->nodes.front();E;E=E->next()) { + + + Ref<VisualScriptNode> node = E->get()->duplicate(); + + int new_id = idc++; + to_select.insert(new_id); + + remap[E->key()]=new_id; + + Vector2 paste_pos = clipboard->nodes_positions[E->key()]; + + while(existing_positions.has(paste_pos.snapped(Vector2(2,2)))) { + paste_pos+=Vector2(20,20)*EDSCALE; + } + + + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,node,paste_pos); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + + } + + for (Set<VisualScript::SequenceConnection>::Element *E=clipboard->sequence_connections.front();E;E=E->next()) { + + + undo_redo->add_do_method(script.ptr(),"sequence_connect",edited_func,remap[E->get().from_node],E->get().from_output,remap[E->get().to_node]); + undo_redo->add_undo_method(script.ptr(),"sequence_disconnect",edited_func,remap[E->get().from_node],E->get().from_output,remap[E->get().to_node]); + + } + + for (Set<VisualScript::DataConnection>::Element *E=clipboard->data_connections.front();E;E=E->next()) { + + + undo_redo->add_do_method(script.ptr(),"data_connect",edited_func,remap[E->get().from_node],E->get().from_port,remap[E->get().to_node],E->get().to_port); + undo_redo->add_undo_method(script.ptr(),"data_disconnect",edited_func,remap[E->get().from_node],E->get().from_port,remap[E->get().to_node],E->get().to_port); + + } + + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + + undo_redo->commit_action(); + + for(int i=0;i<graph->get_child_count();i++) { + GraphNode *gn = graph->get_child(i)->cast_to<GraphNode>(); + if (gn) { + int id = gn->get_name().operator String().to_int(); + gn->set_selected(to_select.has(id)); + + } + } + } break; + + + } +} + +void VisualScriptEditor::_bind_methods() { + + ObjectTypeDB::bind_method("_member_button",&VisualScriptEditor::_member_button); + ObjectTypeDB::bind_method("_member_edited",&VisualScriptEditor::_member_edited); + ObjectTypeDB::bind_method("_member_selected",&VisualScriptEditor::_member_selected); + ObjectTypeDB::bind_method("_update_members",&VisualScriptEditor::_update_members); + ObjectTypeDB::bind_method("_change_base_type",&VisualScriptEditor::_change_base_type); + ObjectTypeDB::bind_method("_change_base_type_callback",&VisualScriptEditor::_change_base_type_callback); + ObjectTypeDB::bind_method("_override_pressed",&VisualScriptEditor::_override_pressed); + ObjectTypeDB::bind_method("_node_selected",&VisualScriptEditor::_node_selected); + ObjectTypeDB::bind_method("_node_moved",&VisualScriptEditor::_node_moved); + ObjectTypeDB::bind_method("_move_node",&VisualScriptEditor::_move_node); + ObjectTypeDB::bind_method("_begin_node_move",&VisualScriptEditor::_begin_node_move); + ObjectTypeDB::bind_method("_end_node_move",&VisualScriptEditor::_end_node_move); + ObjectTypeDB::bind_method("_remove_node",&VisualScriptEditor::_remove_node); + ObjectTypeDB::bind_method("_update_graph",&VisualScriptEditor::_update_graph,DEFVAL(-1)); + ObjectTypeDB::bind_method("_node_ports_changed",&VisualScriptEditor::_node_ports_changed); + ObjectTypeDB::bind_method("_available_node_doubleclicked",&VisualScriptEditor::_available_node_doubleclicked); + ObjectTypeDB::bind_method("_default_value_edited",&VisualScriptEditor::_default_value_edited); + ObjectTypeDB::bind_method("_default_value_changed",&VisualScriptEditor::_default_value_changed); + ObjectTypeDB::bind_method("_menu_option",&VisualScriptEditor::_menu_option); + ObjectTypeDB::bind_method("_graph_ofs_changed",&VisualScriptEditor::_graph_ofs_changed); + ObjectTypeDB::bind_method("_center_on_node",&VisualScriptEditor::_center_on_node); + ObjectTypeDB::bind_method("_comment_node_resized",&VisualScriptEditor::_comment_node_resized); + ObjectTypeDB::bind_method("_button_resource_previewed",&VisualScriptEditor::_button_resource_previewed); + ObjectTypeDB::bind_method("_port_action_menu",&VisualScriptEditor::_port_action_menu); + ObjectTypeDB::bind_method("_selected_connect_node_method_or_setget",&VisualScriptEditor::_selected_connect_node_method_or_setget); + + + + + ObjectTypeDB::bind_method("get_drag_data_fw",&VisualScriptEditor::get_drag_data_fw); + ObjectTypeDB::bind_method("can_drop_data_fw",&VisualScriptEditor::can_drop_data_fw); + ObjectTypeDB::bind_method("drop_data_fw",&VisualScriptEditor::drop_data_fw); + + ObjectTypeDB::bind_method("_input",&VisualScriptEditor::_input); + ObjectTypeDB::bind_method("_on_nodes_delete",&VisualScriptEditor::_on_nodes_delete); + ObjectTypeDB::bind_method("_on_nodes_duplicate",&VisualScriptEditor::_on_nodes_duplicate); + + ObjectTypeDB::bind_method("_hide_timer",&VisualScriptEditor::_hide_timer); + + ObjectTypeDB::bind_method("_graph_connected",&VisualScriptEditor::_graph_connected); + ObjectTypeDB::bind_method("_graph_disconnected",&VisualScriptEditor::_graph_disconnected); + ObjectTypeDB::bind_method("_graph_connect_to_empty",&VisualScriptEditor::_graph_connect_to_empty); + + ObjectTypeDB::bind_method("_update_graph_connections",&VisualScriptEditor::_update_graph_connections); + ObjectTypeDB::bind_method("_node_filter_changed",&VisualScriptEditor::_node_filter_changed); + + ObjectTypeDB::bind_method("_selected_method",&VisualScriptEditor::_selected_method); + ObjectTypeDB::bind_method("_draw_color_over_button",&VisualScriptEditor::_draw_color_over_button); + + + + +} + + + +VisualScriptEditor::VisualScriptEditor() { + + if (!clipboard) { + clipboard = memnew( Clipboard ); + } + updating_graph=false; + + edit_menu = memnew( MenuButton ); + edit_menu->set_text(TTR("Edit")); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/delete_selected"), EDIT_DELETE_NODES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/toggle_breakpoint"), EDIT_TOGGLE_BREAKPOINT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/find_node_type"), EDIT_FIND_NODE_TYPE); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/copy_nodes"), EDIT_COPY_NODES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/cut_nodes"), EDIT_CUT_NODES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/paste_nodes"), EDIT_PASTE_NODES); + + edit_menu->get_popup()->connect("item_pressed",this,"_menu_option"); + + main_hsplit = memnew( HSplitContainer ); + add_child(main_hsplit); + main_hsplit->set_area_as_parent_rect(); + + left_vsplit = memnew( VSplitContainer ); + main_hsplit->add_child(left_vsplit); + + VBoxContainer *left_vb = memnew( VBoxContainer ); + left_vsplit->add_child(left_vb); + left_vb->set_v_size_flags(SIZE_EXPAND_FILL); + left_vb->set_custom_minimum_size(Size2(230,1)*EDSCALE); + + base_type_select = memnew( Button ); + left_vb->add_margin_child(TTR("Base Type:"),base_type_select); + base_type_select->connect("pressed",this,"_change_base_type"); + + members = memnew( Tree ); + left_vb->add_margin_child(TTR("Members:"),members,true); + members->set_hide_root(true); + members->connect("button_pressed",this,"_member_button"); + members->connect("item_edited",this,"_member_edited"); + members->connect("cell_selected",this,"_member_selected",varray(),CONNECT_DEFERRED); + members->set_single_select_cell_editing_only_when_already_selected(true); + members->set_hide_folding(true); + members->set_drag_forwarding(this); + + + VBoxContainer *left_vb2 = memnew( VBoxContainer ); + left_vsplit->add_child(left_vb2); + left_vb2->set_v_size_flags(SIZE_EXPAND_FILL); + + + VBoxContainer *vbc_nodes = memnew( VBoxContainer ); + HBoxContainer *hbc_nodes = memnew( HBoxContainer ); + node_filter = memnew (LineEdit); + node_filter->connect("text_changed",this,"_node_filter_changed"); + hbc_nodes->add_child(node_filter); + node_filter->set_h_size_flags(SIZE_EXPAND_FILL); + node_filter_icon = memnew( TextureFrame ); + node_filter_icon->set_stretch_mode(TextureFrame::STRETCH_KEEP_CENTERED); + hbc_nodes->add_child(node_filter_icon); + vbc_nodes->add_child(hbc_nodes); + + nodes = memnew( Tree ); + vbc_nodes->add_child(nodes); + nodes->set_v_size_flags(SIZE_EXPAND_FILL); + + left_vb2->add_margin_child(TTR("Available Nodes:"),vbc_nodes,true); + + nodes->set_hide_root(true); + nodes->connect("item_activated",this,"_available_node_doubleclicked"); + nodes->set_drag_forwarding(this); + + graph = memnew( GraphEdit ); + main_hsplit->add_child(graph); + graph->set_h_size_flags(SIZE_EXPAND_FILL); + graph->connect("node_selected",this,"_node_selected"); + graph->connect("_begin_node_move",this,"_begin_node_move"); + graph->connect("_end_node_move",this,"_end_node_move"); + graph->connect("delete_nodes_request",this,"_on_nodes_delete"); + graph->connect("duplicate_nodes_request",this,"_on_nodes_duplicate"); + graph->set_drag_forwarding(this); + graph->hide(); + graph->connect("scroll_offset_changed",this,"_graph_ofs_changed"); + + select_func_text = memnew( Label ); + select_func_text->set_text(TTR("Select or create a function to edit graph")); + select_func_text->set_align(Label::ALIGN_CENTER); + select_func_text->set_valign(Label::VALIGN_CENTER); + select_func_text->set_h_size_flags(SIZE_EXPAND_FILL); + main_hsplit->add_child(select_func_text); + + + hint_text = memnew( Label ); + hint_text->set_anchor_and_margin(MARGIN_TOP,ANCHOR_END,100); + hint_text->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_END,0); + hint_text->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,0); + hint_text->set_align(Label::ALIGN_CENTER); + hint_text->set_valign(Label::VALIGN_CENTER); + graph->add_child(hint_text); + + hint_text_timer = memnew( Timer ); + hint_text_timer->set_wait_time(4); + hint_text_timer->connect("timeout",this,"_hide_timer"); + add_child(hint_text_timer); + + //allowed casts (connections) + for(int i=0;i<Variant::VARIANT_MAX;i++) { + graph->add_valid_connection_type(Variant::NIL,i); + graph->add_valid_connection_type(i,Variant::NIL); + for(int j=0;j<Variant::VARIANT_MAX;j++) { + if (Variant::can_convert(Variant::Type(i),Variant::Type(j))) { + graph->add_valid_connection_type(i,j); + } + } + + graph->add_valid_right_disconnect_type(i); + } + + graph->add_valid_left_disconnect_type(TYPE_SEQUENCE); + + graph->connect("connection_request",this,"_graph_connected"); + graph->connect("disconnection_request",this,"_graph_disconnected"); + graph->connect("connection_to_empty",this,"_graph_connect_to_empty"); + + edit_signal_dialog = memnew( AcceptDialog ); + edit_signal_dialog->get_ok()->set_text(TTR("Close")); + add_child(edit_signal_dialog); + edit_signal_dialog->set_title(TTR("Edit Signal Arguments:")); + + signal_editor = memnew( VisualScriptEditorSignalEdit ); + edit_signal_edit = memnew( PropertyEditor ); + edit_signal_edit->hide_top_label(); + edit_signal_dialog->add_child(edit_signal_edit); + edit_signal_dialog->set_child_rect(edit_signal_edit); + edit_signal_edit->edit(signal_editor); + + edit_variable_dialog = memnew( AcceptDialog ); + edit_variable_dialog->get_ok()->set_text(TTR("Close")); + add_child(edit_variable_dialog); + edit_variable_dialog->set_title(TTR("Edit Variable:")); + + variable_editor = memnew( VisualScriptEditorVariableEdit ); + edit_variable_edit = memnew( PropertyEditor ); + edit_variable_edit->hide_top_label(); + edit_variable_dialog->add_child(edit_variable_edit); + edit_variable_dialog->set_child_rect(edit_variable_edit); + edit_variable_edit->edit(variable_editor); + + select_base_type=memnew(CreateDialog); + select_base_type->set_base_type("Object"); //anything goes + select_base_type->connect("create",this,"_change_base_type_callback"); + select_base_type->get_ok()->set_text(TTR("Change")); + add_child(select_base_type); + + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + new_function_menu = memnew( PopupMenu ); + new_function_menu->connect("item_pressed",this,"_override_pressed"); + add_child(new_function_menu); + updating_members=false; + + set_process_input(true); //for revert on drag + set_process_unhandled_input(true); //for revert on drag + + default_value_edit= memnew( CustomPropertyEditor); + add_child(default_value_edit); + default_value_edit->connect("variant_changed",this,"_default_value_changed"); + + method_select = memnew( PropertySelector ); + add_child(method_select); + method_select->connect("selected",this,"_selected_method"); + error_line=-1; + + new_connect_node_select = memnew( PropertySelector ); + add_child(new_connect_node_select); + new_connect_node_select->connect("selected",this,"_selected_connect_node_method_or_setget"); + + port_action_popup = memnew( PopupMenu ); + add_child(port_action_popup); + port_action_popup->connect("item_pressed",this,"_port_action_menu"); + + +} + +VisualScriptEditor::~VisualScriptEditor() { + + undo_redo->clear_history(); //avoid crashes + memdelete(signal_editor); + memdelete(variable_editor); +} + +static ScriptEditorBase * create_editor(const Ref<Script>& p_script) { + + if (p_script->cast_to<VisualScript>()) { + return memnew( VisualScriptEditor ); + } + + return NULL; +} + + +VisualScriptEditor::Clipboard *VisualScriptEditor::clipboard=NULL; + +void VisualScriptEditor::free_clipboard() { + if (clipboard) + memdelete(clipboard); +} + +static void register_editor_callback() { + + ScriptEditor::register_create_script_editor_function(create_editor); + EditorSettings::get_singleton()->set("visual_script_editor/color_functions",Color(1,0.9,0.9)); + EditorSettings::get_singleton()->set("visual_script_editor/color_data",Color(0.9,1.0,0.9)); + EditorSettings::get_singleton()->set("visual_script_editor/color_operators",Color(0.9,0.9,1.0)); + EditorSettings::get_singleton()->set("visual_script_editor/color_flow_control",Color(1.0,1.0,0.8)); + EditorSettings::get_singleton()->set("visual_script_editor/color_custom",Color(0.8,1.0,1.0)); + EditorSettings::get_singleton()->set("visual_script_editor/color_constants",Color(1.0,0.8,1.0)); + + + ED_SHORTCUT("visual_script_editor/delete_selected", TTR("Delete Selected")); + ED_SHORTCUT("visual_script_editor/toggle_breakpoint", TTR("Toggle Breakpoint"), KEY_F9); + ED_SHORTCUT("visual_script_editor/find_node_type", TTR("Find Node Type"), KEY_MASK_CMD+KEY_F); + ED_SHORTCUT("visual_script_editor/copy_nodes", TTR("Copy Nodes"), KEY_MASK_CMD+KEY_C); + ED_SHORTCUT("visual_script_editor/cut_nodes", TTR("Cut Nodes"), KEY_MASK_CMD+KEY_X); + ED_SHORTCUT("visual_script_editor/paste_nodes", TTR("Paste Nodes"), KEY_MASK_CMD+KEY_V); + +} + +void VisualScriptEditor::register_editor() { + + //too early to register stuff here, request a callback + EditorNode::add_plugin_init_callback(register_editor_callback); + + + +} + +#endif diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h new file mode 100644 index 0000000000..5191ed540a --- /dev/null +++ b/modules/visual_script/visual_script_editor.h @@ -0,0 +1,243 @@ +#ifndef VisualSCRIPT_EDITOR_H +#define VisualSCRIPT_EDITOR_H + +#include "tools/editor/plugins/script_editor_plugin.h" +#include "visual_script.h" +#include "tools/editor/property_editor.h" +#include "scene/gui/graph_edit.h" +#include "tools/editor/create_dialog.h" +#include "tools/editor/property_selector.h" +class VisualScriptEditorSignalEdit; +class VisualScriptEditorVariableEdit; + + +#ifdef TOOLS_ENABLED + + +class VisualScriptEditor : public ScriptEditorBase { + OBJ_TYPE(VisualScriptEditor,ScriptEditorBase) + + enum { + TYPE_SEQUENCE=1000, + INDEX_BASE_SEQUENCE=1024 + + + }; + + enum { + EDIT_DELETE_NODES, + EDIT_TOGGLE_BREAKPOINT, + EDIT_FIND_NODE_TYPE, + EDIT_COPY_NODES, + EDIT_CUT_NODES, + EDIT_PASTE_NODES, + }; + + enum PortAction { + + CREATE_CALL, + CREATE_SET, + CREATE_GET, + CREATE_COND, + CREATE_SEQUENCE, + CREATE_SWITCH, + CREATE_ITERATOR, + CREATE_WHILE, + CREATE_RETURN, + }; + + MenuButton *edit_menu; + + Ref<VisualScript> script; + + Button *base_type_select; + + HSplitContainer *main_hsplit; + VSplitContainer *left_vsplit; + + GraphEdit *graph; + + LineEdit *node_filter; + TextureFrame *node_filter_icon; + + VisualScriptEditorSignalEdit *signal_editor; + + AcceptDialog *edit_signal_dialog; + PropertyEditor *edit_signal_edit; + + PropertySelector *method_select; + PropertySelector *new_connect_node_select; + + VisualScriptEditorVariableEdit *variable_editor; + + AcceptDialog *edit_variable_dialog; + PropertyEditor *edit_variable_edit; + + CustomPropertyEditor *default_value_edit; + + UndoRedo *undo_redo; + + Tree *members; + Tree *nodes; + + Label *hint_text; + Timer *hint_text_timer; + + Label *select_func_text; + + bool updating_graph; + + void _show_hint(const String& p_hint); + void _hide_timer(); + + CreateDialog *select_base_type; + + struct VirtualInMenu { + String name; + Variant::Type ret; + bool ret_variant; + Vector< Pair<Variant::Type,String> > args; + }; + + Map<int,VirtualInMenu> virtuals_in_menu; + + PopupMenu *new_function_menu; + + + StringName edited_func; + + void _update_graph_connections(); + void _update_graph(int p_only_id=-1); + + bool updating_members; + + void _update_members(); + + StringName selected; + + String _validate_name(const String& p_name) const; + + struct Clipboard { + + Map<int,Ref<VisualScriptNode> > nodes; + Map<int,Vector2 > nodes_positions; + + Set<VisualScript::SequenceConnection> sequence_connections; + Set<VisualScript::DataConnection> data_connections; + }; + + static Clipboard *clipboard; + + PopupMenu *port_action_popup; + + PortAction port_action; + int port_action_node; + int port_action_output; + Vector2 port_action_pos; + int port_action_new_node; + void _port_action_menu(int p_option); + void _selected_connect_node_method_or_setget(const String& p_text); + + + int error_line; + + void _node_selected(Node* p_node); + void _center_on_node(int p_id); + + void _node_filter_changed(const String& p_text); + void _change_base_type_callback(); + void _change_base_type(); + void _member_selected(); + void _member_edited(); + void _override_pressed(int p_id); + + void _begin_node_move(); + void _end_node_move(); + void _move_node(String func,int p_id,const Vector2& p_to); + + void _node_moved(Vector2 p_from,Vector2 p_to, int p_id); + void _remove_node(int p_id); + void _graph_connected(const String& p_from,int p_from_slot,const String& p_to,int p_to_slot); + void _graph_disconnected(const String& p_from,int p_from_slot,const String& p_to,int p_to_slot); + void _graph_connect_to_empty(const String& p_from,int p_from_slot,const Vector2& p_release_pos); + + void _node_ports_changed(const String& p_func,int p_id); + void _available_node_doubleclicked(); + + void _update_available_nodes(); + + void _member_button(Object *p_item, int p_column, int p_button); + + + + String revert_on_drag; + + void _input(const InputEvent& p_event); + void _on_nodes_delete(); + void _on_nodes_duplicate(); + + Variant get_drag_data_fw(const Point2& p_point,Control* p_from); + bool can_drop_data_fw(const Point2& p_point,const Variant& p_data,Control* p_from) const; + void drop_data_fw(const Point2& p_point,const Variant& p_data,Control* p_from); + + + int editing_id; + int editing_input; + + void _default_value_changed(); + void _default_value_edited(Node * p_button,int p_id,int p_input_port); + + void _menu_option(int p_what); + + void _graph_ofs_changed(const Vector2& p_ofs); + void _comment_node_resized(const Vector2& p_new_size,int p_node); + + int selecting_method_id; + void _selected_method(const String& p_method); + + void _draw_color_over_button(Object* obj,Color p_color); + void _button_resource_previewed(const String& p_path,const Ref<Texture>& p_preview,Variant p_ud); + + VisualScriptNode::TypeGuess _guess_output_type(int p_port_action_node,int p_port_action_output,Set<int> &visited_nodes); + + +protected: + + void _notification(int p_what); + static void _bind_methods(); +public: + + virtual void apply_code(); + virtual Ref<Script> get_edited_script() const; + virtual Vector<String> get_functions(); + virtual void set_edited_script(const Ref<Script>& p_script); + virtual void reload_text(); + virtual String get_name(); + virtual Ref<Texture> get_icon(); + virtual bool is_unsaved(); + virtual Variant get_edit_state(); + virtual void set_edit_state(const Variant& p_state); + virtual void goto_line(int p_line,bool p_with_error=false); + virtual void trim_trailing_whitespace(); + virtual void ensure_focus(); + virtual void tag_saved_version(); + virtual void reload(bool p_soft); + virtual void get_breakpoints(List<int> *p_breakpoints); + virtual bool goto_method(const String& p_method); + virtual void add_callback(const String& p_function,StringArray p_args); + virtual void update_settings(); + virtual void set_debugger_active(bool p_active); + virtual void set_tooltip_request_func(String p_method,Object* p_obj); + virtual Control *get_edit_menu(); + virtual bool can_lose_focus_on_node_selection() { return false; } + + static void register_editor(); + + static void free_clipboard(); + + VisualScriptEditor(); + ~VisualScriptEditor(); +}; +#endif + +#endif // VisualSCRIPT_EDITOR_H diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp new file mode 100644 index 0000000000..b53e81f23f --- /dev/null +++ b/modules/visual_script/visual_script_expression.cpp @@ -0,0 +1,1440 @@ +#include "visual_script_expression.h" + + +bool VisualScriptExpression::_set(const StringName& p_name, const Variant& p_value) { + + if (String(p_name)=="expression") { + expression=p_value; + expression_dirty=true; + ports_changed_notify(); + return true; + } + + if (String(p_name)=="out_type") { + output_type=Variant::Type(int(p_value)); + expression_dirty=true; + ports_changed_notify(); + return true; + } + if (String(p_name)=="sequenced") { + sequenced=p_value; + ports_changed_notify(); + return true; + } + + if (String(p_name)=="input_count") { + + int from=inputs.size(); + inputs.resize(int(p_value)); + for(int i=from;i<inputs.size();i++) { + inputs[i].name=String::chr('a'+i); + if (from==0) { + inputs[i].type=output_type; + } else { + inputs[i].type=inputs[from-1].type; + } + } + expression_dirty=true; + ports_changed_notify(); + _change_notify(); + return true; + } + + if (String(p_name).begins_with("input/")) { + + int idx=String(p_name).get_slice("/",1).to_int(); + ERR_FAIL_INDEX_V(idx,inputs.size(),false); + + String what=String(p_name).get_slice("/",2); + + if (what=="type") { + + inputs[idx].type=Variant::Type(int(p_value)); + } else if (what=="name") { + + inputs[idx].name=p_value; + } else { + return false; + } + + expression_dirty=true; + ports_changed_notify(); + return true; + } + + + return false; + +} + +bool VisualScriptExpression::_get(const StringName& p_name,Variant &r_ret) const { + + if (String(p_name)=="expression") { + r_ret=expression; + return true; + } + + if (String(p_name)=="out_type") { + r_ret=output_type; + return true; + } + + if (String(p_name)=="sequenced") { + r_ret=sequenced; + return true; + } + + if (String(p_name)=="input_count") { + r_ret=inputs.size(); + return true; + } + + if (String(p_name).begins_with("input/")) { + + int idx=String(p_name).get_slice("/",1).to_int(); + ERR_FAIL_INDEX_V(idx,inputs.size(),false); + + String what=String(p_name).get_slice("/",2); + + if (what=="type") { + + r_ret=inputs[idx].type; + } else if (what=="name") { + + r_ret=inputs[idx].name; + } else { + return false; + } + + return true; + } + + + return false; +} +void VisualScriptExpression::_get_property_list( List<PropertyInfo> *p_list) const { + + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + p_list->push_back(PropertyInfo(Variant::STRING,"expression")); + p_list->push_back(PropertyInfo(Variant::INT,"out_type",PROPERTY_HINT_ENUM,argt)); + p_list->push_back(PropertyInfo(Variant::INT,"input_count",PROPERTY_HINT_RANGE,"0,64,1")); + p_list->push_back(PropertyInfo(Variant::BOOL,"sequenced")); + + for(int i=0;i<inputs.size();i++) { + + p_list->push_back(PropertyInfo(Variant::INT,"input/"+itos(i)+"/type",PROPERTY_HINT_ENUM,argt)); + p_list->push_back(PropertyInfo(Variant::STRING,"input/"+itos(i)+"/name")); + } +} + +int VisualScriptExpression::get_output_sequence_port_count() const { + + return sequenced?1:0; +} +bool VisualScriptExpression::has_input_sequence_port() const{ + + return sequenced; +} + + +String VisualScriptExpression::get_output_sequence_port_text(int p_port) const{ + + return String(); +} + + +int VisualScriptExpression::get_input_value_port_count() const{ + + return inputs.size(); + +} +int VisualScriptExpression::get_output_value_port_count() const{ + + return 1; +} + + +PropertyInfo VisualScriptExpression::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(inputs[p_idx].type,inputs[p_idx].name); +} +PropertyInfo VisualScriptExpression::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(output_type,"result"); +} + +String VisualScriptExpression::get_caption() const{ + + return "Expression"; +} +String VisualScriptExpression::get_text() const{ + + return expression; +} + + +Error VisualScriptExpression::_get_token(Token& r_token) { + + while (true) { +#define GET_CHAR() (str_ofs>=expression.length()?0:expression[str_ofs++]) + + CharType cchar = GET_CHAR(); + if (cchar==0) { + r_token.type=TK_EOF; + return OK; + } + + + switch(cchar) { + + case 0: { + r_token.type=TK_EOF; + return OK; + } break; + case '{': { + + r_token.type=TK_CURLY_BRACKET_OPEN; + return OK; + }; + case '}': { + + r_token.type=TK_CURLY_BRACKET_CLOSE; + return OK; + }; + case '[': { + + r_token.type=TK_BRACKET_OPEN; + return OK; + }; + case ']': { + + r_token.type=TK_BRACKET_CLOSE; + return OK; + }; + case '(': { + + r_token.type=TK_PARENTHESIS_OPEN; + return OK; + }; + case ')': { + + r_token.type=TK_PARENTHESIS_CLOSE; + return OK; + }; + case ',': { + + r_token.type=TK_COMMA; + return OK; + }; + case ':': { + + r_token.type=TK_COLON; + return OK; + }; + case '.': { + + r_token.type=TK_PERIOD; + return OK; + }; + case '=': { + + cchar=GET_CHAR(); + if (cchar=='=') { + r_token.type=TK_OP_EQUAL; + } else { + _set_error("Expected '='"); + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; + } + return OK; + }; + case '!': { + + if (expression[str_ofs]=='=') { + r_token.type=TK_OP_NOT_EQUAL; + str_ofs++; + } else { + r_token.type=TK_OP_NOT; + } + return OK; + }; + case '>': { + + if (expression[str_ofs]=='=') { + r_token.type=TK_OP_GREATER_EQUAL; + str_ofs++; + } else if (expression[str_ofs]=='>') { + r_token.type=TK_OP_SHIFT_RIGHT; + str_ofs++; + } else { + r_token.type=TK_OP_GREATER; + } + return OK; + }; + case '<': { + + if (expression[str_ofs]=='=') { + r_token.type=TK_OP_LESS_EQUAL; + str_ofs++; + } else if (expression[str_ofs]=='<') { + r_token.type=TK_OP_SHIFT_LEFT; + str_ofs++; + } else { + r_token.type=TK_OP_LESS; + } + return OK; + }; + case '+': { + r_token.type=TK_OP_ADD; + return OK; + }; + case '-': { + r_token.type=TK_OP_SUB; + return OK; + }; + case '/': { + r_token.type=TK_OP_DIV; + return OK; + }; + case '*': { + r_token.type=TK_OP_MUL; + return OK; + }; + case '%': { + r_token.type=TK_OP_MOD; + return OK; + }; + case '&': { + + if (expression[str_ofs]=='&') { + r_token.type=TK_OP_AND; + str_ofs++; + } else { + r_token.type=TK_OP_BIT_AND; + } + return OK; + }; + case '|': { + + if (expression[str_ofs]=='|') { + r_token.type=TK_OP_OR; + str_ofs++; + } else { + r_token.type=TK_OP_BIT_OR; + } + return OK; + }; + case '^': { + + r_token.type=TK_OP_BIT_XOR; + + return OK; + }; + case '~': { + + r_token.type=TK_OP_BIT_INVERT; + + return OK; + }; + case '"': { + + + String str; + while(true) { + + CharType ch=GET_CHAR(); + + if (ch==0) { + _set_error("Unterminated String"); + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; + } else if (ch=='"') { + break; + } else if (ch=='\\') { + //escaped characters... + + CharType next = GET_CHAR(); + if (next==0) { + _set_error("Unterminated String"); + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; + } + CharType res=0; + + switch(next) { + + case 'b': res=8; break; + case 't': res=9; break; + case 'n': res=10; break; + case 'f': res=12; break; + case 'r': res=13; break; + case 'u': { + //hexnumbarh - oct is deprecated + + + for(int j=0;j<4;j++) { + CharType c = GET_CHAR(); + + if (c==0) { + _set_error("Unterminated String"); + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; + } + if (!((c>='0' && c<='9') || (c>='a' && c<='f') || (c>='A' && c<='F'))) { + + _set_error("Malformed hex constant in string"); + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; + } + CharType v; + if (c>='0' && c<='9') { + v=c-'0'; + } else if (c>='a' && c<='f') { + v=c-'a'; + v+=10; + } else if (c>='A' && c<='F') { + v=c-'A'; + v+=10; + } else { + ERR_PRINT("BUG"); + v=0; + } + + res<<=4; + res|=v; + + + } + + + + } break; + //case '\"': res='\"'; break; + //case '\\': res='\\'; break; + //case '/': res='/'; break; + default: { + res = next; + //r_err_str="Invalid escape sequence"; + //return ERR_PARSE_ERROR; + } break; + } + + str+=res; + + } else { + str+=ch; + } + } + + r_token.type=TK_CONSTANT; + r_token.value=str; + return OK; + + } break; + default: { + + if (cchar<=32) { + break; + } + + if (cchar=='-' || (cchar>='0' && cchar<='9')) { + //a number + + + String num; +#define READING_SIGN 0 +#define READING_INT 1 +#define READING_DEC 2 +#define READING_EXP 3 +#define READING_DONE 4 + int reading=READING_INT; + + if (cchar=='-') { + num+='-'; + cchar=GET_CHAR(); + + } + + + + CharType c = cchar; + bool exp_sign=false; + bool exp_beg=false; + bool is_float=false; + + while(true) { + + switch(reading) { + case READING_INT: { + + if (c>='0' && c<='9') { + //pass + } else if (c=='.') { + reading=READING_DEC; + is_float=true; + } else if (c=='e') { + reading=READING_EXP; + } else { + reading=READING_DONE; + } + + } break; + case READING_DEC: { + + if (c>='0' && c<='9') { + + } else if (c=='e') { + reading=READING_EXP; + + } else { + reading=READING_DONE; + } + + } break; + case READING_EXP: { + + if (c>='0' && c<='9') { + exp_beg=true; + + } else if ((c=='-' || c=='+') && !exp_sign && !exp_beg) { + if (c=='-') + is_float=true; + exp_sign=true; + + } else { + reading=READING_DONE; + } + } break; + } + + if (reading==READING_DONE) + break; + num+=String::chr(c); + c = GET_CHAR(); + + + } + + str_ofs--; + + r_token.type=TK_CONSTANT; + + if (is_float) + r_token.value=num.to_double(); + else + r_token.value=num.to_int(); + return OK; + + } else if ((cchar>='A' && cchar<='Z') || (cchar>='a' && cchar<='z') || cchar=='_') { + + String id; + bool first=true; + + while((cchar>='A' && cchar<='Z') || (cchar>='a' && cchar<='z') || cchar=='_' || (!first && cchar>='0' && cchar<='9')) { + + id+=String::chr(cchar); + cchar=GET_CHAR(); + first=false; + } + + str_ofs--; //go back one + + if (id=="in") { + r_token.type=TK_OP_IN; + } else if (id=="null") { + r_token.type=TK_CONSTANT; + r_token.value=Variant(); + } else if (id=="true") { + r_token.type=TK_CONSTANT; + r_token.value=true; + } else if (id=="false") { + r_token.type=TK_CONSTANT; + r_token.value=false; + } else if (id=="PI") { + r_token.type=TK_CONSTANT; + r_token.value=Math_PI; + } else if (id=="not") { + r_token.type=TK_OP_NOT; + } else if (id=="or") { + r_token.type=TK_OP_OR; + } else if (id=="and") { + r_token.type=TK_OP_AND; + } else if (id=="self") { + r_token.type=TK_SELF; + } else { + + for(int i=0;i<Variant::VARIANT_MAX;i++) { + if (id==Variant::get_type_name(Variant::Type(i))) { + r_token.type=TK_BASIC_TYPE; + r_token.value=i; + return OK; + break; + } + } + + r_token.type=TK_IDENTIFIER; + r_token.value=id; + } + + return OK; + } else { + _set_error("Unexpected character."); + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; + } + } + } + } + + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; +} + +const char* VisualScriptExpression::token_name[TK_MAX]={ +"CURLY BRACKET OPEN", +"CURLY BRACKET CLOSE", +"BRACKET OPEN", +"BRACKET CLOSE", +"PARENTHESIS OPEN", +"PARENTHESIS CLOSE", +"IDENTIFIER", +"SELF", +"CONSTANT", +"BASIC TYPE", +"COLON", +"COMMA", +"PERIOD", +"OP IN", +"OP EQUAL", +"OP NOT EQUAL", +"OP LESS", +"OP LESS EQUAL", +"OP GREATER", +"OP GREATER EQUAL", +"OP AND", +"OP OR", +"OP NOT", +"OP ADD", +"OP SUB", +"OP MUL", +"OP DIV", +"OP MOD", +"OP SHIFT LEFT", +"OP SHIFT RIGHT", +"OP BIT AND", +"OP BIT OR", +"OP BIT XOR", +"OP BIT INVERT", +"EOF", +"ERROR" +}; + +VisualScriptExpression::ENode* VisualScriptExpression::_parse_expression() { + + + Vector<Expression> expression; + + while(true) { + //keep appending stuff to expression + ENode*expr=NULL; + + Token tk; + _get_token(tk); + if (error_set) + return NULL; + + + + switch(tk.type) { + case TK_CURLY_BRACKET_OPEN: { + //a dictionary + DictionaryNode *dn = alloc_node<DictionaryNode>(); + + + while(true) { + + int cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_CURLY_BRACKET_CLOSE) { + break; + } + str_ofs=cofs; //revert + //parse an expression + ENode* expr=_parse_expression(); + if (!expr) + return NULL; + dn->dict.push_back(expr); + + _get_token(tk); + if (tk.type!=TK_COLON) { + _set_error("Expected ':'"); + return NULL; + } + + expr=_parse_expression(); + if (!expr) + return NULL; + + dn->dict.push_back(expr); + + cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_COMMA) { + //all good + } else if (tk.type==TK_CURLY_BRACKET_CLOSE) { + str_ofs=cofs; + } else { + _set_error("Expected ',' or '}'"); + } + } + + expr=dn; + } break; + case TK_BRACKET_OPEN: { + //an array + + ArrayNode *an = alloc_node<ArrayNode>(); + + + while(true) { + + int cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_BRACKET_CLOSE) { + break; + } + str_ofs=cofs; //revert + //parse an expression + ENode* expr=_parse_expression(); + if (!expr) + return NULL; + an->array.push_back(expr); + + cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_COMMA) { + //all good + } else if (tk.type==TK_BRACKET_CLOSE) { + str_ofs=cofs; + } else { + _set_error("Expected ',' or ']'"); + } + } + + expr=an; + } break; + case TK_PARENTHESIS_OPEN: { + //a suexpression + ENode* e=_parse_expression(); + if (error_set) + return NULL; + _get_token(tk); + if (tk.type!=TK_PARENTHESIS_CLOSE) { + _set_error("Expected ')'"); + return NULL; + } + + expr=e; + + } break; + case TK_IDENTIFIER: { + + String what = tk.value; + int index=-1; + for(int i=0;i<inputs.size();i++) { + if (what==inputs[i].name) { + index=i; + break; + } + } + + if (index!=-1) { + InputNode *input = alloc_node<InputNode>(); + input->index=index; + expr=input; + } else { + _set_error("Invalid input identifier '"+what+"'. For script variables, use self (locals are for inputs)."+what); + return NULL; + } + } break; + case TK_SELF: { + + SelfNode *self = alloc_node<SelfNode>(); + expr=self; + } break; + case TK_CONSTANT: { + ConstantNode *constant = alloc_node<ConstantNode>(); + constant->value=tk.value; + expr=constant; + } break; + case TK_BASIC_TYPE: { + //constructor.. + + Variant::Type bt = Variant::Type(int(tk.value)); + _get_token(tk); + if (tk.type!=TK_PARENTHESIS_OPEN) { + _set_error("Expected '('"); + return NULL; + } + + ConstructorNode *constructor = alloc_node<ConstructorNode>(); + constructor->data_type=bt; + + while(true) { + + int cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_PARENTHESIS_CLOSE) { + break; + } + str_ofs=cofs; //revert + //parse an expression + ENode* expr=_parse_expression(); + if (!expr) + return NULL; + + constructor->arguments.push_back(expr); + + cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_COMMA) { + //all good + } else if (tk.type==TK_PARENTHESIS_CLOSE) { + str_ofs=cofs; + } else { + _set_error("Expected ',' or ')'"); + } + } + + expr=constructor; + + } break; + case TK_OP_SUB: { + + Expression e; + e.is_op=true; + e.op=Variant::OP_NEGATE; + expression.push_back(e); + continue; + } break; + case TK_OP_NOT: { + + Expression e; + e.is_op=true; + e.op=Variant::OP_NOT; + expression.push_back(e); + continue; + } break; + + default: { + _set_error("Expected expression."); + return NULL; + } break; + + } + + //before going to operators, must check indexing! + + while(true) { + int cofs2=str_ofs; + _get_token(tk); + if (error_set) + return NULL; + + bool done=false; + + switch(tk.type) { + case TK_BRACKET_OPEN: { + //value indexing + + IndexNode *index = alloc_node<IndexNode>(); + index->base=expr; + + ENode* what=_parse_expression(); + if (!what) + return NULL; + + index->index=what; + + _get_token(tk); + if (tk.type!=TK_BRACKET_CLOSE) { + _set_error("Expected ']' at end of index."); + return NULL; + } + expr=index; + + } break; + case TK_PERIOD: { + //named indexing or function call + _get_token(tk); + if (tk.type!=TK_IDENTIFIER) { + _set_error("Expected identifier after '.'"); + return NULL; + } + + StringName identifier=tk.value; + + int cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_PARENTHESIS_OPEN) { + //function call + CallNode *func_call = alloc_node<CallNode>(); + func_call->method=identifier; + func_call->base=expr; + + while(true) { + + int cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_PARENTHESIS_CLOSE) { + break; + } + str_ofs=cofs; //revert + //parse an expression + ENode* expr=_parse_expression(); + if (!expr) + return NULL; + + func_call->arguments.push_back(expr); + + cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_COMMA) { + //all good + } else if (tk.type==TK_PARENTHESIS_CLOSE) { + str_ofs=cofs; + } else { + _set_error("Expected ',' or ')'"); + } + } + + expr=func_call; + } else { + //named indexing + str_ofs=cofs; + + NamedIndexNode *index = alloc_node<NamedIndexNode>(); + index->base=expr; + index->name=identifier; + expr=index; + + } + + } break; + default: { + str_ofs=cofs2; + done=true; + } break; + } + + if (done) + break; + } + + //push expression + { + Expression e; + e.is_op=false; + e.node=expr; + expression.push_back(e); + } + + //ok finally look for an operator + + + int cofs=str_ofs; + _get_token(tk); + if (error_set) + return NULL; + + + Variant::Operator op = Variant::OP_MAX; + + switch(tk.type) { + case TK_OP_IN: op=Variant::OP_IN; break; + case TK_OP_EQUAL: op=Variant::OP_EQUAL; break; + case TK_OP_NOT_EQUAL: op=Variant::OP_NOT_EQUAL; break; + case TK_OP_LESS: op=Variant::OP_LESS; break; + case TK_OP_LESS_EQUAL: op=Variant::OP_LESS_EQUAL; break; + case TK_OP_GREATER: op=Variant::OP_GREATER; break; + case TK_OP_GREATER_EQUAL: op=Variant::OP_GREATER_EQUAL; break; + case TK_OP_AND: op=Variant::OP_AND; break; + case TK_OP_OR: op=Variant::OP_OR; break; + case TK_OP_NOT: op=Variant::OP_NOT; break; + case TK_OP_ADD: op=Variant::OP_ADD; break; + case TK_OP_SUB: op=Variant::OP_SUBSTRACT; break; + case TK_OP_MUL: op=Variant::OP_MULTIPLY; break; + case TK_OP_DIV: op=Variant::OP_DIVIDE; break; + case TK_OP_MOD: op=Variant::OP_MODULE; break; + case TK_OP_SHIFT_LEFT: op=Variant::OP_SHIFT_LEFT; break; + case TK_OP_SHIFT_RIGHT: op=Variant::OP_SHIFT_RIGHT; break; + case TK_OP_BIT_AND: op=Variant::OP_BIT_AND; break; + case TK_OP_BIT_OR: op=Variant::OP_BIT_OR; break; + case TK_OP_BIT_XOR: op=Variant::OP_BIT_XOR; break; + case TK_OP_BIT_INVERT: op=Variant::OP_BIT_NEGATE; break; + default: {}; + } + + if (op==Variant::OP_MAX) { //stop appending stuff + str_ofs=cofs; + break; + } + + //push operator and go on + { + Expression e; + e.is_op=true; + e.op=op; + expression.push_back(e); + } + } + + + /* Reduce the set set of expressions and place them in an operator tree, respecting precedence */ + + + while(expression.size()>1) { + + int next_op=-1; + int min_priority=0xFFFFF; + bool is_unary=false; + + for(int i=0;i<expression.size();i++) { + + + + if (!expression[i].is_op) { + + continue; + } + + int priority; + + bool unary=false; + + switch(expression[i].op) { + + + case Variant::OP_BIT_NEGATE: priority=0; unary=true; break; + case Variant::OP_NEGATE: priority=1; unary=true; break; + + case Variant::OP_MULTIPLY: priority=2; break; + case Variant::OP_DIVIDE: priority=2; break; + case Variant::OP_MODULE: priority=2; break; + + case Variant::OP_ADD: priority=3; break; + case Variant::OP_SUBSTRACT: priority=3; break; + + case Variant::OP_SHIFT_LEFT: priority=4; break; + case Variant::OP_SHIFT_RIGHT: priority=4; break; + + case Variant::OP_BIT_AND: priority=5; break; + case Variant::OP_BIT_XOR: priority=6; break; + case Variant::OP_BIT_OR: priority=7; break; + + case Variant::OP_LESS: priority=8; break; + case Variant::OP_LESS_EQUAL: priority=8; break; + case Variant::OP_GREATER: priority=8; break; + case Variant::OP_GREATER_EQUAL: priority=8; break; + + case Variant::OP_EQUAL: priority=8; break; + case Variant::OP_NOT_EQUAL: priority=8; break; + + case Variant::OP_IN: priority=10; break; + + case Variant::OP_NOT: priority=11; unary=true; break; + case Variant::OP_AND: priority=12; break; + case Variant::OP_OR: priority=13; break; + + + default: { + _set_error("Parser bug, invalid operator in expression: "+itos(expression[i].op)); + return NULL; + } + + } + + if (priority<min_priority) { + // < is used for left to right (default) + // <= is used for right to left + + next_op=i; + min_priority=priority; + is_unary=unary; + } + + } + + if (next_op==-1) { + + + _set_error("Yet another parser bug...."); + ERR_FAIL_COND_V(next_op==-1,NULL); + } + + + // OK! create operator.. + if (is_unary) { + + int expr_pos=next_op; + while(expression[expr_pos].is_op) { + + expr_pos++; + if (expr_pos==expression.size()) { + //can happen.. + _set_error("Unexpected end of expression.."); + return NULL; + } + } + + //consecutively do unary opeators + for(int i=expr_pos-1;i>=next_op;i--) { + + OperatorNode *op = alloc_node<OperatorNode>(); + op->op=expression[i].op; + op->nodes[0]=expression[i+1].node; + op->nodes[1]=NULL; + expression[i].is_op=false; + expression[i].node=op; + expression.remove(i+1); + } + + + } else { + + if (next_op <1 || next_op>=(expression.size()-1)) { + _set_error("Parser bug.."); + ERR_FAIL_V(NULL); + } + + OperatorNode *op = alloc_node<OperatorNode>(); + op->op=expression[next_op].op; + + if (expression[next_op-1].is_op) { + + _set_error("Parser bug.."); + ERR_FAIL_V(NULL); + } + + if (expression[next_op+1].is_op) { + // this is not invalid and can really appear + // but it becomes invalid anyway because no binary op + // can be followed by an unary op in a valid combination, + // due to how precedence works, unaries will always dissapear first + + _set_error("Unexpected two consecutive operators."); + return NULL; + } + + + op->nodes[0]=expression[next_op-1].node; //expression goes as left + op->nodes[1]=expression[next_op+1].node; //next expression goes as right + + //replace all 3 nodes by this operator and make it an expression + expression[next_op-1].node=op; + expression.remove(next_op); + expression.remove(next_op); + } + } + + return expression[0].node; +} + +bool VisualScriptExpression::_compile_expression() { + + if (!expression_dirty) + return error_set; + + if (nodes) { + memdelete(nodes); + nodes=NULL; + root=NULL; + + } + + error_str=String(); + error_set=false; + str_ofs=0; + + root=_parse_expression(); + + if (error_set) { + root=NULL; + if (nodes) { + memdelete(nodes); + } + nodes=NULL; + return true; + } + + expression_dirty=false; + return false; +} + + +class VisualScriptNodeInstanceExpression : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + VisualScriptExpression *expression; + + //virtual int get_working_memory_size() const { return 0; } + //execute by parsing the tree directly + virtual bool _execute(const Variant** p_inputs,VisualScriptExpression::ENode *p_node,Variant& r_ret,String& r_error_str,Variant::CallError &ce) { + + switch(p_node->type) { + case VisualScriptExpression::ENode::TYPE_INPUT: { + + const VisualScriptExpression::InputNode *in = static_cast<const VisualScriptExpression::InputNode*>(p_node); + r_ret=*p_inputs[in->index]; + } break; + case VisualScriptExpression::ENode::TYPE_CONSTANT: { + + const VisualScriptExpression::ConstantNode *c = static_cast<const VisualScriptExpression::ConstantNode*>(p_node); + r_ret=c->value; + + } break; + case VisualScriptExpression::ENode::TYPE_SELF: { + + r_ret=instance->get_owner_ptr(); + } break; + case VisualScriptExpression::ENode::TYPE_OPERATOR: { + + + const VisualScriptExpression::OperatorNode *op = static_cast<const VisualScriptExpression::OperatorNode*>(p_node); + + Variant a; + bool ret = _execute(p_inputs,op->nodes[0],a,r_error_str,ce); + if (ret) + return true; + + Variant b; + + if (op->nodes[1]) { + ret = _execute(p_inputs,op->nodes[1],b,r_error_str,ce); + if (ret) + return true; + } + + bool valid=true; + Variant::evaluate(op->op,a,b,r_ret,valid); + if (!valid) { + r_error_str="Invalid operands to operator "+Variant::get_operator_name(op->op)+": "+Variant::get_type_name(a.get_type())+" and "+Variant::get_type_name(b.get_type())+"."; + return true; + } + + } break; + case VisualScriptExpression::ENode::TYPE_INDEX: { + + const VisualScriptExpression::IndexNode *index = static_cast<const VisualScriptExpression::IndexNode*>(p_node); + + Variant base; + bool ret = _execute(p_inputs,index->base,base,r_error_str,ce); + if (ret) + return true; + + Variant idx; + + ret = _execute(p_inputs,index->index,idx,r_error_str,ce); + if (ret) + return true; + + bool valid; + r_ret=base.get(idx,&valid); + if (!valid) { + r_error_str="Invalid index of type "+Variant::get_type_name(idx.get_type())+" for base of type "+Variant::get_type_name(base.get_type())+"."; + return true; + } + + + + } break; + case VisualScriptExpression::ENode::TYPE_NAMED_INDEX: { + + const VisualScriptExpression::NamedIndexNode *index = static_cast<const VisualScriptExpression::NamedIndexNode*>(p_node); + + Variant base; + bool ret = _execute(p_inputs,index->base,base,r_error_str,ce); + if (ret) + return true; + + bool valid; + r_ret=base.get_named(index->name,&valid); + if (!valid) { + r_error_str="Invalid index '"+String(index->name)+"' for base of type "+Variant::get_type_name(base.get_type())+"."; + return true; + } + + } break; + case VisualScriptExpression::ENode::TYPE_ARRAY: { + const VisualScriptExpression::ArrayNode *array = static_cast<const VisualScriptExpression::ArrayNode*>(p_node); + + Array arr; + arr.resize(array->array.size()); + for (int i=0;i<array->array.size();i++) { + + Variant value; + bool ret = _execute(p_inputs,array->array[i],value,r_error_str,ce); + if (ret) + return true; + arr[i]=value; + } + + r_ret=arr; + + } break; + case VisualScriptExpression::ENode::TYPE_DICTIONARY: { + const VisualScriptExpression::DictionaryNode *dictionary = static_cast<const VisualScriptExpression::DictionaryNode*>(p_node); + + Dictionary d; + for (int i=0;i<dictionary->dict.size();i+=2) { + + Variant key; + bool ret = _execute(p_inputs,dictionary->dict[i+0],key,r_error_str,ce); + if (ret) + return true; + + Variant value; + ret = _execute(p_inputs,dictionary->dict[i+1],value,r_error_str,ce); + if (ret) + return true; + + d[key]=value; + } + + r_ret=d; + } break; + case VisualScriptExpression::ENode::TYPE_CONSTRUCTOR: { + + const VisualScriptExpression::ConstructorNode *constructor = static_cast<const VisualScriptExpression::ConstructorNode*>(p_node); + + Vector<Variant> arr; + Vector<const Variant*> argp; + arr.resize(constructor->arguments.size()); + argp.resize(constructor->arguments.size()); + + for (int i=0;i<constructor->arguments.size();i++) { + + Variant value; + bool ret = _execute(p_inputs,constructor->arguments[i],value,r_error_str,ce); + if (ret) + return true; + arr[i]=value; + argp[i]=&arr[i]; + } + + + r_ret=Variant::construct(constructor->data_type,argp.ptr(),argp.size(),ce); + + if (ce.error!=Variant::CallError::CALL_OK) { + r_error_str="Invalid arguments to construct '"+Variant::get_type_name(constructor->data_type)+"'."; + return true; + } + + + } break; + case VisualScriptExpression::ENode::TYPE_CALL: { + + const VisualScriptExpression::CallNode *call = static_cast<const VisualScriptExpression::CallNode*>(p_node); + + + Variant base; + bool ret = _execute(p_inputs,call->base,base,r_error_str,ce); + if (ret) + return true; + + Vector<Variant> arr; + Vector<const Variant*> argp; + arr.resize(call->arguments.size()); + argp.resize(call->arguments.size()); + + for (int i=0;i<call->arguments.size();i++) { + + Variant value; + bool ret = _execute(p_inputs,call->arguments[i],value,r_error_str,ce); + if (ret) + return true; + arr[i]=value; + argp[i]=&arr[i]; + } + + + r_ret=base.call(call->method,argp.ptr(),argp.size(),ce); + + if (ce.error!=Variant::CallError::CALL_OK) { + r_error_str="On call to '"+String(call->method)+"':"; + return true; + } + + } break; + } + return false; + } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (!expression->root || expression->error_set) { + r_error_str=expression->error_str; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } + + + bool error = _execute(p_inputs,expression->root,*p_outputs[0],r_error_str,r_error); + if (error && r_error.error==Variant::CallError::CALL_OK) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + } + +#ifdef DEBUG_ENABLED + if (!error && expression->output_type!=Variant::NIL && !Variant::can_convert_strict(p_outputs[0]->get_type(),expression->output_type)) { + + r_error_str+="Can't convert expression result from "+Variant::get_type_name(p_outputs[0]->get_type())+" to "+Variant::get_type_name(expression->output_type)+"."; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + + } +#endif + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptExpression::instance(VisualScriptInstance* p_instance){ + + _compile_expression(); + VisualScriptNodeInstanceExpression *instance = memnew( VisualScriptNodeInstanceExpression ); + instance->instance=p_instance; + instance->expression=this; + return instance; +} + + +VisualScriptExpression::VisualScriptExpression() +{ + output_type=Variant::NIL; + expression_dirty=true; + error_set=true; + root=NULL; + nodes=NULL; + sequenced=false; +} + +VisualScriptExpression::~VisualScriptExpression() { + + if (nodes) { + memdelete(nodes); + } +} + + +void register_visual_script_expression_node() { + + VisualScriptLanguage::singleton->add_register_func("operators/expression",create_node_generic<VisualScriptExpression>); + +} diff --git a/modules/visual_script/visual_script_expression.h b/modules/visual_script/visual_script_expression.h new file mode 100644 index 0000000000..90b955b5da --- /dev/null +++ b/modules/visual_script/visual_script_expression.h @@ -0,0 +1,269 @@ +#ifndef VISUALSCRIPTEXPRESSION_H +#define VISUALSCRIPTEXPRESSION_H + +#include "visual_script.h" + +class VisualScriptExpression : public VisualScriptNode { + + OBJ_TYPE(VisualScriptExpression,VisualScriptNode) +friend class VisualScriptNodeInstanceExpression; + + struct Input { + + Variant::Type type; + String name; + + Input() { type=Variant::NIL; } + }; + + Vector<Input> inputs; + Variant::Type output_type; + + String expression; + + bool sequenced; + int str_ofs; + bool expression_dirty; + + bool _compile_expression(); + + enum TokenType { + TK_CURLY_BRACKET_OPEN, + TK_CURLY_BRACKET_CLOSE, + TK_BRACKET_OPEN, + TK_BRACKET_CLOSE, + TK_PARENTHESIS_OPEN, + TK_PARENTHESIS_CLOSE, + TK_IDENTIFIER, + TK_SELF, + TK_CONSTANT, + TK_BASIC_TYPE, + TK_COLON, + TK_COMMA, + TK_PERIOD, + TK_OP_IN, + TK_OP_EQUAL, + TK_OP_NOT_EQUAL, + TK_OP_LESS, + TK_OP_LESS_EQUAL, + TK_OP_GREATER, + TK_OP_GREATER_EQUAL, + TK_OP_AND, + TK_OP_OR, + TK_OP_NOT, + TK_OP_ADD, + TK_OP_SUB, + TK_OP_MUL, + TK_OP_DIV, + TK_OP_MOD, + TK_OP_SHIFT_LEFT, + TK_OP_SHIFT_RIGHT, + TK_OP_BIT_AND, + TK_OP_BIT_OR, + TK_OP_BIT_XOR, + TK_OP_BIT_INVERT, + TK_EOF, + TK_ERROR, + TK_MAX + }; + + static const char* token_name[TK_MAX]; + struct Token { + + TokenType type; + Variant value; + }; + + + void _set_error(const String& p_err) { + if (error_set) + return; + error_str=p_err; + error_set=true; + } + + Error _get_token(Token& r_token); + + String error_str; + bool error_set; + + + + struct ENode { + + enum Type { + TYPE_INPUT, + TYPE_CONSTANT, + TYPE_SELF, + TYPE_OPERATOR, + TYPE_INDEX, + TYPE_NAMED_INDEX, + TYPE_ARRAY, + TYPE_DICTIONARY, + TYPE_CONSTRUCTOR, + TYPE_CALL + }; + + ENode *next; + + Type type; + + ENode() { next=NULL; } + virtual ~ENode() { if (next) { memdelete(next); } } + }; + + struct Expression { + + bool is_op; + union { + Variant::Operator op; + ENode *node; + }; + }; + + ENode* _parse_expression(); + + struct InputNode : public ENode { + + int index; + InputNode() { + type=TYPE_INPUT; + } + }; + + + struct ConstantNode : public ENode { + + Variant value; + ConstantNode() { + type=TYPE_CONSTANT; + } + }; + + struct OperatorNode : public ENode { + + Variant::Operator op; + + ENode* nodes[2]; + + OperatorNode() { + type=TYPE_OPERATOR; + } + }; + + struct SelfNode : public ENode { + + + SelfNode() { + type=TYPE_SELF; + } + }; + + struct IndexNode : public ENode { + ENode*base; + ENode*index; + + IndexNode() { + type=TYPE_INDEX; + } + }; + + struct NamedIndexNode : public ENode { + ENode*base; + StringName name; + + NamedIndexNode() { + type=TYPE_NAMED_INDEX; + } + + }; + + struct ConstructorNode : public ENode { + Variant::Type data_type; + Vector<ENode*> arguments; + + ConstructorNode() { + type=TYPE_CONSTRUCTOR; + } + }; + + struct CallNode : public ENode { + ENode*base; + StringName method; + Vector<ENode*> arguments; + + CallNode() { + type=TYPE_CALL; + } + + }; + + struct ArrayNode : public ENode { + Vector<ENode*> array; + ArrayNode() { + type=TYPE_ARRAY; + } + + }; + + struct DictionaryNode : public ENode { + Vector<ENode*> dict; + DictionaryNode() { + type=TYPE_DICTIONARY; + } + + }; + + template<class T> + T* alloc_node() { + T* node = memnew(T); + node->next=nodes; + nodes=node; + return node; + } + + ENode *root; + ENode *nodes; + + + + + +protected: + + bool _set(const StringName& p_name, const Variant& p_value); + bool _get(const StringName& p_name,Variant &r_ret) const; + void _get_property_list( List<PropertyInfo> *p_list) const; + +public: + + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "operators"; } + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptExpression(); + ~VisualScriptExpression(); +}; + + +void register_visual_script_expression_node(); + + +#endif // VISUALSCRIPTEXPRESSION_H diff --git a/modules/visual_script/visual_script_flow_control.cpp b/modules/visual_script/visual_script_flow_control.cpp new file mode 100644 index 0000000000..97338da187 --- /dev/null +++ b/modules/visual_script/visual_script_flow_control.cpp @@ -0,0 +1,1940 @@ +#include "visual_script_flow_control.h" +#include "os/keyboard.h" +#include "globals.h" + + +////////////////////////////////////////// +////////////////RETURN//////////////////// +////////////////////////////////////////// + +int VisualScriptReturn::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptReturn::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptReturn::get_input_value_port_count() const{ + + return with_value?1:0; +} +int VisualScriptReturn::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptReturn::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptReturn::get_input_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="result"; + pinfo.type=type; + return pinfo; +} +PropertyInfo VisualScriptReturn::get_output_value_port_info(int p_idx) const{ + return PropertyInfo(); +} + +String VisualScriptReturn::get_caption() const { + + return "Return"; +} + +String VisualScriptReturn::get_text() const { + + return get_name(); +} + +void VisualScriptReturn::set_return_type(Variant::Type p_type) { + + if (type==p_type) + return; + type=p_type; + ports_changed_notify(); + +} + +Variant::Type VisualScriptReturn::get_return_type() const{ + + return type; +} + +void VisualScriptReturn::set_enable_return_value(bool p_enable) { + if (with_value==p_enable) + return; + + with_value=p_enable; + ports_changed_notify(); +} + +bool VisualScriptReturn::is_return_value_enabled() const { + + return with_value; +} + +void VisualScriptReturn::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_return_type","type"),&VisualScriptReturn::set_return_type); + ObjectTypeDB::bind_method(_MD("get_return_type"),&VisualScriptReturn::get_return_type); + ObjectTypeDB::bind_method(_MD("set_enable_return_value","enable"),&VisualScriptReturn::set_enable_return_value); + ObjectTypeDB::bind_method(_MD("is_return_value_enabled"),&VisualScriptReturn::is_return_value_enabled); + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + ADD_PROPERTY(PropertyInfo(Variant::BOOL,"return_value/enabled"),_SCS("set_enable_return_value"),_SCS("is_return_value_enabled")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"return_value/type",PROPERTY_HINT_ENUM,argt),_SCS("set_return_type"),_SCS("get_return_type")); + +} + +class VisualScriptNodeInstanceReturn : public VisualScriptNodeInstance { +public: + + VisualScriptReturn *node; + VisualScriptInstance *instance; + bool with_value; + + virtual int get_working_memory_size() const { return 1; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (with_value) { + *p_working_mem = *p_inputs[0]; + } else { + *p_working_mem = Variant(); + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptReturn::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceReturn * instance = memnew(VisualScriptNodeInstanceReturn ); + instance->node=this; + instance->instance=p_instance; + instance->with_value=with_value; + return instance; +} + +VisualScriptReturn::VisualScriptReturn() { + + with_value=false; + type=Variant::NIL; +} + +template<bool with_value> +static Ref<VisualScriptNode> create_return_node(const String& p_name) { + + Ref<VisualScriptReturn> node; + node.instance(); + node->set_enable_return_value(with_value); + return node; +} + + + +////////////////////////////////////////// +////////////////CONDITION///////////////// +////////////////////////////////////////// + +int VisualScriptCondition::get_output_sequence_port_count() const { + + return 3; +} + +bool VisualScriptCondition::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptCondition::get_input_value_port_count() const{ + + return 1; +} +int VisualScriptCondition::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptCondition::get_output_sequence_port_text(int p_port) const { + + if (p_port==0) + return "true"; + else if (p_port==1) + return "false"; + else + return "done"; +} + +PropertyInfo VisualScriptCondition::get_input_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="cond"; + pinfo.type=Variant::BOOL; + return pinfo; +} +PropertyInfo VisualScriptCondition::get_output_value_port_info(int p_idx) const{ + return PropertyInfo(); +} + +String VisualScriptCondition::get_caption() const { + + return "Condition"; +} + +String VisualScriptCondition::get_text() const { + + return "if (cond) is: "; +} + + +void VisualScriptCondition::_bind_methods() { + + + +} + +class VisualScriptNodeInstanceCondition : public VisualScriptNodeInstance { +public: + + VisualScriptCondition *node; + VisualScriptInstance *instance; + + //virtual int get_working_memory_size() const { return 1; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_start_mode==START_MODE_CONTINUE_SEQUENCE) + return 2; + else if (p_inputs[0]->operator bool()) + return 0 | STEP_FLAG_PUSH_STACK_BIT; + else + return 1 | STEP_FLAG_PUSH_STACK_BIT; + } + + +}; + +VisualScriptNodeInstance* VisualScriptCondition::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceCondition * instance = memnew(VisualScriptNodeInstanceCondition ); + instance->node=this; + instance->instance=p_instance; + return instance; +} + +VisualScriptCondition::VisualScriptCondition() { + +} + + + +////////////////////////////////////////// +////////////////WHILE///////////////// +////////////////////////////////////////// + +int VisualScriptWhile::get_output_sequence_port_count() const { + + return 2; +} + +bool VisualScriptWhile::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptWhile::get_input_value_port_count() const{ + + return 1; +} +int VisualScriptWhile::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptWhile::get_output_sequence_port_text(int p_port) const { + + if (p_port==0) + return "repeat"; + else + return "exit"; +} + +PropertyInfo VisualScriptWhile::get_input_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="cond"; + pinfo.type=Variant::BOOL; + return pinfo; +} +PropertyInfo VisualScriptWhile::get_output_value_port_info(int p_idx) const{ + return PropertyInfo(); +} + +String VisualScriptWhile::get_caption() const { + + return "While"; +} + +String VisualScriptWhile::get_text() const { + + return "while (cond): "; +} + + +void VisualScriptWhile::_bind_methods() { + + + +} + +class VisualScriptNodeInstanceWhile : public VisualScriptNodeInstance { +public: + + VisualScriptWhile *node; + VisualScriptInstance *instance; + + //virtual int get_working_memory_size() const { return 1; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + bool keep_going = p_inputs[0]->operator bool(); + + if (keep_going) + return 0|STEP_FLAG_PUSH_STACK_BIT; + else + return 1; + } + + +}; + +VisualScriptNodeInstance* VisualScriptWhile::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceWhile * instance = memnew(VisualScriptNodeInstanceWhile ); + instance->node=this; + instance->instance=p_instance; + return instance; +} +VisualScriptWhile::VisualScriptWhile() { + +} + + + +////////////////////////////////////////// +////////////////ITERATOR///////////////// +////////////////////////////////////////// + +int VisualScriptIterator::get_output_sequence_port_count() const { + + return 2; +} + +bool VisualScriptIterator::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptIterator::get_input_value_port_count() const{ + + return 1; +} +int VisualScriptIterator::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptIterator::get_output_sequence_port_text(int p_port) const { + + if (p_port==0) + return "each"; + else + return "exit"; +} + +PropertyInfo VisualScriptIterator::get_input_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="input"; + pinfo.type=Variant::NIL; + return pinfo; +} +PropertyInfo VisualScriptIterator::get_output_value_port_info(int p_idx) const{ + PropertyInfo pinfo; + pinfo.name="elem"; + pinfo.type=Variant::NIL; + return pinfo; +} +String VisualScriptIterator::get_caption() const { + + return "Iterator"; +} + +String VisualScriptIterator::get_text() const { + + return "for (elem) in (input): "; +} + + +void VisualScriptIterator::_bind_methods() { + + + +} + +class VisualScriptNodeInstanceIterator : public VisualScriptNodeInstance { +public: + + VisualScriptIterator *node; + VisualScriptInstance *instance; + + virtual int get_working_memory_size() const { return 2; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_start_mode==START_MODE_BEGIN_SEQUENCE) { + p_working_mem[0]=*p_inputs[0]; + bool valid; + bool can_iter = p_inputs[0]->iter_init(p_working_mem[1],valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Input type not iterable: ")+Variant::get_type_name(p_inputs[0]->get_type()); + return 0; + } + + if (!can_iter) + return 1; //nothing to iterate + + + *p_outputs[0]=p_working_mem[0].iter_get( p_working_mem[1],valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Iterator became invalid"); + return 0; + } + + + } else { //continue sequence + + bool valid; + bool can_iter = p_working_mem[0].iter_next(p_working_mem[1],valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Iterator became invalid: ")+Variant::get_type_name(p_inputs[0]->get_type()); + return 0; + } + + if (!can_iter) + return 1; //nothing to iterate + + + *p_outputs[0]=p_working_mem[0].iter_get( p_working_mem[1],valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Iterator became invalid"); + return 0; + } + + } + + return 0|STEP_FLAG_PUSH_STACK_BIT; //go around + } + + +}; + +VisualScriptNodeInstance* VisualScriptIterator::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceIterator * instance = memnew(VisualScriptNodeInstanceIterator ); + instance->node=this; + instance->instance=p_instance; + return instance; +} + +VisualScriptIterator::VisualScriptIterator() { + +} + + + +////////////////////////////////////////// +////////////////SEQUENCE///////////////// +////////////////////////////////////////// + +int VisualScriptSequence::get_output_sequence_port_count() const { + + return steps; +} + +bool VisualScriptSequence::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptSequence::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptSequence::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptSequence::get_output_sequence_port_text(int p_port) const { + + return itos(p_port+1); +} + +PropertyInfo VisualScriptSequence::get_input_value_port_info(int p_idx) const{ + return PropertyInfo(); +} +PropertyInfo VisualScriptSequence::get_output_value_port_info(int p_idx) const{ + return PropertyInfo(Variant::INT,"current"); +} +String VisualScriptSequence::get_caption() const { + + return "Sequence"; +} + +String VisualScriptSequence::get_text() const { + + return "in order: "; +} + +void VisualScriptSequence::set_steps(int p_steps) { + + ERR_FAIL_COND(p_steps<1); + if (steps==p_steps) + return; + + steps=p_steps; + ports_changed_notify(); + +} + +int VisualScriptSequence::get_steps() const { + + return steps; +} + +void VisualScriptSequence::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_steps","steps"),&VisualScriptSequence::set_steps); + ObjectTypeDB::bind_method(_MD("get_steps"),&VisualScriptSequence::get_steps); + + ADD_PROPERTY(PropertyInfo(Variant::INT,"steps",PROPERTY_HINT_RANGE,"1,64,1"),_SCS("set_steps"),_SCS("get_steps")); + +} + +class VisualScriptNodeInstanceSequence : public VisualScriptNodeInstance { +public: + + VisualScriptSequence *node; + VisualScriptInstance *instance; + int steps; + + virtual int get_working_memory_size() const { return 1; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_start_mode==START_MODE_BEGIN_SEQUENCE) { + + p_working_mem[0]=0; + } + + int step = p_working_mem[0]; + + *p_outputs[0]=step; + + if (step+1==steps) + return step; + else { + p_working_mem[0]=step+1; + return step|STEP_FLAG_PUSH_STACK_BIT; + } + + } + + +}; + +VisualScriptNodeInstance* VisualScriptSequence::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceSequence * instance = memnew(VisualScriptNodeInstanceSequence ); + instance->node=this; + instance->instance=p_instance; + instance->steps=steps; + return instance; +} +VisualScriptSequence::VisualScriptSequence() { + + steps=1; +} + + +////////////////////////////////////////// +////////////////EVENT TYPE FILTER/////////// +////////////////////////////////////////// + +int VisualScriptSwitch::get_output_sequence_port_count() const { + + return case_values.size()+1; +} + +bool VisualScriptSwitch::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptSwitch::get_input_value_port_count() const{ + + + return case_values.size()+1; +} +int VisualScriptSwitch::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptSwitch::get_output_sequence_port_text(int p_port) const { + + if (p_port==case_values.size()) + return "done"; + + return String(); +} + +PropertyInfo VisualScriptSwitch::get_input_value_port_info(int p_idx) const{ + + if (p_idx<case_values.size()) { + return PropertyInfo(case_values[p_idx].type," ="); + } else + return PropertyInfo(Variant::NIL,"input"); +} + +PropertyInfo VisualScriptSwitch::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptSwitch::get_caption() const { + + return "Switch"; +} + +String VisualScriptSwitch::get_text() const { + + return "'input' is:"; +} + + +class VisualScriptNodeInstanceSwitch : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + int case_count; + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return false; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_start_mode==START_MODE_CONTINUE_SEQUENCE) { + return case_count; //exit + } + + for(int i=0;i<case_count;i++) { + + if (*p_inputs[i]==*p_inputs[case_count]) { + return i|STEP_FLAG_PUSH_STACK_BIT; + } + } + + return case_count; + } + + +}; + +VisualScriptNodeInstance* VisualScriptSwitch::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceSwitch * instance = memnew(VisualScriptNodeInstanceSwitch ); + instance->instance=p_instance; + instance->case_count=case_values.size(); + return instance; +} + +bool VisualScriptSwitch::_set(const StringName& p_name, const Variant& p_value) { + + if (String(p_name)=="case_count") { + case_values.resize(p_value); + _change_notify(); + ports_changed_notify(); + return true; + } + + if (String(p_name).begins_with("case/")) { + + int idx = String(p_name).get_slice("/",1).to_int(); + ERR_FAIL_INDEX_V(idx,case_values.size(),false); + + case_values[idx].type=Variant::Type(int(p_value)); + _change_notify(); + ports_changed_notify(); + + return true; + } + + return false; +} + +bool VisualScriptSwitch::_get(const StringName& p_name,Variant &r_ret) const { + + if (String(p_name)=="case_count") { + r_ret=case_values.size(); + return true; + } + + if (String(p_name).begins_with("case/")) { + + int idx = String(p_name).get_slice("/",1).to_int(); + ERR_FAIL_INDEX_V(idx,case_values.size(),false); + + r_ret=case_values[idx].type; + return true; + } + + return false; + +} +void VisualScriptSwitch::_get_property_list( List<PropertyInfo> *p_list) const { + + p_list->push_back(PropertyInfo(Variant::INT,"case_count",PROPERTY_HINT_RANGE,"0,128")); + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + for(int i=0;i<case_values.size();i++) { + p_list->push_back(PropertyInfo(Variant::INT,"case/"+itos(i),PROPERTY_HINT_ENUM,argt)); + } +} + + +void VisualScriptSwitch::_bind_methods() { + + +} + +VisualScriptSwitch::VisualScriptSwitch() { + + +} + +////////////////////////////////////////// +////////////////EVENT ACTION FILTER/////////// +////////////////////////////////////////// + + +int VisualScriptInputFilter::get_output_sequence_port_count() const { + + return filters.size(); +} + +bool VisualScriptInputFilter::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptInputFilter::get_input_value_port_count() const{ + + + return 1; +} +int VisualScriptInputFilter::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptInputFilter::get_output_sequence_port_text(int p_port) const { + + String text; + + switch(filters[p_port].type) { + case InputEvent::NONE: { + text="None"; + } break; + case InputEvent::KEY: { + + InputEventKey k = filters[p_port].key; + + if (k.scancode==0 && k.unicode==0) { + text="No Key"; + } else { + if (k.scancode!=0) { + text="KeyCode: "+keycode_get_string(k.scancode); + } else if (k.unicode!=0) { + text="Uniode: "+String::chr(k.unicode); + } + + if (k.pressed) + text+=", Pressed"; + else + text+=", Released"; + + if (k.echo) + text+=", Echo"; + if (k.mod.alt) + text="Alt+"+text; + if (k.mod.shift) + text="Shift+"+text; + if (k.mod.control) + text="Ctrl+"+text; + if (k.mod.meta) + text="Meta+"+text; + } + + } break; + case InputEvent::MOUSE_MOTION: { + InputEventMouseMotion mm = filters[p_port].mouse_motion; + text="Mouse Motion"; + + String b = "Left,Right,Middle,WheelUp,WheelDown,WheelLeft,WheelRight"; + + for(int i=0;i<7;i++) { + if (mm.button_mask&(1<<i)) { + text=b.get_slice(",",i)+"+"+text; + } + } + if (mm.mod.alt) + text="Alt+"+text; + if (mm.mod.shift) + text="Shift+"+text; + if (mm.mod.control) + text="Ctrl+"+text; + if (mm.mod.meta) + text="Meta+"+text; + } break; + case InputEvent::MOUSE_BUTTON: { + + InputEventMouseButton mb = filters[p_port].mouse_button; + + String b = "Any,Left,Right,Middle,WheelUp,WheelDown,WheelLeft,WheelRight"; + + text=b.get_slice(",",mb.button_index)+" Mouse Button"; + + if (mb.pressed) + text+=", Pressed"; + else + text+=", Released"; + + if (mb.doubleclick) + text+=", DblClick"; + if (mb.mod.alt) + text="Alt+"+text; + if (mb.mod.shift) + text="Shift+"+text; + if (mb.mod.control) + text="Ctrl+"+text; + if (mb.mod.meta) + text="Meta+"+text; + + + } break; + case InputEvent::JOYSTICK_MOTION: { + + InputEventJoystickMotion jm = filters[p_port].joy_motion; + + text="JoyMotion Axis "+itos(jm.axis>>1); + if (jm.axis&1) + text+=" > "+rtos(jm.axis_value); + else + text+=" < "+rtos(-jm.axis_value); + + } break; + case InputEvent::JOYSTICK_BUTTON: { + InputEventJoystickButton jb = filters[p_port].joy_button; + + text="JoyButton "+itos(jb.button_index); + if (jb.pressed) + text+=", Pressed"; + else + text+=", Released"; + } break; + case InputEvent::SCREEN_TOUCH: { + InputEventScreenTouch sd = filters[p_port].screen_touch; + + text="Touch Finger "+itos(sd.index); + if (sd.pressed) + text+=", Pressed"; + else + text+=", Released"; + } break; + case InputEvent::SCREEN_DRAG: { + InputEventScreenDrag sd = filters[p_port].screen_drag; + text="Drag Finger "+itos(sd.index); + } break; + case InputEvent::ACTION: { + + + List<PropertyInfo> pinfo; + Globals::get_singleton()->get_property_list(&pinfo); + int index=1; + + text="No Action"; + for(List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + const PropertyInfo &pi=E->get(); + + if (!pi.name.begins_with("input/")) + continue; + + + if (filters[p_port].action.action==index) { + text="Action "+pi.name.substr(pi.name.find("/")+1,pi.name.length()); + break; + } + index++; + } + + if (filters[p_port].action.pressed) + text+=", Pressed"; + else + text+=", Released"; + + + } break; + } + + + + return text+" - "+itos(p_port); +} + +PropertyInfo VisualScriptInputFilter::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::INPUT_EVENT,"event"); +} + +PropertyInfo VisualScriptInputFilter::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::INPUT_EVENT,""); +} + + +String VisualScriptInputFilter::get_caption() const { + + return "InputFilter"; +} + +String VisualScriptInputFilter::get_text() const { + + return ""; +} + + + +bool VisualScriptInputFilter::_set(const StringName& p_name, const Variant& p_value) { + + if (p_name=="filter_count") { + filters.resize(p_value); + _change_notify(); + ports_changed_notify(); + return true; + } + + + if (String(p_name).begins_with("filter_")) { + + int idx = String(p_name).replace_first("filters_","").get_slice("/",0).to_int(); + + ERR_FAIL_INDEX_V(idx,filters.size(),false); + + String what = String(p_name).get_slice("/",1); + + + if (what=="type") { + filters[idx]=InputEvent(); + filters[idx].type=InputEvent::Type(int(p_value)); + if (filters[idx].type==InputEvent::JOYSTICK_MOTION) { + filters[idx].joy_motion.axis_value=0.5; //for treshold + } else if (filters[idx].type==InputEvent::KEY) { + filters[idx].key.pressed=true; //put these as true to make it more user friendly + } else if (filters[idx].type==InputEvent::MOUSE_BUTTON) { + filters[idx].mouse_button.pressed=true; + } else if (filters[idx].type==InputEvent::JOYSTICK_BUTTON) { + filters[idx].joy_button.pressed=true; + } else if (filters[idx].type==InputEvent::SCREEN_TOUCH) { + filters[idx].screen_touch.pressed=true; + } else if (filters[idx].type==InputEvent::ACTION) { + filters[idx].action.pressed=true; + } + _change_notify(); + ports_changed_notify(); + + return true; + } + if (what=="device") { + filters[idx].device=p_value; + ports_changed_notify(); + return true; + } + + switch(filters[idx].type) { + + case InputEvent::KEY: { + + if (what=="scancode") { + String sc = p_value; + if (sc==String()) { + filters[idx].key.scancode=0; + } else { + filters[idx].key.scancode=find_keycode(p_value); + } + + } else if (what=="unicode") { + + String uc = p_value; + + if (uc==String()) { + filters[idx].key.unicode=0; + } else { + filters[idx].key.unicode=uc[0]; + } + + } else if (what=="pressed") { + + filters[idx].key.pressed=p_value; + } else if (what=="echo") { + + filters[idx].key.echo=p_value; + + } else if (what=="mod_alt") { + filters[idx].key.mod.alt=p_value; + + } else if (what=="mod_shift") { + filters[idx].key.mod.shift=p_value; + + } else if (what=="mod_ctrl") { + filters[idx].key.mod.control=p_value; + + } else if (what=="mod_meta") { + filters[idx].key.mod.meta=p_value; + } else { + return false; + } + ports_changed_notify(); + + return true; + } break; + case InputEvent::MOUSE_MOTION: { + + + if (what=="button_mask") { + filters[idx].mouse_motion.button_mask=p_value; + + } else if (what=="mod_alt") { + filters[idx].mouse_motion.mod.alt=p_value; + + } else if (what=="mod_shift") { + filters[idx].mouse_motion.mod.shift=p_value; + + } else if (what=="mod_ctrl") { + filters[idx].mouse_motion.mod.control=p_value; + + } else if (what=="mod_meta") { + filters[idx].mouse_motion.mod.meta=p_value; + } else { + return false; + } + + ports_changed_notify(); + return true; + + } break; + case InputEvent::MOUSE_BUTTON: { + + if (what=="button_index") { + filters[idx].mouse_button.button_index=p_value; + } else if (what=="pressed") { + filters[idx].mouse_button.pressed=p_value; + } else if (what=="doubleclicked") { + filters[idx].mouse_button.doubleclick=p_value; + + } else if (what=="mod_alt") { + filters[idx].mouse_button.mod.alt=p_value; + + } else if (what=="mod_shift") { + filters[idx].mouse_button.mod.shift=p_value; + + } else if (what=="mod_ctrl") { + filters[idx].mouse_button.mod.control=p_value; + + } else if (what=="mod_meta") { + filters[idx].mouse_button.mod.meta=p_value; + } else { + return false; + } + ports_changed_notify(); + return true; + + } break; + case InputEvent::JOYSTICK_MOTION: { + + if (what=="axis") { + filters[idx].joy_motion.axis=int(p_value)<<1|filters[idx].joy_motion.axis; + } else if (what=="mode") { + filters[idx].joy_motion.axis|=int(p_value); + } else if (what=="treshold") { + filters[idx].joy_motion.axis_value=p_value; + } else { + return false; + } + ports_changed_notify(); + return true; + + + } break; + case InputEvent::JOYSTICK_BUTTON: { + + if (what=="button_index") { + filters[idx].joy_button.button_index=p_value; + } else if (what=="pressed") { + filters[idx].joy_button.pressed=p_value; + } else { + return false; + } + ports_changed_notify(); + return true; + + } break; + case InputEvent::SCREEN_TOUCH: { + + if (what=="finger_index") { + filters[idx].screen_touch.index=p_value; + } else if (what=="pressed") { + filters[idx].screen_touch.pressed=p_value; + } else { + return false; + } + ports_changed_notify(); + return true; + } break; + case InputEvent::SCREEN_DRAG: { + if (what=="finger_index") { + filters[idx].screen_drag.index=p_value; + } else { + return false; + } + ports_changed_notify(); + return true; + } break; + case InputEvent::ACTION: { + + + if (what=="action_name") { + + List<PropertyInfo> pinfo; + Globals::get_singleton()->get_property_list(&pinfo); + int index=1; + + for(List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + const PropertyInfo &pi=E->get(); + + if (!pi.name.begins_with("input/")) + continue; + + String name = pi.name.substr(pi.name.find("/")+1,pi.name.length()); + if (name==String(p_value)) { + + filters[idx].action.action=index; + ports_changed_notify(); + return true; + } + + index++; + } + + filters[idx].action.action=0; + ports_changed_notify(); + + return false; + + } else if (what=="pressed") { + + filters[idx].action.pressed=p_value; + ports_changed_notify(); + return true; + } + + + } break; + + } + } + return false; +} + +bool VisualScriptInputFilter::_get(const StringName& p_name,Variant &r_ret) const{ + + if (p_name=="filter_count") { + r_ret=filters.size(); + return true; + } + + + if (String(p_name).begins_with("filter_")) { + + int idx = String(p_name).replace_first("filters_","").get_slice("/",0).to_int(); + + ERR_FAIL_INDEX_V(idx,filters.size(),false); + + String what = String(p_name).get_slice("/",1); + + + if (what=="type") { + r_ret=filters[idx].type; + return true; + } + if (what=="device") { + r_ret=filters[idx].device; + return true; + } + + switch(filters[idx].type) { + + case InputEvent::KEY: { + + if (what=="scancode") { + if (filters[idx].key.scancode==0) + r_ret=String(); + else { + + r_ret=keycode_get_string(filters[idx].key.scancode); + } + + } else if (what=="unicode") { + + + if (filters[idx].key.unicode==0) { + r_ret=String(); + } else { + CharType str[2]={ (CharType)filters[idx].key.unicode, 0}; + r_ret=String(str); + } + + } else if (what=="pressed") { + + r_ret=filters[idx].key.pressed; + } else if (what=="echo") { + + r_ret=filters[idx].key.echo; + + } else if (what=="mod_alt") { + r_ret=filters[idx].key.mod.alt; + + } else if (what=="mod_shift") { + r_ret=filters[idx].key.mod.shift; + + } else if (what=="mod_ctrl") { + r_ret=filters[idx].key.mod.control; + + } else if (what=="mod_meta") { + r_ret=filters[idx].key.mod.meta; + } else { + return false; + } + + return true; + } break; + case InputEvent::MOUSE_MOTION: { + + + if (what=="button_mask") { + r_ret=filters[idx].mouse_motion.button_mask; + + } else if (what=="mod_alt") { + r_ret=filters[idx].mouse_motion.mod.alt; + + } else if (what=="mod_shift") { + r_ret=filters[idx].mouse_motion.mod.shift; + + } else if (what=="mod_ctrl") { + r_ret=filters[idx].mouse_motion.mod.control; + + } else if (what=="mod_meta") { + r_ret=filters[idx].mouse_motion.mod.meta; + } else { + return false; + } + + return true; + + } break; + case InputEvent::MOUSE_BUTTON: { + + if (what=="button_index") { + r_ret=filters[idx].mouse_button.button_index; + } else if (what=="pressed") { + r_ret=filters[idx].mouse_button.pressed; + } else if (what=="doubleclicked") { + r_ret=filters[idx].mouse_button.doubleclick; + + } else if (what=="mod_alt") { + r_ret=filters[idx].mouse_button.mod.alt; + + } else if (what=="mod_shift") { + r_ret=filters[idx].mouse_button.mod.shift; + + } else if (what=="mod_ctrl") { + r_ret=filters[idx].mouse_button.mod.control; + + } else if (what=="mod_meta") { + r_ret=filters[idx].mouse_button.mod.meta; + } else { + return false; + } + return true; + + } break; + case InputEvent::JOYSTICK_MOTION: { + + if (what=="axis_index") { + r_ret=filters[idx].joy_motion.axis>>1; + } else if (what=="mode") { + r_ret=filters[idx].joy_motion.axis&1; + } else if (what=="treshold") { + r_ret=filters[idx].joy_motion.axis_value; + } else { + return false; + } + return true; + + + } break; + case InputEvent::JOYSTICK_BUTTON: { + + if (what=="button_index") { + r_ret=filters[idx].joy_button.button_index; + } else if (what=="pressed") { + r_ret=filters[idx].joy_button.pressed; + } else { + return false; + } + return true; + + } break; + case InputEvent::SCREEN_TOUCH: { + + if (what=="finger_index") { + r_ret=filters[idx].screen_touch.index; + } else if (what=="pressed") { + r_ret=filters[idx].screen_touch.pressed; + } else { + return false; + } + return true; + } break; + case InputEvent::SCREEN_DRAG: { + if (what=="finger_index") { + r_ret=filters[idx].screen_drag.index; + } else { + return false; + } + return true; + } break; + case InputEvent::ACTION: { + + + if (what=="action_name") { + + List<PropertyInfo> pinfo; + Globals::get_singleton()->get_property_list(&pinfo); + int index=1; + + for(List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + const PropertyInfo &pi=E->get(); + + if (!pi.name.begins_with("input/")) + continue; + + + if (filters[idx].action.action==index) { + r_ret=pi.name.substr(pi.name.find("/")+1,pi.name.length()); + return true; + } + index++; + } + + r_ret="None"; //no index + return false; + + } else if (what=="pressed") { + + r_ret=filters[idx].action.pressed; + return true; + } + + + } break; + + } + } + return false; +} + +static const char* event_type_names[InputEvent::TYPE_MAX]={ + "None", + "Key", + "MouseMotion", + "MouseButton", + "JoystickMotion", + "JoystickButton", + "ScreenTouch", + "ScreenDrag", + "Action" +}; + +void VisualScriptInputFilter::_get_property_list( List<PropertyInfo> *p_list) const { + + p_list->push_back(PropertyInfo(Variant::INT,"filter_count",PROPERTY_HINT_RANGE,"0,64")); + + String et; + for(int i=0;i<InputEvent::TYPE_MAX;i++) { + if (i>0) + et+=","; + + et+=event_type_names[i]; + } + + String kc; + String actions; + + + + for(int i=0;i<filters.size();i++) { + + String base = "filter_"+itos(i)+"/"; + p_list->push_back(PropertyInfo(Variant::INT,base+"type",PROPERTY_HINT_ENUM,et)); + p_list->push_back(PropertyInfo(Variant::INT,base+"device")); + switch(filters[i].type) { + + case InputEvent::NONE: { + + } break; + case InputEvent::KEY: { + if (kc==String()) { + int kcc = keycode_get_count(); + kc="None"; + for(int i=0;i<kcc;i++) { + kc+=","; + kc+=String(keycode_get_name_by_index(i)); + } + } + p_list->push_back(PropertyInfo(Variant::STRING,base+"scancode",PROPERTY_HINT_ENUM,kc)); + p_list->push_back(PropertyInfo(Variant::STRING,base+"unicode")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"pressed")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"echo")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_alt")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_shift")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_ctrl")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_meta")); + + + } break; + case InputEvent::MOUSE_MOTION: { + p_list->push_back(PropertyInfo(Variant::INT,base+"button_mask",PROPERTY_HINT_FLAGS,"Left,Right,Middle,WheelUp,WheelDown,WheelLeft,WheelRight")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_alt")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_shift")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_ctrl")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_meta")); + + } break; + case InputEvent::MOUSE_BUTTON: { + p_list->push_back(PropertyInfo(Variant::INT,base+"button_index",PROPERTY_HINT_ENUM,"Any,Left,Right,Middle,WheelUp,WheelDown,WheelLeft,WheelRight")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"pressed")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"doubleclicked")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_alt")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_shift")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_ctrl")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_meta")); + + } break; + case InputEvent::JOYSTICK_MOTION: { + + p_list->push_back(PropertyInfo(Variant::INT,base+"axis_index")); + p_list->push_back(PropertyInfo(Variant::INT,base+"mode",PROPERTY_HINT_ENUM,"Min,Max")); + p_list->push_back(PropertyInfo(Variant::REAL,base+"treshold",PROPERTY_HINT_RANGE,"0,1,0.01")); + } break; + case InputEvent::JOYSTICK_BUTTON: { + p_list->push_back(PropertyInfo(Variant::INT,base+"button_index")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"pressed")); + + } break; + case InputEvent::SCREEN_TOUCH: { + p_list->push_back(PropertyInfo(Variant::INT,base+"finger_index")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"pressed")); + + } break; + case InputEvent::SCREEN_DRAG: { + p_list->push_back(PropertyInfo(Variant::INT,base+"finger_index")); + } break; + case InputEvent::ACTION: { + + + + if (actions==String()) { + + actions="None"; + + List<PropertyInfo> pinfo; + Globals::get_singleton()->get_property_list(&pinfo); + Vector<String> al; + + for(List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + const PropertyInfo &pi=E->get(); + + if (!pi.name.begins_with("input/")) + continue; + + String name = pi.name.substr(pi.name.find("/")+1,pi.name.length()); + + + al.push_back(name); + } + + for(int i=0;i<al.size();i++) { + actions+=","; + actions+=al[i]; + } + } + + p_list->push_back(PropertyInfo(Variant::STRING,base+"action_name",PROPERTY_HINT_ENUM,actions)); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"pressed")); + + } break; + + } + } +} + +class VisualScriptNodeInstanceInputFilter : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + Vector<InputEvent> filters; + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return false; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_inputs[0]->get_type()!=Variant::INPUT_EVENT) { + r_error_str="Input value not of type event"; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } + + InputEvent event = *p_inputs[0]; + + + for(int i=0;i<filters.size();i++) { + + const InputEvent &ie = filters[i]; + if (ie.type!=event.type) + continue; + + bool match=false; + + switch(ie.type) { + + case InputEvent::NONE: { + + match=true; + } break; + case InputEvent::KEY: { + + InputEventKey k = ie.key; + InputEventKey k2 = event.key; + + if (k.scancode==0 && k.unicode==0 && k2.scancode==0 && k2.unicode==0) { + match=true; + + } else { + + if ( (k.scancode!=0 && k.scancode==k2.scancode) || (k.unicode!=0 && k.unicode==k2.unicode)) { + //key valid + + if ( + k.pressed==k2.pressed && + k.echo==k2.echo && + k.mod == k2.mod + ) { + match=true; + } + } + + } + + } break; + case InputEvent::MOUSE_MOTION: { + InputEventMouseMotion mm = ie.mouse_motion; + InputEventMouseMotion mm2 = event.mouse_motion; + + if ( mm.button_mask==mm2.button_mask && + mm.mod==mm2.mod + ) { + match=true; + } + + } break; + case InputEvent::MOUSE_BUTTON: { + + InputEventMouseButton mb = ie.mouse_button; + InputEventMouseButton mb2 = event.mouse_button; + + if ( mb.button_index==mb2.button_index && + mb.pressed==mb2.pressed && + mb.doubleclick==mb2.doubleclick && + mb.mod==mb2.mod) { + match=true; + } + + + } break; + case InputEvent::JOYSTICK_MOTION: { + + InputEventJoystickMotion jm = ie.joy_motion; + InputEventJoystickMotion jm2 = event.joy_motion; + + int axis = jm.axis>>1; + + if (axis==jm2.axis) { + + if (jm.axis&1) { + //greater + if (jm2.axis_value > jm.axis_value) { + match=true; + } + } else { + //less + if (jm2.axis_value < -jm.axis_value) { + match=true; + } + } + } + + + } break; + case InputEvent::JOYSTICK_BUTTON: { + InputEventJoystickButton jb = ie.joy_button; + InputEventJoystickButton jb2 = event.joy_button; + + if ( jb.button_index==jb2.button_index && + jb.pressed == jb2.pressed + ) { + match=true; + } + } break; + case InputEvent::SCREEN_TOUCH: { + InputEventScreenTouch st = ie.screen_touch; + InputEventScreenTouch st2 = event.screen_touch; + + if ( st.index==st2.index && + st.pressed==st2.pressed) { + match=true; + } + + } break; + case InputEvent::SCREEN_DRAG: { + InputEventScreenDrag sd = ie.screen_drag; + InputEventScreenDrag sd2 = event.screen_drag; + + if (sd.index==sd2.index) { + match=true; + } + } break; + case InputEvent::ACTION: { + + InputEventAction ia = ie.action; + InputEventAction ia2 = event.action; + + if ( ia.action==ia2.action && + ia.pressed==ia2.pressed) { + match=true; + } + } break; + + } + + *p_outputs[0] = event; + + if (match) + return i; //go through match output + + } + + return STEP_NO_ADVANCE_BIT; //none found, don't advance + + + } + + +}; + + +VisualScriptNodeInstance* VisualScriptInputFilter::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceInputFilter * instance = memnew(VisualScriptNodeInstanceInputFilter ); + instance->instance=p_instance; + instance->filters=filters; + return instance; +} + + + + +VisualScriptInputFilter::VisualScriptInputFilter() { + + +} + + +////////////////////////////////////////// +////////////////TYPE CAST/////////// +////////////////////////////////////////// + + +int VisualScriptTypeCast::get_output_sequence_port_count() const { + + return 2; +} + +bool VisualScriptTypeCast::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptTypeCast::get_input_value_port_count() const{ + + + return 1; +} +int VisualScriptTypeCast::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptTypeCast::get_output_sequence_port_text(int p_port) const { + + return p_port==0 ? "yes" : "no"; +} + +PropertyInfo VisualScriptTypeCast::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::OBJECT,"instance"); +} + +PropertyInfo VisualScriptTypeCast::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::OBJECT,""); +} + + +String VisualScriptTypeCast::get_caption() const { + + return "TypeCast"; +} + +String VisualScriptTypeCast::get_text() const { + + if (script!=String()) + return "Is "+script.get_file()+"?"; + else + return "Is "+base_type+"?"; +} + +void VisualScriptTypeCast::set_base_type(const StringName& p_type) { + + if (base_type==p_type) + return; + + base_type=p_type; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptTypeCast::get_base_type() const{ + + return base_type; +} + +void VisualScriptTypeCast::set_base_script(const String& p_path){ + + if (script==p_path) + return; + + script=p_path; + _change_notify(); + ports_changed_notify(); + +} +String VisualScriptTypeCast::get_base_script() const{ + + return script; +} + + +class VisualScriptNodeInstanceTypeCast : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + StringName base_type; + String script; + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return false; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + Object *obj = *p_inputs[0]; + + *p_outputs[0]=Variant(); + + if (!obj) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Instance is null"; + return 0; + } + + if (script!=String()) { + + Ref<Script> obj_script = obj->get_script(); + if (!obj_script.is_valid()) { + return 1; //well, definitely not the script because object we got has no script. + } + + if (!ResourceCache::has(script)) { + //if the script is not in use by anyone, we can safely assume whathever we got is not casting to it. + return 1; + } + Ref<Script> cast_script = Ref<Resource>(ResourceCache::get(script)); + if (!cast_script.is_valid()) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Script path is not a script: "+script; + return 1; + } + + while(obj_script.is_valid()) { + + if (cast_script==obj_script) { + *p_outputs[0]=*p_inputs[0]; //copy + return 0; // it is the script, yey + } + + obj_script=obj_script->get_base_script(); + } + + return 1; //not found sorry + } + + if (ObjectTypeDB::is_type(obj->get_type_name(),base_type)) { + *p_outputs[0]=*p_inputs[0]; //copy + return 0; + } else + return 1; + + } + + +}; + +VisualScriptNodeInstance* VisualScriptTypeCast::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceTypeCast * instance = memnew(VisualScriptNodeInstanceTypeCast ); + instance->instance=p_instance; + instance->base_type=base_type; + instance->script=script; + return instance; +} + + + +void VisualScriptTypeCast::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_base_type","type"),&VisualScriptTypeCast::set_base_type); + ObjectTypeDB::bind_method(_MD("get_base_type"),&VisualScriptTypeCast::get_base_type); + + ObjectTypeDB::bind_method(_MD("set_base_script","path"),&VisualScriptTypeCast::set_base_script); + ObjectTypeDB::bind_method(_MD("get_base_script"),&VisualScriptTypeCast::get_base_script); + + + List<String> script_extensions; + for(int i=0;i>ScriptServer::get_language_count();i++) { + ScriptServer::get_language(i)->get_recognized_extensions(&script_extensions); + } + + String script_ext_hint; + for (List<String>::Element *E=script_extensions.front();E;E=E->next()) { + if (script_ext_hint!=String()) + script_ext_hint+=","; + script_ext_hint+="*."+E->get(); + } + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"function/base_type",PROPERTY_HINT_TYPE_STRING,"Object"),_SCS("set_base_type"),_SCS("get_base_type")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/base_script",PROPERTY_HINT_FILE,script_ext_hint),_SCS("set_base_script"),_SCS("get_base_script")); + +} + +VisualScriptTypeCast::VisualScriptTypeCast() { + + base_type="Object"; +} + + +void register_visual_script_flow_control_nodes() { + + VisualScriptLanguage::singleton->add_register_func("flow_control/return",create_return_node<false>); + VisualScriptLanguage::singleton->add_register_func("flow_control/return_with_value",create_return_node<true>); + VisualScriptLanguage::singleton->add_register_func("flow_control/condition",create_node_generic<VisualScriptCondition>); + VisualScriptLanguage::singleton->add_register_func("flow_control/while",create_node_generic<VisualScriptWhile>); + VisualScriptLanguage::singleton->add_register_func("flow_control/iterator",create_node_generic<VisualScriptIterator>); + VisualScriptLanguage::singleton->add_register_func("flow_control/sequence",create_node_generic<VisualScriptSequence>); + VisualScriptLanguage::singleton->add_register_func("flow_control/switch",create_node_generic<VisualScriptSwitch>); + VisualScriptLanguage::singleton->add_register_func("flow_control/input_filter",create_node_generic<VisualScriptInputFilter>); + VisualScriptLanguage::singleton->add_register_func("flow_control/type_cast",create_node_generic<VisualScriptTypeCast>); + + + +} diff --git a/modules/visual_script/visual_script_flow_control.h b/modules/visual_script/visual_script_flow_control.h new file mode 100644 index 0000000000..e0da84a534 --- /dev/null +++ b/modules/visual_script/visual_script_flow_control.h @@ -0,0 +1,341 @@ +#ifndef VISUAL_SCRIPT_FLOW_CONTROL_H +#define VISUAL_SCRIPT_FLOW_CONTROL_H + +#include "visual_script.h" + +class VisualScriptReturn : public VisualScriptNode { + + OBJ_TYPE(VisualScriptReturn,VisualScriptNode) + + + Variant::Type type; + bool with_value; +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + void set_return_type(Variant::Type); + Variant::Type get_return_type() const; + + void set_enable_return_value(bool p_enable); + bool is_return_value_enabled() const; + + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptReturn(); +}; + + +class VisualScriptCondition : public VisualScriptNode { + + OBJ_TYPE(VisualScriptCondition,VisualScriptNode) + + + +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptCondition(); +}; + + +class VisualScriptWhile : public VisualScriptNode { + + OBJ_TYPE(VisualScriptWhile,VisualScriptNode) + + + +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptWhile(); +}; + + + +class VisualScriptIterator : public VisualScriptNode { + + OBJ_TYPE(VisualScriptIterator,VisualScriptNode) + + + +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptIterator(); +}; + + + +class VisualScriptSequence : public VisualScriptNode { + + OBJ_TYPE(VisualScriptSequence,VisualScriptNode) + + + int steps; + +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + void set_steps(int p_steps); + int get_steps() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptSequence(); +}; + + + + +class VisualScriptSwitch : public VisualScriptNode { + + OBJ_TYPE(VisualScriptSwitch,VisualScriptNode) + + struct Case { + Variant::Type type; + Case() { type=Variant::NIL; } + }; + + Vector<Case> case_values; + +friend class VisualScriptNodeInstanceSwitch; +protected: + + bool _set(const StringName& p_name, const Variant& p_value); + bool _get(const StringName& p_name,Variant &r_ret) const; + void _get_property_list( List<PropertyInfo> *p_list) const; + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + virtual bool has_mixed_input_and_sequence_ports() const { return true; } + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + + VisualScriptSwitch(); +}; + + + + +class VisualScriptInputFilter : public VisualScriptNode { + + OBJ_TYPE(VisualScriptInputFilter,VisualScriptNode) + + Vector<InputEvent> filters; + + +protected: + bool _set(const StringName& p_name, const Variant& p_value); + bool _get(const StringName& p_name,Variant &r_ret) const; + void _get_property_list( List<PropertyInfo> *p_list) const; + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + + VisualScriptInputFilter(); +}; + + + + + +class VisualScriptTypeCast : public VisualScriptNode { + + OBJ_TYPE(VisualScriptTypeCast,VisualScriptNode) + + + StringName base_type; + String script; + +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + void set_base_type(const StringName& p_type); + StringName get_base_type() const; + + void set_base_script(const String& p_path); + String get_base_script() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + + VisualScriptTypeCast(); +}; + + + + +void register_visual_script_flow_control_nodes(); + + + +#endif // VISUAL_SCRIPT_FLOW_CONTROL_H diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp new file mode 100644 index 0000000000..de99beacaf --- /dev/null +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -0,0 +1,2520 @@ +#include "visual_script_func_nodes.h" +#include "scene/main/scene_main_loop.h" +#include "os/os.h" +#include "scene/main/node.h" +#include "visual_script_nodes.h" +#include "io/resource_loader.h" +#include "globals.h" + +////////////////////////////////////////// +////////////////CALL////////////////////// +////////////////////////////////////////// + +int VisualScriptFunctionCall::get_output_sequence_port_count() const { + + if (method_cache.flags&METHOD_FLAG_CONST || call_mode==CALL_MODE_BASIC_TYPE) + return 0; + else + return 1; +} + +bool VisualScriptFunctionCall::has_input_sequence_port() const{ + + if (method_cache.flags&METHOD_FLAG_CONST || call_mode==CALL_MODE_BASIC_TYPE) + return false; + else + return true; +} +#ifdef TOOLS_ENABLED + +static Node* _find_script_node(Node* p_edited_scene,Node* p_current_node,const Ref<Script> &script) { + + if (p_edited_scene!=p_current_node && p_current_node->get_owner()!=p_edited_scene) + return NULL; + + Ref<Script> scr = p_current_node->get_script(); + + if (scr.is_valid() && scr==script) + return p_current_node; + + for(int i=0;i<p_current_node->get_child_count();i++) { + Node *n = _find_script_node(p_edited_scene,p_current_node->get_child(i),script); + if (n) + return n; + } + + return NULL; +} + +#endif +Node *VisualScriptFunctionCall::_get_base_node() const { + +#ifdef TOOLS_ENABLED + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return NULL; + + MainLoop * main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) + return NULL; + + SceneTree *scene_tree = main_loop->cast_to<SceneTree>(); + + if (!scene_tree) + return NULL; + + Node *edited_scene = scene_tree->get_edited_scene_root(); + + if (!edited_scene) + return NULL; + + Node* script_node = _find_script_node(edited_scene,edited_scene,script); + + if (!script_node) + return NULL; + + if (!script_node->has_node(base_path)) + return NULL; + + Node *path_to = script_node->get_node(base_path); + + return path_to; +#else + + return NULL; +#endif +} + +StringName VisualScriptFunctionCall::_get_base_type() const { + + if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) + return get_visual_script()->get_instance_base_type(); + else if (call_mode==CALL_MODE_NODE_PATH && get_visual_script().is_valid()) { + Node *path = _get_base_node(); + if (path) + return path->get_type(); + + } + + return base_type; +} + + +int VisualScriptFunctionCall::get_input_value_port_count() const{ + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + + Vector<StringName> names = Variant::get_method_argument_names(basic_type,function); + return names.size() + (rpc_call_mode>=RPC_RELIABLE_TO_ID?1:0) + 1; + + } else { + + MethodBind *mb = ObjectTypeDB::get_method(_get_base_type(),function); + if (mb) { + return mb->get_argument_count() + (call_mode==CALL_MODE_INSTANCE?1:0) + (rpc_call_mode>=RPC_RELIABLE_TO_ID?1:0) - use_default_args; + } + + return method_cache.arguments.size() + (call_mode==CALL_MODE_INSTANCE?1:0) + (rpc_call_mode>=RPC_RELIABLE_TO_ID?1:0) - use_default_args; + } + +} +int VisualScriptFunctionCall::get_output_value_port_count() const{ + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + bool returns=false; + Variant::get_method_return_type(basic_type,function,&returns); + return returns?1:0; + + } else { + int ret; + MethodBind *mb = ObjectTypeDB::get_method(_get_base_type(),function); + if (mb) { + ret = mb->has_return() ? 1 : 0; + } else + ret = 1; //it is assumed that script always returns something + + if (call_mode==CALL_MODE_INSTANCE) { + ret++; + } + + return ret; + } +} + +String VisualScriptFunctionCall::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptFunctionCall::get_input_value_port_info(int p_idx) const{ + + if (call_mode==CALL_MODE_INSTANCE || call_mode==CALL_MODE_BASIC_TYPE) { + if (p_idx==0) { + PropertyInfo pi; + pi.type=(call_mode==CALL_MODE_INSTANCE?Variant::OBJECT:basic_type); + pi.name=(call_mode==CALL_MODE_INSTANCE?String("instance"):Variant::get_type_name(basic_type).to_lower()); + return pi; + } else { + p_idx--; + } + } + + if (rpc_call_mode>=RPC_RELIABLE_TO_ID) { + + if (p_idx==0) { + return PropertyInfo(Variant::INT,"peer_id"); + } else { + p_idx--; + } + + } + +#ifdef DEBUG_METHODS_ENABLED + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + + Vector<StringName> names = Variant::get_method_argument_names(basic_type,function); + Vector<Variant::Type> types = Variant::get_method_argument_types(basic_type,function); + return PropertyInfo(types[p_idx],names[p_idx]); + + } else { + + MethodBind *mb = ObjectTypeDB::get_method(_get_base_type(),function); + if (mb) { + return mb->get_argument_info(p_idx); + } + + if (p_idx>=0 && p_idx < method_cache.arguments.size()) { + return method_cache.arguments[p_idx]; + } + + return PropertyInfo(); + } +#else + return PropertyInfo(); +#endif + +} + +PropertyInfo VisualScriptFunctionCall::get_output_value_port_info(int p_idx) const{ + + +#ifdef DEBUG_METHODS_ENABLED + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + + return PropertyInfo(Variant::get_method_return_type(basic_type,function),""); + } else { + + if (call_mode==CALL_MODE_INSTANCE) { + if (p_idx==0) { + return PropertyInfo(Variant::OBJECT,"pass"); + } else { + p_idx--; + } + } + + PropertyInfo ret; + + /*MethodBind *mb = ObjectTypeDB::get_method(_get_base_type(),function); + if (mb) { + + ret = mb->get_argument_info(-1); + } else {*/ + + ret = method_cache.return_val; + + //} + + if (call_mode==CALL_MODE_INSTANCE) { + ret.name="return"; + } else { + ret.name=""; + } + return ret; + + + } +#else + return PropertyInfo(); +#endif +} + + +String VisualScriptFunctionCall::get_caption() const { + + static const char*cname[5]= { + "CallSelf", + "CallNode", + "CallInstance", + "CallBasic", + "CallSingleton" + }; + + String caption = cname[call_mode]; + + if (rpc_call_mode) { + caption+=" (RPC)"; + } + + return caption; +} + +String VisualScriptFunctionCall::get_text() const { + + if (call_mode==CALL_MODE_SELF) + return " "+String(function)+"()"; + if (call_mode==CALL_MODE_SINGLETON) + return String(singleton)+":"+String(function)+"()"; + else if (call_mode==CALL_MODE_BASIC_TYPE) + return Variant::get_type_name(basic_type)+"."+String(function)+"()"; + else if (call_mode==CALL_MODE_NODE_PATH) + return " ["+String(base_path.simplified())+"]."+String(function)+"()"; + else + return " "+base_type+"."+String(function)+"()"; + +} + + +void VisualScriptFunctionCall::set_basic_type(Variant::Type p_type) { + + if (basic_type==p_type) + return; + basic_type=p_type; + + + _change_notify(); + ports_changed_notify(); +} + +Variant::Type VisualScriptFunctionCall::get_basic_type() const{ + + return basic_type; +} + +void VisualScriptFunctionCall::set_base_type(const StringName& p_type) { + + if (base_type==p_type) + return; + + base_type=p_type; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptFunctionCall::get_base_type() const{ + + return base_type; +} + +void VisualScriptFunctionCall::set_base_script(const String& p_path) { + + if (base_script==p_path) + return; + + base_script=p_path; + _change_notify(); + ports_changed_notify(); +} + +String VisualScriptFunctionCall::get_base_script() const { + + return base_script; +} + +void VisualScriptFunctionCall::set_singleton(const StringName& p_path) { + + if (singleton==p_path) + return; + + singleton=p_path; + Object *obj = Globals::get_singleton()->get_singleton_object(singleton); + if (obj) { + base_type=obj->get_type(); + } + + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptFunctionCall::get_singleton() const { + + return singleton; +} + + + +void VisualScriptFunctionCall::_update_method_cache() { + StringName type; + Ref<Script> script; + + if (call_mode==CALL_MODE_NODE_PATH) { + + Node* node=_get_base_node(); + if (node) { + type=node->get_type(); + base_type=type; //cache, too + script = node->get_script(); + } + } else if (call_mode==CALL_MODE_SELF) { + + if (get_visual_script().is_valid()) { + type=get_visual_script()->get_instance_base_type(); + base_type=type; //cache, too + script=get_visual_script(); + } + + } else if (call_mode==CALL_MODE_SINGLETON) { + + Object *obj = Globals::get_singleton()->get_singleton_object(singleton); + if (obj) { + type=obj->get_type(); + script=obj->get_script(); + } + + } else if (call_mode==CALL_MODE_INSTANCE) { + + type=base_type; + if (base_script!=String()) { + + if (!ResourceCache::has(base_script) && ScriptServer::edit_request_func) { + + ScriptServer::edit_request_func(base_script); //make sure it's loaded + } + + if (ResourceCache::has(base_script)) { + + script = Ref<Resource>( ResourceCache::get(base_script) ); + } else { + return; + } + } + } + + +// print_line("BASE: "+String(type)+" FUNC: "+String(function)); + MethodBind *mb = ObjectTypeDB::get_method(type,function); + if (mb) { + use_default_args=mb->get_default_argument_count(); + method_cache = MethodInfo(); + for(int i=0;i<mb->get_argument_count();i++) { +#ifdef DEBUG_METHODS_ENABLED + method_cache.arguments.push_back(mb->get_argument_info(i)); +#else + method_cache.arguments.push_back(PropertyInfo()); +#endif + } + + if (mb->is_const()) { + method_cache.flags|=METHOD_FLAG_CONST; + } + +#ifdef DEBUG_METHODS_ENABLED + + method_cache.return_val = mb->get_argument_info(-1); +#endif + } else if (script.is_valid() && script->has_method(function)) { + + method_cache = script->get_method_info(function); + use_default_args=method_cache.default_arguments.size(); + } +} + +void VisualScriptFunctionCall::set_function(const StringName& p_type){ + + if (function==p_type) + return; + + function=p_type; + + if (call_mode==CALL_MODE_BASIC_TYPE) { + use_default_args = Variant::get_method_default_arguments(basic_type,function).size(); + } else { + //update all caches + + _update_method_cache(); + + } + + + _change_notify(); + ports_changed_notify(); +} +StringName VisualScriptFunctionCall::get_function() const { + + + return function; +} + +void VisualScriptFunctionCall::set_base_path(const NodePath& p_type) { + + if (base_path==p_type) + return; + + base_path=p_type; + _change_notify(); + ports_changed_notify(); +} + +NodePath VisualScriptFunctionCall::get_base_path() const { + + return base_path; +} + + +void VisualScriptFunctionCall::set_call_mode(CallMode p_mode) { + + if (call_mode==p_mode) + return; + + call_mode=p_mode; + _change_notify(); + ports_changed_notify(); + +} +VisualScriptFunctionCall::CallMode VisualScriptFunctionCall::get_call_mode() const { + + return call_mode; +} + +void VisualScriptFunctionCall::set_use_default_args(int p_amount) { + + if (use_default_args==p_amount) + return; + + use_default_args=p_amount; + ports_changed_notify(); + + +} + +void VisualScriptFunctionCall::set_rpc_call_mode(VisualScriptFunctionCall::RPCCallMode p_mode) { + + if (rpc_call_mode==p_mode) + return; + rpc_call_mode=p_mode; + ports_changed_notify(); + _change_notify(); +} + +VisualScriptFunctionCall::RPCCallMode VisualScriptFunctionCall::get_rpc_call_mode() const{ + + return rpc_call_mode; +} + + +int VisualScriptFunctionCall::get_use_default_args() const{ + + return use_default_args; +} + + +void VisualScriptFunctionCall::set_validate(bool p_amount) { + + validate=p_amount; +} + +bool VisualScriptFunctionCall::get_validate() const { + + return validate; +} + + +void VisualScriptFunctionCall::_set_argument_cache(const Dictionary& p_cache) { + //so everything works in case all else fails + method_cache=MethodInfo::from_dict(p_cache); + +} + +Dictionary VisualScriptFunctionCall::_get_argument_cache() const { + + return method_cache; +} + +void VisualScriptFunctionCall::_validate_property(PropertyInfo& property) const { + + if (property.name=="function/base_type") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=PROPERTY_USAGE_NOEDITOR; + } + } + + if (property.name=="function/base_script") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=0; + } + } + + if (property.name=="function/basic_type") { + if (call_mode!=CALL_MODE_BASIC_TYPE) { + property.usage=0; + } + } + + if (property.name=="function/singleton") { + if (call_mode!=CALL_MODE_SINGLETON) { + property.usage=0; + } else { + List<Globals::Singleton> names; + Globals::get_singleton()->get_singletons(&names); + property.hint=PROPERTY_HINT_ENUM; + String sl; + for (List<Globals::Singleton>::Element *E=names.front();E;E=E->next()) { + if (sl!=String()) + sl+=","; + sl+=E->get().name; + } + property.hint_string=sl; + + } + } + + if (property.name=="function/node_path") { + if (call_mode!=CALL_MODE_NODE_PATH) { + property.usage=0; + } else { + + Node *bnode = _get_base_node(); + if (bnode) { + property.hint_string=bnode->get_path(); //convert to loong string + } else { + + } + } + } + + if (property.name=="function/function") { + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + property.hint=PROPERTY_HINT_METHOD_OF_VARIANT_TYPE; + property.hint_string=Variant::get_type_name(basic_type); + + } else if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) { + property.hint=PROPERTY_HINT_METHOD_OF_SCRIPT; + property.hint_string=itos(get_visual_script()->get_instance_ID()); + } else if (call_mode==CALL_MODE_SINGLETON) { + + Object *obj = Globals::get_singleton()->get_singleton_object(singleton); + if (obj) { + property.hint=PROPERTY_HINT_METHOD_OF_INSTANCE; + property.hint_string=itos(obj->get_instance_ID()); + } else { + + property.hint=PROPERTY_HINT_METHOD_OF_BASE_TYPE; + property.hint_string=base_type;//should be cached + } + } else if (call_mode==CALL_MODE_INSTANCE) { + property.hint=PROPERTY_HINT_METHOD_OF_BASE_TYPE; + property.hint_string=base_type; + + if (base_script!=String()) { + if (!ResourceCache::has(base_script) && ScriptServer::edit_request_func) { + + ScriptServer::edit_request_func(base_script); //make sure it's loaded + } + + if (ResourceCache::has(base_script)) { + + Ref<Script> script = Ref<Resource>( ResourceCache::get(base_script) ); + if (script.is_valid()) { + + property.hint=PROPERTY_HINT_METHOD_OF_SCRIPT; + property.hint_string=itos(script->get_instance_ID()); + } + } + } + + } else if (call_mode==CALL_MODE_NODE_PATH) { + Node *node = _get_base_node(); + if (node) { + property.hint=PROPERTY_HINT_METHOD_OF_INSTANCE; + property.hint_string=itos(node->get_instance_ID()); + } else { + property.hint=PROPERTY_HINT_METHOD_OF_BASE_TYPE; + property.hint_string=get_base_type(); + } + + } + + } + + if (property.name=="function/use_default_args") { + + property.hint=PROPERTY_HINT_RANGE; + + int mc=0; + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + mc = Variant::get_method_default_arguments(basic_type,function).size(); + } else { + MethodBind *mb = ObjectTypeDB::get_method(_get_base_type(),function); + if (mb) { + + mc=mb->get_default_argument_count(); + } + } + + if (mc==0) { + property.usage=0; //do not show + } else { + + property.hint_string="0,"+itos(mc)+",1"; + } + } + + if (property.name=="rpc/call_mode") { + if (call_mode==CALL_MODE_BASIC_TYPE) { + property.usage=0; + } + } + +} + + +void VisualScriptFunctionCall::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_base_type","base_type"),&VisualScriptFunctionCall::set_base_type); + ObjectTypeDB::bind_method(_MD("get_base_type"),&VisualScriptFunctionCall::get_base_type); + + ObjectTypeDB::bind_method(_MD("set_base_script","base_script"),&VisualScriptFunctionCall::set_base_script); + ObjectTypeDB::bind_method(_MD("get_base_script"),&VisualScriptFunctionCall::get_base_script); + + ObjectTypeDB::bind_method(_MD("set_basic_type","basic_type"),&VisualScriptFunctionCall::set_basic_type); + ObjectTypeDB::bind_method(_MD("get_basic_type"),&VisualScriptFunctionCall::get_basic_type); + + ObjectTypeDB::bind_method(_MD("set_singleton","singleton"),&VisualScriptFunctionCall::set_singleton); + ObjectTypeDB::bind_method(_MD("get_singleton"),&VisualScriptFunctionCall::get_singleton); + + ObjectTypeDB::bind_method(_MD("set_function","function"),&VisualScriptFunctionCall::set_function); + ObjectTypeDB::bind_method(_MD("get_function"),&VisualScriptFunctionCall::get_function); + + ObjectTypeDB::bind_method(_MD("set_call_mode","mode"),&VisualScriptFunctionCall::set_call_mode); + ObjectTypeDB::bind_method(_MD("get_call_mode"),&VisualScriptFunctionCall::get_call_mode); + + ObjectTypeDB::bind_method(_MD("set_base_path","base_path"),&VisualScriptFunctionCall::set_base_path); + ObjectTypeDB::bind_method(_MD("get_base_path"),&VisualScriptFunctionCall::get_base_path); + + ObjectTypeDB::bind_method(_MD("set_use_default_args","amount"),&VisualScriptFunctionCall::set_use_default_args); + ObjectTypeDB::bind_method(_MD("get_use_default_args"),&VisualScriptFunctionCall::get_use_default_args); + + ObjectTypeDB::bind_method(_MD("_set_argument_cache","argument_cache"),&VisualScriptFunctionCall::_set_argument_cache); + ObjectTypeDB::bind_method(_MD("_get_argument_cache"),&VisualScriptFunctionCall::_get_argument_cache); + + ObjectTypeDB::bind_method(_MD("set_rpc_call_mode","mode"),&VisualScriptFunctionCall::set_rpc_call_mode); + ObjectTypeDB::bind_method(_MD("get_rpc_call_mode"),&VisualScriptFunctionCall::get_rpc_call_mode); + + ObjectTypeDB::bind_method(_MD("set_validate","enable"),&VisualScriptFunctionCall::set_validate); + ObjectTypeDB::bind_method(_MD("get_validate"),&VisualScriptFunctionCall::get_validate); + + String bt; + for(int i=0;i<Variant::VARIANT_MAX;i++) { + if (i>0) + bt+=","; + + bt+=Variant::get_type_name(Variant::Type(i)); + } + + + List<String> script_extensions; + for(int i=0;i<ScriptServer::get_language_count();i++) { + ScriptServer::get_language(i)->get_recognized_extensions(&script_extensions); + } + + String script_ext_hint; + for (List<String>::Element *E=script_extensions.front();E;E=E->next()) { + if (script_ext_hint!=String()) + script_ext_hint+=","; + script_ext_hint+="*."+E->get(); + } + + + + ADD_PROPERTY(PropertyInfo(Variant::INT,"function/call_mode",PROPERTY_HINT_ENUM,"Self,Node Path,Instance,Basic Type,Singleton"),_SCS("set_call_mode"),_SCS("get_call_mode")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"function/base_type",PROPERTY_HINT_TYPE_STRING,"Object"),_SCS("set_base_type"),_SCS("get_base_type")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"function/base_script",PROPERTY_HINT_FILE,script_ext_hint),_SCS("set_base_script"),_SCS("get_base_script")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"function/singleton"),_SCS("set_singleton"),_SCS("get_singleton")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"function/basic_type",PROPERTY_HINT_ENUM,bt),_SCS("set_basic_type"),_SCS("get_basic_type")); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH,"function/node_path",PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE),_SCS("set_base_path"),_SCS("get_base_path")); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY,"function/argument_cache",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("_set_argument_cache"),_SCS("_get_argument_cache")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"function/function"),_SCS("set_function"),_SCS("get_function")); //when set, if loaded properly, will override argument count. + ADD_PROPERTY(PropertyInfo(Variant::INT,"function/use_default_args"),_SCS("set_use_default_args"),_SCS("get_use_default_args")); + ADD_PROPERTY(PropertyInfo(Variant::BOOL,"function/validate"),_SCS("set_validate"),_SCS("get_validate")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"rpc/call_mode",PROPERTY_HINT_ENUM,"Disabled,Reliable,Unreliable,ReliableToID,UnreliableToID"),_SCS("set_rpc_call_mode"),_SCS("get_rpc_call_mode")); //when set, if loaded properly, will override argument count. + + BIND_CONSTANT( CALL_MODE_SELF ); + BIND_CONSTANT( CALL_MODE_NODE_PATH); + BIND_CONSTANT( CALL_MODE_INSTANCE); + BIND_CONSTANT( CALL_MODE_BASIC_TYPE ); +} + +class VisualScriptNodeInstanceFunctionCall : public VisualScriptNodeInstance { +public: + + + VisualScriptFunctionCall::CallMode call_mode; + NodePath node_path; + int input_args; + bool validate; + bool returns; + VisualScriptFunctionCall::RPCCallMode rpc_mode; + StringName function; + StringName singleton; + + VisualScriptFunctionCall *node; + VisualScriptInstance *instance; + + + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + + _FORCE_INLINE_ bool call_rpc(Object* p_base,const Variant** p_args,int p_argcount) { + + if (!p_base) + return false; + + Node * node = p_base->cast_to<Node>(); + if (!node) + return false; + + int to_id=0; + bool reliable=true; + + if (rpc_mode>=VisualScriptFunctionCall::RPC_RELIABLE_TO_ID) { + to_id = *p_args[0]; + p_args+=1; + p_argcount-=1; + if (rpc_mode==VisualScriptFunctionCall::RPC_UNRELIABLE_TO_ID) { + reliable=false; + } + } else if (rpc_mode==VisualScriptFunctionCall::RPC_UNRELIABLE) { + reliable=false; + } + + node->rpcp(to_id,!reliable,function,p_args,p_argcount); + + return true; + } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + + switch(call_mode) { + + case VisualScriptFunctionCall::CALL_MODE_SELF: { + + Object *object=instance->get_owner_ptr(); + + if (rpc_mode) { + call_rpc(object,p_inputs,input_args); + } else if (returns) { + *p_outputs[0] = object->call(function,p_inputs,input_args,r_error); + } else { + object->call(function,p_inputs,input_args,r_error); + } + } break; + case VisualScriptFunctionCall::CALL_MODE_NODE_PATH: { + + Node* node = instance->get_owner_ptr()->cast_to<Node>(); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Base object is not a Node!"; + return 0; + } + + Node* another = node->get_node(node_path); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Path does not lead Node!"; + return 0; + } + + if (rpc_mode) { + call_rpc(node,p_inputs,input_args); + } else if (returns) { + *p_outputs[0] = another->call(function,p_inputs,input_args,r_error); + } else { + another->call(function,p_inputs,input_args,r_error); + } + + } break; + case VisualScriptFunctionCall::CALL_MODE_INSTANCE: + case VisualScriptFunctionCall::CALL_MODE_BASIC_TYPE: { + + Variant v = *p_inputs[0]; + + if (rpc_mode) { + Object *obj = v; + if (obj) { + call_rpc(obj,p_inputs+1,input_args-1); + } + } else if (returns) { + if (call_mode==VisualScriptFunctionCall::CALL_MODE_INSTANCE) { + *p_outputs[1] = v.call(function,p_inputs+1,input_args,r_error); + } else { + *p_outputs[0] = v.call(function,p_inputs+1,input_args,r_error); + } + } else { + v.call(function,p_inputs+1,input_args,r_error); + } + + if (call_mode==VisualScriptFunctionCall::CALL_MODE_INSTANCE) { + *p_outputs[0]=*p_inputs[0]; + } + + } break; + case VisualScriptFunctionCall::CALL_MODE_SINGLETON: { + + Object *object=Globals::get_singleton()->get_singleton_object(singleton); + if (!object) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Invalid singleton name: '"+String(singleton)+"'"; + return 0; + } + + if (rpc_mode) { + call_rpc(object,p_inputs,input_args); + } else if (returns) { + *p_outputs[0] = object->call(function,p_inputs,input_args,r_error); + } else { + object->call(function,p_inputs,input_args,r_error); + } + } break; + + } + + if (!validate) { + + //ignore call errors if validation is disabled + r_error.error=Variant::CallError::CALL_OK; + r_error_str=String(); + } + + return 0; + + } + + +}; + +VisualScriptNodeInstance* VisualScriptFunctionCall::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceFunctionCall * instance = memnew(VisualScriptNodeInstanceFunctionCall ); + instance->node=this; + instance->instance=p_instance; + instance->singleton=singleton; + instance->function=function; + instance->call_mode=call_mode; + instance->returns=get_output_value_port_count(); + instance->node_path=base_path; + instance->input_args = get_input_value_port_count() - ( (call_mode==CALL_MODE_BASIC_TYPE || call_mode==CALL_MODE_INSTANCE) ? 1: 0 ); + instance->rpc_mode=rpc_call_mode; + instance->validate=validate; + return instance; +} + + +VisualScriptFunctionCall::TypeGuess VisualScriptFunctionCall::guess_output_type(TypeGuess* p_inputs, int p_output) const { + + if (p_output==0 && call_mode==CALL_MODE_INSTANCE) { + return p_inputs[0]; + } + + return VisualScriptNode::guess_output_type(p_inputs,p_output); + +} + +VisualScriptFunctionCall::VisualScriptFunctionCall() { + + validate=true; + call_mode=CALL_MODE_SELF; + basic_type=Variant::NIL; + use_default_args=0; + base_type="Object"; + rpc_call_mode=RPC_DISABLED; + + +} + +template<VisualScriptFunctionCall::CallMode cmode> +static Ref<VisualScriptNode> create_function_call_node(const String& p_name) { + + Ref<VisualScriptFunctionCall> node; + node.instance(); + node->set_call_mode(cmode); + return node; +} + + +////////////////////////////////////////// +////////////////SET////////////////////// +////////////////////////////////////////// + +static const char* event_type_names[InputEvent::TYPE_MAX]={ + "None", + "Key", + "MouseMotion", + "MouseButton", + "JoystickMotion", + "JoystickButton", + "ScreenTouch", + "ScreenDrag", + "Action" +}; + + +int VisualScriptPropertySet::get_output_sequence_port_count() const { + + return call_mode!=CALL_MODE_BASIC_TYPE ? 1 : 0; +} + +bool VisualScriptPropertySet::has_input_sequence_port() const{ + + return call_mode!=CALL_MODE_BASIC_TYPE ? true : false; +} + +Node *VisualScriptPropertySet::_get_base_node() const { + +#ifdef TOOLS_ENABLED + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return NULL; + + MainLoop * main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) + return NULL; + + SceneTree *scene_tree = main_loop->cast_to<SceneTree>(); + + if (!scene_tree) + return NULL; + + Node *edited_scene = scene_tree->get_edited_scene_root(); + + if (!edited_scene) + return NULL; + + Node* script_node = _find_script_node(edited_scene,edited_scene,script); + + if (!script_node) + return NULL; + + if (!script_node->has_node(base_path)) + return NULL; + + Node *path_to = script_node->get_node(base_path); + + return path_to; +#else + + return NULL; +#endif +} + +StringName VisualScriptPropertySet::_get_base_type() const { + + if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) + return get_visual_script()->get_instance_base_type(); + else if (call_mode==CALL_MODE_NODE_PATH && get_visual_script().is_valid()) { + Node *path = _get_base_node(); + if (path) + return path->get_type(); + + } + + return base_type; +} + +int VisualScriptPropertySet::get_input_value_port_count() const{ + + int pc = (call_mode==CALL_MODE_BASIC_TYPE || call_mode==CALL_MODE_INSTANCE)?2:1; + + return pc; +} +int VisualScriptPropertySet::get_output_value_port_count() const{ + + return (call_mode==CALL_MODE_BASIC_TYPE || call_mode==CALL_MODE_INSTANCE) ? 1 : 0; +} + +String VisualScriptPropertySet::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptPropertySet::get_input_value_port_info(int p_idx) const{ + + if (call_mode==CALL_MODE_INSTANCE || call_mode==CALL_MODE_BASIC_TYPE) { + if (p_idx==0) { + PropertyInfo pi; + pi.type=(call_mode==CALL_MODE_INSTANCE?Variant::OBJECT:basic_type); + pi.name=(call_mode==CALL_MODE_INSTANCE?String("instance"):Variant::get_type_name(basic_type).to_lower()); + return pi; + } else { + p_idx--; + } + } + + PropertyInfo pinfo=type_cache; + pinfo.name="value"; + return pinfo; +} + +PropertyInfo VisualScriptPropertySet::get_output_value_port_info(int p_idx) const{ + if (call_mode==CALL_MODE_BASIC_TYPE) { + return PropertyInfo(basic_type,"out"); + } else if (call_mode==CALL_MODE_INSTANCE) { + return PropertyInfo(Variant::OBJECT,"pass"); + } else { + return PropertyInfo(); + } + +} + + +String VisualScriptPropertySet::get_caption() const { + + static const char*cname[4]= { + "SelfSet", + "NodeSet", + "InstanceSet", + "BasicSet" + }; + + return cname[call_mode]; +} + +String VisualScriptPropertySet::get_text() const { + + String prop; + + if (call_mode==CALL_MODE_BASIC_TYPE) + prop=Variant::get_type_name(basic_type)+"."+property; + else if (call_mode==CALL_MODE_NODE_PATH) + prop=String(base_path)+":"+property; + else if (call_mode==CALL_MODE_SELF) + prop=property; + else if (call_mode==CALL_MODE_INSTANCE) + prop=String(base_type)+":"+property; + + return prop; + +} + +void VisualScriptPropertySet::_update_base_type() { + //cache it because this information may not be available on load + if (call_mode==CALL_MODE_NODE_PATH) { + + Node* node=_get_base_node(); + if (node) { + base_type=node->get_type(); + } + } else if (call_mode==CALL_MODE_SELF) { + + if (get_visual_script().is_valid()) { + base_type=get_visual_script()->get_instance_base_type(); + } + } + +} +void VisualScriptPropertySet::set_basic_type(Variant::Type p_type) { + + if (basic_type==p_type) + return; + basic_type=p_type; + + + _change_notify(); + _update_base_type(); + ports_changed_notify(); +} + +Variant::Type VisualScriptPropertySet::get_basic_type() const{ + + return basic_type; +} + +void VisualScriptPropertySet::set_event_type(InputEvent::Type p_type) { + + if (event_type==p_type) + return; + event_type=p_type; + if (call_mode==CALL_MODE_BASIC_TYPE) { + _update_cache(); + } + _change_notify(); + _update_base_type(); + ports_changed_notify(); +} + +InputEvent::Type VisualScriptPropertySet::get_event_type() const{ + + return event_type; +} + + +void VisualScriptPropertySet::set_base_type(const StringName& p_type) { + + if (base_type==p_type) + return; + + base_type=p_type; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptPropertySet::get_base_type() const{ + + return base_type; +} + + +void VisualScriptPropertySet::set_base_script(const String& p_path) { + + if (base_script==p_path) + return; + + base_script=p_path; + _change_notify(); + ports_changed_notify(); +} + +String VisualScriptPropertySet::get_base_script() const { + + return base_script; +} + + +void VisualScriptPropertySet::_update_cache() { + + + if (!OS::get_singleton()->get_main_loop()) + return; + if (!OS::get_singleton()->get_main_loop()->cast_to<SceneTree>()) + return; + + if (!OS::get_singleton()->get_main_loop()->cast_to<SceneTree>()->is_editor_hint()) //only update cache if editor exists, it's pointless otherwise + return; + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + //not super efficient.. + + Variant v; + if (basic_type==Variant::INPUT_EVENT) { + InputEvent ev; + ev.type=event_type; + v=ev; + } else { + Variant::CallError ce; + v = Variant::construct(basic_type,NULL,0,ce); + } + + List<PropertyInfo> pinfo; + v.get_property_list(&pinfo); + + for (List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + + if (E->get().name==property) { + + type_cache=E->get(); + } + } + + } else { + + + StringName type; + Ref<Script> script; + Node *node=NULL; + + if (call_mode==CALL_MODE_NODE_PATH) { + + node=_get_base_node(); + if (node) { + type=node->get_type(); + base_type=type; //cache, too + script = node->get_script(); + } + } else if (call_mode==CALL_MODE_SELF) { + + if (get_visual_script().is_valid()) { + type=get_visual_script()->get_instance_base_type(); + base_type=type; //cache, too + script=get_visual_script(); + } + } else if (call_mode==CALL_MODE_INSTANCE) { + + type=base_type; + if (base_script!=String()) { + + if (!ResourceCache::has(base_script) && ScriptServer::edit_request_func) { + + ScriptServer::edit_request_func(base_script); //make sure it's loaded + } + + if (ResourceCache::has(base_script)) { + + script = Ref<Resource>( ResourceCache::get(base_script) ); + } else { + return; + } + } + } + + List<PropertyInfo> pinfo; + + + if (node) { + + node->get_property_list(&pinfo); + } else { + ObjectTypeDB::get_property_list(type,&pinfo); + } + + if (script.is_valid()) { + + script->get_script_property_list(&pinfo); + } + + for (List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + + if (E->get().name==property) { + type_cache=E->get(); + return; + } + } + + } +} + +void VisualScriptPropertySet::set_property(const StringName& p_type){ + + if (property==p_type) + return; + + property=p_type; + _update_cache(); + _change_notify(); + ports_changed_notify(); +} +StringName VisualScriptPropertySet::get_property() const { + + + return property; +} + +void VisualScriptPropertySet::set_base_path(const NodePath& p_type) { + + if (base_path==p_type) + return; + + base_path=p_type; + _update_base_type(); + _change_notify(); + ports_changed_notify(); +} + +NodePath VisualScriptPropertySet::get_base_path() const { + + return base_path; +} + + +void VisualScriptPropertySet::set_call_mode(CallMode p_mode) { + + if (call_mode==p_mode) + return; + + call_mode=p_mode; + _update_base_type(); + _change_notify(); + ports_changed_notify(); + +} +VisualScriptPropertySet::CallMode VisualScriptPropertySet::get_call_mode() const { + + return call_mode; +} + + + + +void VisualScriptPropertySet::_set_type_cache(const Dictionary &p_type) { + type_cache=PropertyInfo::from_dict(p_type); +} + +Dictionary VisualScriptPropertySet::_get_type_cache() const { + + return type_cache; +} + +void VisualScriptPropertySet::_validate_property(PropertyInfo& property) const { + + if (property.name=="property/base_type") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=PROPERTY_USAGE_NOEDITOR; + } + } + + if (property.name=="property/base_script") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=0; + } + } + + if (property.name=="property/basic_type") { + if (call_mode!=CALL_MODE_BASIC_TYPE) { + property.usage=0; + } + } + + if (property.name=="property/event_type") { + if (call_mode!=CALL_MODE_BASIC_TYPE || basic_type!=Variant::INPUT_EVENT) { + property.usage=0; + } + } + + if (property.name=="property/node_path") { + if (call_mode!=CALL_MODE_NODE_PATH) { + property.usage=0; + } else { + + Node *bnode = _get_base_node(); + if (bnode) { + property.hint_string=bnode->get_path(); //convert to loong string + } else { + + } + } + } + + if (property.name=="property/property") { + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + property.hint=PROPERTY_HINT_PROPERTY_OF_VARIANT_TYPE; + property.hint_string=Variant::get_type_name(basic_type); + + } else if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) { + property.hint=PROPERTY_HINT_PROPERTY_OF_SCRIPT; + property.hint_string=itos(get_visual_script()->get_instance_ID()); + } else if (call_mode==CALL_MODE_INSTANCE) { + property.hint=PROPERTY_HINT_PROPERTY_OF_BASE_TYPE; + property.hint_string=base_type; + + if (base_script!=String()) { + if (!ResourceCache::has(base_script) && ScriptServer::edit_request_func) { + + ScriptServer::edit_request_func(base_script); //make sure it's loaded + } + + if (ResourceCache::has(base_script)) { + + Ref<Script> script = Ref<Resource>( ResourceCache::get(base_script) ); + if (script.is_valid()) { + + property.hint=PROPERTY_HINT_PROPERTY_OF_SCRIPT; + property.hint_string=itos(script->get_instance_ID()); + } + } + } + + } else if (call_mode==CALL_MODE_NODE_PATH) { + Node *node = _get_base_node(); + if (node) { + property.hint=PROPERTY_HINT_PROPERTY_OF_INSTANCE; + property.hint_string=itos(node->get_instance_ID()); + } else { + property.hint=PROPERTY_HINT_PROPERTY_OF_BASE_TYPE; + property.hint_string=get_base_type(); + } + + } + + } + +} + +void VisualScriptPropertySet::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_base_type","base_type"),&VisualScriptPropertySet::set_base_type); + ObjectTypeDB::bind_method(_MD("get_base_type"),&VisualScriptPropertySet::get_base_type); + + ObjectTypeDB::bind_method(_MD("set_base_script","base_script"),&VisualScriptPropertySet::set_base_script); + ObjectTypeDB::bind_method(_MD("get_base_script"),&VisualScriptPropertySet::get_base_script); + + ObjectTypeDB::bind_method(_MD("set_basic_type","basic_type"),&VisualScriptPropertySet::set_basic_type); + ObjectTypeDB::bind_method(_MD("get_basic_type"),&VisualScriptPropertySet::get_basic_type); + + ObjectTypeDB::bind_method(_MD("_set_type_cache","type_cache"),&VisualScriptPropertySet::_set_type_cache); + ObjectTypeDB::bind_method(_MD("_get_type_cache"),&VisualScriptPropertySet::_get_type_cache); + + ObjectTypeDB::bind_method(_MD("set_event_type","event_type"),&VisualScriptPropertySet::set_event_type); + ObjectTypeDB::bind_method(_MD("get_event_type"),&VisualScriptPropertySet::get_event_type); + + ObjectTypeDB::bind_method(_MD("set_property","property"),&VisualScriptPropertySet::set_property); + ObjectTypeDB::bind_method(_MD("get_property"),&VisualScriptPropertySet::get_property); + + ObjectTypeDB::bind_method(_MD("set_call_mode","mode"),&VisualScriptPropertySet::set_call_mode); + ObjectTypeDB::bind_method(_MD("get_call_mode"),&VisualScriptPropertySet::get_call_mode); + + ObjectTypeDB::bind_method(_MD("set_base_path","base_path"),&VisualScriptPropertySet::set_base_path); + ObjectTypeDB::bind_method(_MD("get_base_path"),&VisualScriptPropertySet::get_base_path); + + + + String bt; + for(int i=0;i<Variant::VARIANT_MAX;i++) { + if (i>0) + bt+=","; + + bt+=Variant::get_type_name(Variant::Type(i)); + } + + String et; + for(int i=0;i<InputEvent::TYPE_MAX;i++) { + if (i>0) + et+=","; + + et+=event_type_names[i]; + } + + List<String> script_extensions; + for(int i=0;i<ScriptServer::get_language_count();i++) { + ScriptServer::get_language(i)->get_recognized_extensions(&script_extensions); + } + + String script_ext_hint; + for (List<String>::Element *E=script_extensions.front();E;E=E->next()) { + if (script_ext_hint!=String()) + script_ext_hint+=","; + script_ext_hint+="*."+E->get(); + } + + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/set_mode",PROPERTY_HINT_ENUM,"Self,Node Path,Instance,Basic Type"),_SCS("set_call_mode"),_SCS("get_call_mode")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/base_type",PROPERTY_HINT_TYPE_STRING,"Object"),_SCS("set_base_type"),_SCS("get_base_type")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/base_script",PROPERTY_HINT_FILE,script_ext_hint),_SCS("set_base_script"),_SCS("get_base_script")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/type_cache",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("_set_type_cache"),_SCS("_get_type_cache")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/basic_type",PROPERTY_HINT_ENUM,bt),_SCS("set_basic_type"),_SCS("get_basic_type")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/event_type",PROPERTY_HINT_ENUM,et),_SCS("set_event_type"),_SCS("get_event_type")); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH,"property/node_path",PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE),_SCS("set_base_path"),_SCS("get_base_path")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/property"),_SCS("set_property"),_SCS("get_property")); + + BIND_CONSTANT( CALL_MODE_SELF ); + BIND_CONSTANT( CALL_MODE_NODE_PATH); + BIND_CONSTANT( CALL_MODE_INSTANCE); + +} + +class VisualScriptNodeInstancePropertySet : public VisualScriptNodeInstance { +public: + + + VisualScriptPropertySet::CallMode call_mode; + NodePath node_path; + StringName property; + + VisualScriptPropertySet *node; + VisualScriptInstance *instance; + + + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + + switch(call_mode) { + + case VisualScriptPropertySet::CALL_MODE_SELF: { + + Object *object=instance->get_owner_ptr(); + + bool valid; + + object->set(property,*p_inputs[0],&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Invalid set value '"+String(*p_inputs[0])+"' on property '"+String(property)+"' of type "+object->get_type(); + } + } break; + case VisualScriptPropertySet::CALL_MODE_NODE_PATH: { + + Node* node = instance->get_owner_ptr()->cast_to<Node>(); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Base object is not a Node!"; + return 0; + } + + Node* another = node->get_node(node_path); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Path does not lead Node!"; + return 0; + } + + bool valid; + + another->set(property,*p_inputs[0],&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Invalid set value '"+String(*p_inputs[0])+"' on property '"+String(property)+"' of type "+another->get_type(); + } + + } break; + case VisualScriptPropertySet::CALL_MODE_INSTANCE: + case VisualScriptPropertySet::CALL_MODE_BASIC_TYPE: { + + Variant v = *p_inputs[0]; + + bool valid; + + v.set(property,*p_inputs[1],&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Invalid set value '"+String(*p_inputs[1])+"' ("+Variant::get_type_name(p_inputs[1]->get_type())+") on property '"+String(property)+"' of type "+Variant::get_type_name(v.get_type()); + } + + *p_outputs[0]=v; + + } break; + + } + return 0; + + } + + +}; + +VisualScriptNodeInstance* VisualScriptPropertySet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstancePropertySet * instance = memnew(VisualScriptNodeInstancePropertySet ); + instance->node=this; + instance->instance=p_instance; + instance->property=property; + instance->call_mode=call_mode; + instance->node_path=base_path; + return instance; +} + + + +VisualScriptPropertySet::TypeGuess VisualScriptPropertySet::guess_output_type(TypeGuess* p_inputs, int p_output) const { + + if (p_output==0 && call_mode==CALL_MODE_INSTANCE) { + return p_inputs[0]; + } + + return VisualScriptNode::guess_output_type(p_inputs,p_output); + +} +VisualScriptPropertySet::VisualScriptPropertySet() { + + call_mode=CALL_MODE_SELF; + base_type="Object"; + basic_type=Variant::NIL; + event_type=InputEvent::NONE; + +} + +template<VisualScriptPropertySet::CallMode cmode> +static Ref<VisualScriptNode> create_property_set_node(const String& p_name) { + + Ref<VisualScriptPropertySet> node; + node.instance(); + node->set_call_mode(cmode); + return node; +} + + +////////////////////////////////////////// +////////////////GET////////////////////// +////////////////////////////////////////// + +int VisualScriptPropertyGet::get_output_sequence_port_count() const { + + return 0;// (call_mode==CALL_MODE_SELF || call_mode==CALL_MODE_NODE_PATH)?0:1; +} + +bool VisualScriptPropertyGet::has_input_sequence_port() const{ + + return false;//(call_mode==CALL_MODE_SELF || call_mode==CALL_MODE_NODE_PATH)?false:true; +} +void VisualScriptPropertyGet::_update_base_type() { + //cache it because this information may not be available on load + if (call_mode==CALL_MODE_NODE_PATH) { + + Node* node=_get_base_node(); + if (node) { + base_type=node->get_type(); + } + } else if (call_mode==CALL_MODE_SELF) { + + if (get_visual_script().is_valid()) { + base_type=get_visual_script()->get_instance_base_type(); + } + } + +} +Node *VisualScriptPropertyGet::_get_base_node() const { + +#ifdef TOOLS_ENABLED + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return NULL; + + MainLoop * main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) + return NULL; + + SceneTree *scene_tree = main_loop->cast_to<SceneTree>(); + + if (!scene_tree) + return NULL; + + Node *edited_scene = scene_tree->get_edited_scene_root(); + + if (!edited_scene) + return NULL; + + Node* script_node = _find_script_node(edited_scene,edited_scene,script); + + if (!script_node) + return NULL; + + if (!script_node->has_node(base_path)) + return NULL; + + Node *path_to = script_node->get_node(base_path); + + return path_to; +#else + + return NULL; +#endif +} + +StringName VisualScriptPropertyGet::_get_base_type() const { + + if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) + return get_visual_script()->get_instance_base_type(); + else if (call_mode==CALL_MODE_NODE_PATH && get_visual_script().is_valid()) { + Node *path = _get_base_node(); + if (path) + return path->get_type(); + + } + + return base_type; +} + + +int VisualScriptPropertyGet::get_input_value_port_count() const{ + + return (call_mode==CALL_MODE_BASIC_TYPE || call_mode==CALL_MODE_INSTANCE)?1:0; + +} +int VisualScriptPropertyGet::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptPropertyGet::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptPropertyGet::get_input_value_port_info(int p_idx) const{ + + if (call_mode==CALL_MODE_INSTANCE || call_mode==CALL_MODE_BASIC_TYPE) { + if (p_idx==0) { + PropertyInfo pi; + pi.type=(call_mode==CALL_MODE_INSTANCE?Variant::OBJECT:basic_type); + pi.name=(call_mode==CALL_MODE_INSTANCE?String("instance"):Variant::get_type_name(basic_type).to_lower()); + return pi; + } else { + p_idx--; + } + } + return PropertyInfo(); + +} + +PropertyInfo VisualScriptPropertyGet::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(type_cache,"value"); +} + + +String VisualScriptPropertyGet::get_caption() const { + + static const char*cname[4]= { + "SelfGet", + "NodeGet", + "InstanceGet", + "BasicGet" + }; + + return cname[call_mode]; +} + +String VisualScriptPropertyGet::get_text() const { + + String prop; + + if (call_mode==CALL_MODE_BASIC_TYPE) + prop=Variant::get_type_name(basic_type)+"."+property; + else if (call_mode==CALL_MODE_NODE_PATH) + prop=String(base_path)+":"+property; + else if (call_mode==CALL_MODE_SELF) + prop=property; + else if (call_mode==CALL_MODE_INSTANCE) + prop=String(base_type)+":"+property; + + return prop; +} + +void VisualScriptPropertyGet::set_base_type(const StringName& p_type) { + + if (base_type==p_type) + return; + + base_type=p_type; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptPropertyGet::get_base_type() const{ + + return base_type; +} + +void VisualScriptPropertyGet::set_base_script(const String& p_path) { + + if (base_script==p_path) + return; + + base_script=p_path; + _change_notify(); + ports_changed_notify(); +} + +String VisualScriptPropertyGet::get_base_script() const { + + return base_script; +} + + +void VisualScriptPropertyGet::_update_cache() { + + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + //not super efficient.. + + Variant v; + if (basic_type==Variant::INPUT_EVENT) { + InputEvent ev; + ev.type=event_type; + v=ev; + } else { + Variant::CallError ce; + v = Variant::construct(basic_type,NULL,0,ce); + } + + List<PropertyInfo> pinfo; + v.get_property_list(&pinfo); + + for (List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + + if (E->get().name==property) { + + type_cache=E->get().type; + return; + } + } + + } else { + + + StringName type; + Ref<Script> script; + Node *node=NULL; + + if (call_mode==CALL_MODE_NODE_PATH) { + + node=_get_base_node(); + if (node) { + type=node->get_type(); + base_type=type; //cache, too + script = node->get_script(); + } + } else if (call_mode==CALL_MODE_SELF) { + + if (get_visual_script().is_valid()) { + type=get_visual_script()->get_instance_base_type(); + base_type=type; //cache, too + script=get_visual_script(); + } + } else if (call_mode==CALL_MODE_INSTANCE) { + + type=base_type; + if (base_script!=String()) { + + if (!ResourceCache::has(base_script) && ScriptServer::edit_request_func) { + + ScriptServer::edit_request_func(base_script); //make sure it's loaded + } + + if (ResourceCache::has(base_script)) { + + script = Ref<Resource>( ResourceCache::get(base_script) ); + } else { + return; + } + } + } + + + bool valid=false; + + Variant::Type type_ret; + + type_ret=ObjectTypeDB::get_property_type(base_type,property,&valid); + + if (valid) { + type_cache=type_ret; + return; //all dandy + } + + if (node) { + + Variant prop = node->get(property,&valid); + if (valid) { + type_cache=prop.get_type(); + return; //all dandy again + } + } + + if (script.is_valid()) { + + type_ret=script->get_static_property_type(property,&valid); + + if (valid) { + type_cache=type_ret; + return; //all dandy + } + } + } +} + +void VisualScriptPropertyGet::set_property(const StringName& p_type){ + + if (property==p_type) + return; + + property=p_type; + + + _update_cache(); + _change_notify(); + ports_changed_notify(); +} +StringName VisualScriptPropertyGet::get_property() const { + + + return property; +} + +void VisualScriptPropertyGet::set_base_path(const NodePath& p_type) { + + if (base_path==p_type) + return; + + base_path=p_type; + _change_notify(); + _update_base_type(); + ports_changed_notify(); +} + +NodePath VisualScriptPropertyGet::get_base_path() const { + + return base_path; +} + + +void VisualScriptPropertyGet::set_call_mode(CallMode p_mode) { + + if (call_mode==p_mode) + return; + + call_mode=p_mode; + _change_notify(); + _update_base_type(); + ports_changed_notify(); + +} +VisualScriptPropertyGet::CallMode VisualScriptPropertyGet::get_call_mode() const { + + return call_mode; +} + + + +void VisualScriptPropertyGet::set_basic_type(Variant::Type p_type) { + + if (basic_type==p_type) + return; + basic_type=p_type; + + + _change_notify(); + ports_changed_notify(); +} + +Variant::Type VisualScriptPropertyGet::get_basic_type() const{ + + return basic_type; +} + + +void VisualScriptPropertyGet::set_event_type(InputEvent::Type p_type) { + + if (event_type==p_type) + return; + event_type=p_type; + if(call_mode==CALL_MODE_BASIC_TYPE) { + _update_cache(); + } + _change_notify(); + _update_base_type(); + ports_changed_notify(); +} + +InputEvent::Type VisualScriptPropertyGet::get_event_type() const{ + + return event_type; +} + + +void VisualScriptPropertyGet::_set_type_cache(Variant::Type p_type) { + type_cache=p_type; +} + +Variant::Type VisualScriptPropertyGet::_get_type_cache() const { + + return type_cache; +} + + +void VisualScriptPropertyGet::_validate_property(PropertyInfo& property) const { + + if (property.name=="property/base_type") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=PROPERTY_USAGE_NOEDITOR; + } + } + + if (property.name=="property/base_script") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=0; + } + } + + if (property.name=="property/basic_type") { + if (call_mode!=CALL_MODE_BASIC_TYPE) { + property.usage=0; + } + } + if (property.name=="property/event_type") { + if (call_mode!=CALL_MODE_BASIC_TYPE || basic_type!=Variant::INPUT_EVENT) { + property.usage=0; + } + } + + if (property.name=="property/node_path") { + if (call_mode!=CALL_MODE_NODE_PATH) { + property.usage=0; + } else { + + Node *bnode = _get_base_node(); + if (bnode) { + property.hint_string=bnode->get_path(); //convert to loong string + } else { + + } + } + } + + if (property.name=="property/property") { + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + property.hint=PROPERTY_HINT_PROPERTY_OF_VARIANT_TYPE; + property.hint_string=Variant::get_type_name(basic_type); + + } else if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) { + property.hint=PROPERTY_HINT_PROPERTY_OF_SCRIPT; + property.hint_string=itos(get_visual_script()->get_instance_ID()); + } else if (call_mode==CALL_MODE_INSTANCE) { + property.hint=PROPERTY_HINT_PROPERTY_OF_BASE_TYPE; + property.hint_string=base_type; + + if (base_script!=String()) { + if (!ResourceCache::has(base_script) && ScriptServer::edit_request_func) { + + ScriptServer::edit_request_func(base_script); //make sure it's loaded + } + + if (ResourceCache::has(base_script)) { + + Ref<Script> script = Ref<Resource>( ResourceCache::get(base_script) ); + if (script.is_valid()) { + + property.hint=PROPERTY_HINT_PROPERTY_OF_SCRIPT; + property.hint_string=itos(script->get_instance_ID()); + } + } + } + } else if (call_mode==CALL_MODE_NODE_PATH) { + Node *node = _get_base_node(); + if (node) { + property.hint=PROPERTY_HINT_PROPERTY_OF_INSTANCE; + property.hint_string=itos(node->get_instance_ID()); + } else { + property.hint=PROPERTY_HINT_PROPERTY_OF_BASE_TYPE; + property.hint_string=get_base_type(); + } + + } + + } + +} + +void VisualScriptPropertyGet::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_base_type","base_type"),&VisualScriptPropertyGet::set_base_type); + ObjectTypeDB::bind_method(_MD("get_base_type"),&VisualScriptPropertyGet::get_base_type); + + ObjectTypeDB::bind_method(_MD("set_base_script","base_script"),&VisualScriptPropertyGet::set_base_script); + ObjectTypeDB::bind_method(_MD("get_base_script"),&VisualScriptPropertyGet::get_base_script); + + ObjectTypeDB::bind_method(_MD("set_basic_type","basic_type"),&VisualScriptPropertyGet::set_basic_type); + ObjectTypeDB::bind_method(_MD("get_basic_type"),&VisualScriptPropertyGet::get_basic_type); + + ObjectTypeDB::bind_method(_MD("_set_type_cache","type_cache"),&VisualScriptPropertyGet::_set_type_cache); + ObjectTypeDB::bind_method(_MD("_get_type_cache"),&VisualScriptPropertyGet::_get_type_cache); + + ObjectTypeDB::bind_method(_MD("set_event_type","event_type"),&VisualScriptPropertyGet::set_event_type); + ObjectTypeDB::bind_method(_MD("get_event_type"),&VisualScriptPropertyGet::get_event_type); + + + ObjectTypeDB::bind_method(_MD("set_property","property"),&VisualScriptPropertyGet::set_property); + ObjectTypeDB::bind_method(_MD("get_property"),&VisualScriptPropertyGet::get_property); + + ObjectTypeDB::bind_method(_MD("set_call_mode","mode"),&VisualScriptPropertyGet::set_call_mode); + ObjectTypeDB::bind_method(_MD("get_call_mode"),&VisualScriptPropertyGet::get_call_mode); + + ObjectTypeDB::bind_method(_MD("set_base_path","base_path"),&VisualScriptPropertyGet::set_base_path); + ObjectTypeDB::bind_method(_MD("get_base_path"),&VisualScriptPropertyGet::get_base_path); + + String bt; + for(int i=0;i<Variant::VARIANT_MAX;i++) { + if (i>0) + bt+=","; + + bt+=Variant::get_type_name(Variant::Type(i)); + } + + String et; + for(int i=0;i<InputEvent::TYPE_MAX;i++) { + if (i>0) + et+=","; + + et+=event_type_names[i]; + } + + List<String> script_extensions; + for(int i=0;i<ScriptServer::get_language_count();i++) { + ScriptServer::get_language(i)->get_recognized_extensions(&script_extensions); + } + + String script_ext_hint; + for (List<String>::Element *E=script_extensions.front();E;E=E->next()) { + if (script_ext_hint!=String()) + script_ext_hint+=","; + script_ext_hint+="."+E->get(); + } + + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/set_mode",PROPERTY_HINT_ENUM,"Self,Node Path,Instance,Basic Type"),_SCS("set_call_mode"),_SCS("get_call_mode")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/base_type",PROPERTY_HINT_TYPE_STRING,"Object"),_SCS("set_base_type"),_SCS("get_base_type")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/base_script",PROPERTY_HINT_FILE,script_ext_hint),_SCS("set_base_script"),_SCS("get_base_script")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/type_cache",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("_set_type_cache"),_SCS("_get_type_cache")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/basic_type",PROPERTY_HINT_ENUM,bt),_SCS("set_basic_type"),_SCS("get_basic_type")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/event_type",PROPERTY_HINT_ENUM,et),_SCS("set_event_type"),_SCS("get_event_type")); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH,"property/node_path",PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE),_SCS("set_base_path"),_SCS("get_base_path")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/property"),_SCS("set_property"),_SCS("get_property")); + + BIND_CONSTANT( CALL_MODE_SELF ); + BIND_CONSTANT( CALL_MODE_NODE_PATH); + BIND_CONSTANT( CALL_MODE_INSTANCE); +} + +class VisualScriptNodeInstancePropertyGet : public VisualScriptNodeInstance { +public: + + + VisualScriptPropertyGet::CallMode call_mode; + NodePath node_path; + StringName property; + + VisualScriptPropertyGet *node; + VisualScriptInstance *instance; + + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + + + switch(call_mode) { + + case VisualScriptPropertyGet::CALL_MODE_SELF: { + + Object *object=instance->get_owner_ptr(); + + bool valid; + + *p_outputs[0] = object->get(property,&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Invalid index property name."); + return 0; + } + } break; + case VisualScriptPropertyGet::CALL_MODE_NODE_PATH: { + + Node* node = instance->get_owner_ptr()->cast_to<Node>(); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Base object is not a Node!"); + return 0; + } + + Node* another = node->get_node(node_path); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Path does not lead Node!"); + return 0; + } + + bool valid; + + + *p_outputs[0] = another->get(property,&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=vformat(RTR("Invalid index property name '%s' in node %s."),String(property),another->get_name()); + return 0; + } + + } break; + default: { + + bool valid; + Variant v = *p_inputs[0]; + + *p_outputs[0] = v.get(property,&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Invalid index property name."); + + } + }; + } + + return 0; + } + + + + +}; + +VisualScriptNodeInstance* VisualScriptPropertyGet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstancePropertyGet * instance = memnew(VisualScriptNodeInstancePropertyGet ); + instance->node=this; + instance->instance=p_instance; + instance->property=property; + instance->call_mode=call_mode; + instance->node_path=base_path; + + return instance; +} + +VisualScriptPropertyGet::VisualScriptPropertyGet() { + + call_mode=CALL_MODE_SELF; + base_type="Object"; + basic_type=Variant::NIL; + event_type=InputEvent::NONE; + type_cache=Variant::NIL; + +} + +template<VisualScriptPropertyGet::CallMode cmode> +static Ref<VisualScriptNode> create_property_get_node(const String& p_name) { + + Ref<VisualScriptPropertyGet> node; + node.instance(); + node->set_call_mode(cmode); + return node; +} + + +////////////////////////////////////////// +////////////////EMIT////////////////////// +////////////////////////////////////////// + +int VisualScriptEmitSignal::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptEmitSignal::has_input_sequence_port() const{ + + return true; +} + + +int VisualScriptEmitSignal::get_input_value_port_count() const{ + + Ref<VisualScript> vs = get_visual_script(); + if (vs.is_valid()) { + + if (!vs->has_custom_signal(name)) + return 0; + + return vs->custom_signal_get_argument_count(name); + } + + return 0; + +} +int VisualScriptEmitSignal::get_output_value_port_count() const{ + return 0; +} + +String VisualScriptEmitSignal::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptEmitSignal::get_input_value_port_info(int p_idx) const{ + + Ref<VisualScript> vs = get_visual_script(); + if (vs.is_valid()) { + + if (!vs->has_custom_signal(name)) + return PropertyInfo(); + + return PropertyInfo(vs->custom_signal_get_argument_type(name,p_idx),vs->custom_signal_get_argument_name(name,p_idx)); + } + + return PropertyInfo(); + +} + +PropertyInfo VisualScriptEmitSignal::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptEmitSignal::get_caption() const { + + return "EmitSignal"; +} + +String VisualScriptEmitSignal::get_text() const { + + return "emit "+String(name); +} + + + +void VisualScriptEmitSignal::set_signal(const StringName& p_type){ + + if (name==p_type) + return; + + name=p_type; + + _change_notify(); + ports_changed_notify(); +} +StringName VisualScriptEmitSignal::get_signal() const { + + + return name; +} + + +void VisualScriptEmitSignal::_validate_property(PropertyInfo& property) const { + + + + if (property.name=="signal/signal") { + property.hint=PROPERTY_HINT_ENUM; + + + List<StringName> sigs; + + Ref<VisualScript> vs = get_visual_script(); + if (vs.is_valid()) { + + vs->get_custom_signal_list(&sigs); + + } + + String ml; + for (List<StringName>::Element *E=sigs.front();E;E=E->next()) { + + if (ml!=String()) + ml+=","; + ml+=E->get(); + } + + property.hint_string=ml; + } + +} + + +void VisualScriptEmitSignal::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_signal","name"),&VisualScriptEmitSignal::set_signal); + ObjectTypeDB::bind_method(_MD("get_signal"),&VisualScriptEmitSignal::get_signal); + + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"signal/signal"),_SCS("set_signal"),_SCS("get_signal")); + + +} + +class VisualScriptNodeInstanceEmitSignal : public VisualScriptNodeInstance { +public: + + VisualScriptEmitSignal *node; + VisualScriptInstance *instance; + int argcount; + StringName name; + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + + Object *obj = instance->get_owner_ptr(); + + obj->emit_signal(name,p_inputs,argcount); + + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptEmitSignal::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceEmitSignal * instance = memnew(VisualScriptNodeInstanceEmitSignal ); + instance->node=this; + instance->instance=p_instance; + instance->name=name; + instance->argcount=get_input_value_port_count(); + return instance; +} + +VisualScriptEmitSignal::VisualScriptEmitSignal() { +} + + + +static Ref<VisualScriptNode> create_basic_type_call_node(const String& p_name) { + + Vector<String> path = p_name.split("/"); + ERR_FAIL_COND_V(path.size()<4,Ref<VisualScriptNode>()); + String base_type = path[2]; + String method = path[3]; + + Ref<VisualScriptFunctionCall> node; + node.instance(); + + Variant::Type type=Variant::VARIANT_MAX; + + for(int i=0;i<Variant::VARIANT_MAX;i++) { + + if (Variant::get_type_name(Variant::Type(i))==base_type) { + type=Variant::Type(i); + break; + } + } + + ERR_FAIL_COND_V(type==Variant::VARIANT_MAX,Ref<VisualScriptNode>()); + + + node->set_call_mode(VisualScriptFunctionCall::CALL_MODE_BASIC_TYPE); + node->set_basic_type(type); + node->set_function(method); + + return node; +} + + +void register_visual_script_func_nodes() { + + VisualScriptLanguage::singleton->add_register_func("functions/call",create_node_generic<VisualScriptFunctionCall>); + VisualScriptLanguage::singleton->add_register_func("functions/set",create_node_generic<VisualScriptPropertySet>); + VisualScriptLanguage::singleton->add_register_func("functions/get",create_node_generic<VisualScriptPropertyGet>); + + //VisualScriptLanguage::singleton->add_register_func("functions/call_script/call_self",create_script_call_node<VisualScriptScriptCall::CALL_MODE_SELF>); +// VisualScriptLanguage::singleton->add_register_func("functions/call_script/call_node",create_script_call_node<VisualScriptScriptCall::CALL_MODE_NODE_PATH>); + VisualScriptLanguage::singleton->add_register_func("functions/emit_signal",create_node_generic<VisualScriptEmitSignal>); + + + for(int i=0;i<Variant::VARIANT_MAX;i++) { + + Variant::Type t = Variant::Type(i); + String type_name = Variant::get_type_name(t); + Variant::CallError ce; + Variant vt = Variant::construct(t,NULL,0,ce); + List<MethodInfo> ml; + vt.get_method_list(&ml); + + for (List<MethodInfo>::Element *E=ml.front();E;E=E->next()) { + VisualScriptLanguage::singleton->add_register_func("functions/by_type/"+type_name+"/"+E->get().name,create_basic_type_call_node); + } + } +} diff --git a/modules/visual_script/visual_script_func_nodes.h b/modules/visual_script/visual_script_func_nodes.h new file mode 100644 index 0000000000..43ef276cf4 --- /dev/null +++ b/modules/visual_script/visual_script_func_nodes.h @@ -0,0 +1,349 @@ +#ifndef VISUAL_SCRIPT_FUNC_NODES_H +#define VISUAL_SCRIPT_FUNC_NODES_H + +#include "visual_script.h" + + +class VisualScriptFunctionCall : public VisualScriptNode { + + OBJ_TYPE(VisualScriptFunctionCall,VisualScriptNode) +public: + enum CallMode { + CALL_MODE_SELF, + CALL_MODE_NODE_PATH, + CALL_MODE_INSTANCE, + CALL_MODE_BASIC_TYPE, + CALL_MODE_SINGLETON, + }; + + enum RPCCallMode { + RPC_DISABLED, + RPC_RELIABLE, + RPC_UNRELIABLE, + RPC_RELIABLE_TO_ID, + RPC_UNRELIABLE_TO_ID + }; + +private: + + CallMode call_mode; + StringName base_type; + String base_script; + Variant::Type basic_type; + NodePath base_path; + StringName function; + int use_default_args; + RPCCallMode rpc_call_mode; + StringName singleton; + bool validate; + + + Node *_get_base_node() const; + StringName _get_base_type() const; + + MethodInfo method_cache; + void _update_method_cache(); + + void _set_argument_cache(const Dictionary& p_args); + Dictionary _get_argument_cache() const; + +protected: + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_basic_type(Variant::Type p_type); + Variant::Type get_basic_type() const; + + void set_base_type(const StringName& p_type); + StringName get_base_type() const; + + void set_base_script(const String& p_path); + String get_base_script() const; + + void set_singleton(const StringName& p_type); + StringName get_singleton() const; + + void set_function(const StringName& p_type); + StringName get_function() const; + + void set_base_path(const NodePath& p_type); + NodePath get_base_path() const; + + + void set_call_mode(CallMode p_mode); + CallMode get_call_mode() const; + + void set_use_default_args(int p_amount); + int get_use_default_args() const; + + void set_validate(bool p_amount); + bool get_validate() const; + + void set_rpc_call_mode(RPCCallMode p_mode); + RPCCallMode get_rpc_call_mode() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + + VisualScriptFunctionCall(); +}; + +VARIANT_ENUM_CAST(VisualScriptFunctionCall::CallMode ); +VARIANT_ENUM_CAST(VisualScriptFunctionCall::RPCCallMode ); + + +class VisualScriptPropertySet : public VisualScriptNode { + + OBJ_TYPE(VisualScriptPropertySet,VisualScriptNode) +public: + enum CallMode { + CALL_MODE_SELF, + CALL_MODE_NODE_PATH, + CALL_MODE_INSTANCE, + CALL_MODE_BASIC_TYPE, + + + }; +private: + + PropertyInfo type_cache; + + CallMode call_mode; + Variant::Type basic_type; + StringName base_type; + String base_script; + NodePath base_path; + StringName property; + InputEvent::Type event_type; + + Node *_get_base_node() const; + StringName _get_base_type() const; + + void _update_base_type(); + + void _update_cache(); + + void _set_type_cache(const Dictionary& p_type); + Dictionary _get_type_cache() const; + + +protected: + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_base_type(const StringName& p_type); + StringName get_base_type() const; + + void set_base_script(const String& p_path); + String get_base_script() const; + + void set_basic_type(Variant::Type p_type); + Variant::Type get_basic_type() const; + + void set_event_type(InputEvent::Type p_type); + InputEvent::Type get_event_type() const; + + void set_property(const StringName& p_type); + StringName get_property() const; + + void set_base_path(const NodePath& p_type); + NodePath get_base_path() const; + + void set_call_mode(CallMode p_mode); + CallMode get_call_mode() const; + + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + VisualScriptPropertySet(); +}; + +VARIANT_ENUM_CAST(VisualScriptPropertySet::CallMode ); + + +class VisualScriptPropertyGet : public VisualScriptNode { + + OBJ_TYPE(VisualScriptPropertyGet,VisualScriptNode) +public: + enum CallMode { + CALL_MODE_SELF, + CALL_MODE_NODE_PATH, + CALL_MODE_INSTANCE, + CALL_MODE_BASIC_TYPE, + + }; +private: + + Variant::Type type_cache; + + CallMode call_mode; + Variant::Type basic_type; + StringName base_type; + String base_script; + NodePath base_path; + StringName property; + InputEvent::Type event_type; + + void _update_base_type(); + Node *_get_base_node() const; + StringName _get_base_type() const; + + void _update_cache(); + + void _set_type_cache(Variant::Type p_type); + Variant::Type _get_type_cache() const; + +protected: + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_base_type(const StringName& p_type); + StringName get_base_type() const; + + void set_base_script(const String& p_path); + String get_base_script() const; + + void set_basic_type(Variant::Type p_type); + Variant::Type get_basic_type() const; + + void set_event_type(InputEvent::Type p_type); + InputEvent::Type get_event_type() const; + + void set_property(const StringName& p_type); + StringName get_property() const; + + void set_base_path(const NodePath& p_type); + NodePath get_base_path() const; + + void set_call_mode(CallMode p_mode); + CallMode get_call_mode() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptPropertyGet(); +}; + + + + + +VARIANT_ENUM_CAST(VisualScriptPropertyGet::CallMode ); + + + +class VisualScriptEmitSignal : public VisualScriptNode { + + OBJ_TYPE(VisualScriptEmitSignal,VisualScriptNode) + +private: + + StringName name; + + +protected: + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_signal(const StringName& p_type); + StringName get_signal() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + + + + VisualScriptEmitSignal(); +}; + + + +void register_visual_script_func_nodes(); + +#endif // VISUAL_SCRIPT_FUNC_NODES_H diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp new file mode 100644 index 0000000000..7ada292b13 --- /dev/null +++ b/modules/visual_script/visual_script_nodes.cpp @@ -0,0 +1,3958 @@ +#include "visual_script_nodes.h" +#include "global_constants.h" +#include "globals.h" +#include "scene/main/scene_main_loop.h" +#include "os/os.h" +#include "scene/main/node.h" +#include "os/input.h" + +////////////////////////////////////////// +////////////////FUNCTION////////////////// +////////////////////////////////////////// + + +bool VisualScriptFunction::_set(const StringName& p_name, const Variant& p_value) { + + + if (p_name=="argument_count") { + + int new_argc=p_value; + int argc = arguments.size(); + if (argc==new_argc) + return true; + + arguments.resize(new_argc); + + for(int i=argc;i<new_argc;i++) { + arguments[i].name="arg"+itos(i+1); + arguments[i].type=Variant::NIL; + } + ports_changed_notify(); + _change_notify(); + return true; + } + if (String(p_name).begins_with("argument/")) { + int idx = String(p_name).get_slice("/",1).to_int()-1; + ERR_FAIL_INDEX_V(idx,arguments.size(),false); + String what = String(p_name).get_slice("/",2); + if (what=="type") { + + Variant::Type new_type = Variant::Type(int(p_value)); + arguments[idx].type=new_type; + ports_changed_notify(); + + return true; + } + + if (what=="name") { + + arguments[idx].name=p_value; + ports_changed_notify(); + return true; + } + + + } + + if (p_name=="stack/stackless") { + set_stack_less(p_value); + return true; + } + + if (p_name=="stack/size") { + stack_size=p_value; + return true; + } + + if (p_name=="rpc/mode") { + rpc_mode=ScriptInstance::RPCMode(int(p_value)); + return true; + } + + return false; +} + +bool VisualScriptFunction::_get(const StringName& p_name,Variant &r_ret) const { + + + if (p_name=="argument_count") { + r_ret = arguments.size(); + return true; + } + if (String(p_name).begins_with("argument/")) { + int idx = String(p_name).get_slice("/",1).to_int()-1; + ERR_FAIL_INDEX_V(idx,arguments.size(),false); + String what = String(p_name).get_slice("/",2); + if (what=="type") { + r_ret = arguments[idx].type; + return true; + } + if (what=="name") { + r_ret = arguments[idx].name; + return true; + } + + + + } + + if (p_name=="stack/stackless") { + r_ret=stack_less; + return true; + } + + if (p_name=="stack/size") { + r_ret=stack_size; + return true; + } + + if (p_name=="rpc/mode") { + r_ret=rpc_mode; + return true; + } + + return false; +} +void VisualScriptFunction::_get_property_list( List<PropertyInfo> *p_list) const { + + + p_list->push_back(PropertyInfo(Variant::INT,"argument_count",PROPERTY_HINT_RANGE,"0,256")); + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + for(int i=0;i<arguments.size();i++) { + p_list->push_back(PropertyInfo(Variant::INT,"argument/"+itos(i+1)+"/type",PROPERTY_HINT_ENUM,argt)); + p_list->push_back(PropertyInfo(Variant::STRING,"argument/"+itos(i+1)+"/name")); + } + if (!stack_less) { + p_list->push_back(PropertyInfo(Variant::INT,"stack/size",PROPERTY_HINT_RANGE,"1,100000")); + } + p_list->push_back(PropertyInfo(Variant::BOOL,"stack/stackless")); + p_list->push_back(PropertyInfo(Variant::INT,"rpc/mode",PROPERTY_HINT_ENUM,"Disabled,Remote,Sync,Master,Slave")); + +} + + +int VisualScriptFunction::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptFunction::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptFunction::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptFunction::get_output_value_port_count() const{ + + return arguments.size(); +} + +String VisualScriptFunction::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptFunction::get_input_value_port_info(int p_idx) const{ + + ERR_FAIL_V(PropertyInfo()); + return PropertyInfo(); +} +PropertyInfo VisualScriptFunction::get_output_value_port_info(int p_idx) const{ + + ERR_FAIL_INDEX_V(p_idx,arguments.size(),PropertyInfo()); + PropertyInfo out; + out.type=arguments[p_idx].type; + out.name=arguments[p_idx].name; + return out; +} + +String VisualScriptFunction::get_caption() const { + + return "Function"; +} + +String VisualScriptFunction::get_text() const { + + return get_name(); //use name as function name I guess +} + +void VisualScriptFunction::add_argument(Variant::Type p_type,const String& p_name,int p_index){ + + Argument arg; + arg.name=p_name; + arg.type=p_type; + if (p_index>=0) + arguments.insert(p_index,arg); + else + arguments.push_back(arg); + + ports_changed_notify(); + +} +void VisualScriptFunction::set_argument_type(int p_argidx,Variant::Type p_type){ + + ERR_FAIL_INDEX(p_argidx,arguments.size()); + + arguments[p_argidx].type=p_type; + ports_changed_notify(); +} +Variant::Type VisualScriptFunction::get_argument_type(int p_argidx) const { + + ERR_FAIL_INDEX_V(p_argidx,arguments.size(),Variant::NIL); + return arguments[p_argidx].type; + +} +void VisualScriptFunction::set_argument_name(int p_argidx,const String& p_name) { + + ERR_FAIL_INDEX(p_argidx,arguments.size()); + + arguments[p_argidx].name=p_name; + ports_changed_notify(); + +} +String VisualScriptFunction::get_argument_name(int p_argidx) const { + + ERR_FAIL_INDEX_V(p_argidx,arguments.size(),String()); + return arguments[p_argidx].name; + +} +void VisualScriptFunction::remove_argument(int p_argidx) { + + ERR_FAIL_INDEX(p_argidx,arguments.size()); + + arguments.remove(p_argidx); + ports_changed_notify(); + +} + +int VisualScriptFunction::get_argument_count() const { + + return arguments.size(); +} + + +void VisualScriptFunction::set_rpc_mode(ScriptInstance::RPCMode p_mode) { + rpc_mode=p_mode; +} + +ScriptInstance::RPCMode VisualScriptFunction::get_rpc_mode() const { + return rpc_mode; +} + + +class VisualScriptNodeInstanceFunction : public VisualScriptNodeInstance { +public: + + VisualScriptFunction *node; + VisualScriptInstance *instance; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + int ac = node->get_argument_count(); + + for(int i=0;i<ac;i++) { +#ifdef DEBUG_ENABLED + Variant::Type expected = node->get_argument_type(i); + if (expected!=Variant::NIL) { + if (!Variant::can_convert_strict(p_inputs[i]->get_type(),expected)) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.expected=expected; + r_error.argument=i; + return 0; + } + } +#endif + + *p_outputs[i]=*p_inputs[i]; + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptFunction::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceFunction * instance = memnew(VisualScriptNodeInstanceFunction ); + instance->node=this; + instance->instance=p_instance; + return instance; +} + +VisualScriptFunction::VisualScriptFunction() { + + stack_size=256; + stack_less=false; + rpc_mode=ScriptInstance::RPC_MODE_DISABLED; +} + + +void VisualScriptFunction::set_stack_less(bool p_enable) { + stack_less=p_enable; + _change_notify(); +} + +bool VisualScriptFunction::is_stack_less() const { + return stack_less; +} + +void VisualScriptFunction::set_stack_size(int p_size) { + + ERR_FAIL_COND(p_size <1 || p_size>100000); + stack_size=p_size; +} + +int VisualScriptFunction::get_stack_size() const { + + return stack_size; +} + + +////////////////////////////////////////// +////////////////OPERATOR////////////////// +////////////////////////////////////////// + +int VisualScriptOperator::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptOperator::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptOperator::get_input_value_port_count() const{ + + return (op==Variant::OP_BIT_NEGATE || op==Variant::OP_NOT || op==Variant::OP_NEGATE) ? 1 : 2; +} +int VisualScriptOperator::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptOperator::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptOperator::get_input_value_port_info(int p_idx) const{ + + static const Variant::Type port_types[Variant::OP_MAX][2]={ + {Variant::NIL,Variant::NIL}, //OP_EQUAL, + {Variant::NIL,Variant::NIL}, //OP_NOT_EQUAL, + {Variant::NIL,Variant::NIL}, //OP_LESS, + {Variant::NIL,Variant::NIL}, //OP_LESS_EQUAL, + {Variant::NIL,Variant::NIL}, //OP_GREATER, + {Variant::NIL,Variant::NIL}, //OP_GREATER_EQUAL, + //mathematic + {Variant::NIL,Variant::NIL}, //OP_ADD, + {Variant::NIL,Variant::NIL}, //OP_SUBSTRACT, + {Variant::NIL,Variant::NIL}, //OP_MULTIPLY, + {Variant::NIL,Variant::NIL}, //OP_DIVIDE, + {Variant::NIL,Variant::NIL}, //OP_NEGATE, + {Variant::INT,Variant::INT}, //OP_MODULE, + {Variant::STRING,Variant::STRING}, //OP_STRING_CONCAT, + //bitwise + {Variant::INT,Variant::INT}, //OP_SHIFT_LEFT, + {Variant::INT,Variant::INT}, //OP_SHIFT_RIGHT, + {Variant::INT,Variant::INT}, //OP_BIT_AND, + {Variant::INT,Variant::INT}, //OP_BIT_OR, + {Variant::INT,Variant::INT}, //OP_BIT_XOR, + {Variant::INT,Variant::INT}, //OP_BIT_NEGATE, + //logic + {Variant::BOOL,Variant::BOOL}, //OP_AND, + {Variant::BOOL,Variant::BOOL}, //OP_OR, + {Variant::BOOL,Variant::BOOL}, //OP_XOR, + {Variant::BOOL,Variant::BOOL}, //OP_NOT, + //containment + {Variant::NIL,Variant::NIL} //OP_IN, + }; + + ERR_FAIL_INDEX_V(p_idx,Variant::OP_MAX,PropertyInfo()); + + PropertyInfo pinfo; + pinfo.name=p_idx==0?"A":"B"; + pinfo.type=port_types[op][p_idx]; + if (pinfo.type==Variant::NIL) + pinfo.type=typed; + return pinfo; +} +PropertyInfo VisualScriptOperator::get_output_value_port_info(int p_idx) const{ + static const Variant::Type port_types[Variant::OP_MAX]={ + //comparation + Variant::BOOL, //OP_EQUAL, + Variant::BOOL, //OP_NOT_EQUAL, + Variant::BOOL, //OP_LESS, + Variant::BOOL, //OP_LESS_EQUAL, + Variant::BOOL, //OP_GREATER, + Variant::BOOL, //OP_GREATER_EQUAL, + //mathematic + Variant::NIL, //OP_ADD, + Variant::NIL, //OP_SUBSTRACT, + Variant::NIL, //OP_MULTIPLY, + Variant::NIL, //OP_DIVIDE, + Variant::NIL, //OP_NEGATE, + Variant::INT, //OP_MODULE, + Variant::STRING, //OP_STRING_CONCAT, + //bitwise + Variant::INT, //OP_SHIFT_LEFT, + Variant::INT, //OP_SHIFT_RIGHT, + Variant::INT, //OP_BIT_AND, + Variant::INT, //OP_BIT_OR, + Variant::INT, //OP_BIT_XOR, + Variant::INT, //OP_BIT_NEGATE, + //logic + Variant::BOOL, //OP_AND, + Variant::BOOL, //OP_OR, + Variant::BOOL, //OP_XOR, + Variant::BOOL, //OP_NOT, + //containment + Variant::BOOL //OP_IN, + }; + + PropertyInfo pinfo; + pinfo.name=""; + pinfo.type=port_types[op]; + if (pinfo.type==Variant::NIL) + pinfo.type=typed; + return pinfo; + +} + +static const char* op_names[]={ + //comparation + "Equal", //OP_EQUAL, + "NotEqual", //OP_NOT_EQUAL, + "Less", //OP_LESS, + "LessEqual", //OP_LESS_EQUAL, + "Greater", //OP_GREATER, + "GreaterEq", //OP_GREATER_EQUAL, + //mathematic + "Add", //OP_ADD, + "Subtract", //OP_SUBSTRACT, + "Multiply", //OP_MULTIPLY, + "Divide", //OP_DIVIDE, + "Negate", //OP_NEGATE, + "Remainder", //OP_MODULE, + "Concat", //OP_STRING_CONCAT, + //bitwise + "ShiftLeft", //OP_SHIFT_LEFT, + "ShiftRight", //OP_SHIFT_RIGHT, + "BitAnd", //OP_BIT_AND, + "BitOr", //OP_BIT_OR, + "BitXor", //OP_BIT_XOR, + "BitNeg", //OP_BIT_NEGATE, + //logic + "And", //OP_AND, + "Or", //OP_OR, + "Xor", //OP_XOR, + "Not", //OP_NOT, + //containment + "In", //OP_IN, +}; + +String VisualScriptOperator::get_caption() const { + + + + return op_names[op]; +} + +String VisualScriptOperator::get_text() const { + + static const wchar_t* op_names[]={ + //comparation + L"A = B", //OP_EQUAL, + L"A \u2260 B", //OP_NOT_EQUAL, + L"A < B", //OP_LESS, + L"A \u2264 B", //OP_LESS_EQUAL, + L"A > B", //OP_GREATER, + L"A \u2265 B", //OP_GREATER_EQUAL, + //mathematic + L"A + B", //OP_ADD, + L"A - B", //OP_SUBSTRACT, + L"A x B", //OP_MULTIPLY, + L"A \u00F7 B", //OP_DIVIDE, + L"\u00AC A", //OP_NEGATE, + L"A mod B", //OP_MODULE, + L"A .. B", //OP_STRING_CONCAT, + //bitwise + L"A << B", //OP_SHIFT_LEFT, + L"A >> B", //OP_SHIFT_RIGHT, + L"A & B", //OP_BIT_AND, + L"A | B", //OP_BIT_OR, + L"A ^ B", //OP_BIT_XOR, + L"~A", //OP_BIT_NEGATE, + //logic + L"A and B", //OP_AND, + L"A or B", //OP_OR, + L"A xor B", //OP_XOR, + L"not A", //OP_NOT, + + }; + return op_names[op]; +} + +void VisualScriptOperator::set_operator(Variant::Operator p_op) { + + if (op==p_op) + return; + op=p_op; + ports_changed_notify(); + +} + +Variant::Operator VisualScriptOperator::get_operator() const{ + + return op; +} + +void VisualScriptOperator::set_typed(Variant::Type p_op) { + + if (typed==p_op) + return; + + typed=p_op; + ports_changed_notify(); +} + +Variant::Type VisualScriptOperator::get_typed() const { + + return typed; +} + + +void VisualScriptOperator::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_operator","op"),&VisualScriptOperator::set_operator); + ObjectTypeDB::bind_method(_MD("get_operator"),&VisualScriptOperator::get_operator); + + ObjectTypeDB::bind_method(_MD("set_typed","type"),&VisualScriptOperator::set_typed); + ObjectTypeDB::bind_method(_MD("get_typed"),&VisualScriptOperator::get_typed); + + String types; + for(int i=0;i<Variant::OP_MAX;i++) { + if (i>0) + types+=","; + types+=op_names[i]; + } + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + ADD_PROPERTY(PropertyInfo(Variant::INT,"operator_value/type",PROPERTY_HINT_ENUM,types),_SCS("set_operator"),_SCS("get_operator")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"typed_value/typed",PROPERTY_HINT_ENUM,argt),_SCS("set_typed"),_SCS("get_typed")); + +} + +class VisualScriptNodeInstanceOperator : public VisualScriptNodeInstance { +public: + + bool unary; + Variant::Operator op; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + bool valid; + if (unary) { + + Variant::evaluate(op,*p_inputs[0],Variant(),*p_outputs[0],valid); + } else { + Variant::evaluate(op,*p_inputs[0],*p_inputs[1],*p_outputs[0],valid); + } + + if (!valid) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + if (p_outputs[0]->get_type()==Variant::STRING) { + r_error_str=*p_outputs[0]; + } else { + if (unary) + r_error_str=String(op_names[op])+RTR(": Invalid argument of type: ")+Variant::get_type_name(p_inputs[0]->get_type()); + else + r_error_str=String(op_names[op])+RTR(": Invalid arguments: ")+"A: "+Variant::get_type_name(p_inputs[0]->get_type())+" B: "+Variant::get_type_name(p_inputs[1]->get_type()); + } + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptOperator::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceOperator * instance = memnew(VisualScriptNodeInstanceOperator ); + instance->unary=get_input_value_port_count()==1; + instance->op=op; + return instance; +} + +VisualScriptOperator::VisualScriptOperator() { + + op=Variant::OP_ADD; + typed=Variant::NIL; +} + + + +template<Variant::Operator OP> +static Ref<VisualScriptNode> create_op_node(const String& p_name) { + + Ref<VisualScriptOperator> node; + node.instance(); + node->set_operator(OP); + return node; +} + +////////////////////////////////////////// +////////////////VARIABLE GET////////////////// +////////////////////////////////////////// + +int VisualScriptVariableGet::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptVariableGet::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptVariableGet::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptVariableGet::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptVariableGet::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptVariableGet::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptVariableGet::get_output_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="value"; + if (get_visual_script().is_valid() && get_visual_script()->has_variable(variable)) { + PropertyInfo vinfo = get_visual_script()->get_variable_info(variable); + pinfo.type=vinfo.type; + pinfo.hint=vinfo.hint; + pinfo.hint_string=vinfo.hint_string; + } + return pinfo; +} + + +String VisualScriptVariableGet::get_caption() const { + + return "Variable"; +} + +String VisualScriptVariableGet::get_text() const { + + return variable; +} + +void VisualScriptVariableGet::set_variable(StringName p_variable) { + + if (variable==p_variable) + return; + variable=p_variable; + ports_changed_notify(); + +} + +StringName VisualScriptVariableGet::get_variable() const{ + + return variable; +} + +void VisualScriptVariableGet::_validate_property(PropertyInfo& property) const { + + if (property.name=="variable/name" && get_visual_script().is_valid()) { + Ref<VisualScript> vs = get_visual_script(); + List<StringName> vars; + vs->get_variable_list(&vars); + + String vhint; + for (List<StringName>::Element *E=vars.front();E;E=E->next()) { + if (vhint!=String()) + vhint+=","; + + vhint+=E->get().operator String(); + } + + property.hint=PROPERTY_HINT_ENUM; + property.hint_string=vhint; + } +} + +void VisualScriptVariableGet::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_variable","name"),&VisualScriptVariableGet::set_variable); + ObjectTypeDB::bind_method(_MD("get_variable"),&VisualScriptVariableGet::get_variable); + + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"variable/name"),_SCS("set_variable"),_SCS("get_variable")); + +} + +class VisualScriptNodeInstanceVariableGet : public VisualScriptNodeInstance { +public: + + VisualScriptVariableGet *node; + VisualScriptInstance *instance; + StringName variable; + + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (instance->get_variable(variable,p_outputs[0])==false) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("VariableGet not found in script: ")+"'"+String(variable)+"'"; + return false; + } + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptVariableGet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceVariableGet * instance = memnew(VisualScriptNodeInstanceVariableGet ); + instance->node=this; + instance->instance=p_instance; + instance->variable=variable; + return instance; +} +VisualScriptVariableGet::VisualScriptVariableGet() { + + +} + + +////////////////////////////////////////// +////////////////VARIABLE SET////////////////// +////////////////////////////////////////// + +int VisualScriptVariableSet::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptVariableSet::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptVariableSet::get_input_value_port_count() const{ + + return 1; +} +int VisualScriptVariableSet::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptVariableSet::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptVariableSet::get_input_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="set"; + if (get_visual_script().is_valid() && get_visual_script()->has_variable(variable)) { + PropertyInfo vinfo = get_visual_script()->get_variable_info(variable); + pinfo.type=vinfo.type; + pinfo.hint=vinfo.hint; + pinfo.hint_string=vinfo.hint_string; + } + return pinfo; +} + +PropertyInfo VisualScriptVariableSet::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptVariableSet::get_caption() const { + + return "VariableSet"; +} + +String VisualScriptVariableSet::get_text() const { + + return variable; +} + +void VisualScriptVariableSet::set_variable(StringName p_variable) { + + if (variable==p_variable) + return; + variable=p_variable; + ports_changed_notify(); + +} + +StringName VisualScriptVariableSet::get_variable() const{ + + return variable; +} + +void VisualScriptVariableSet::_validate_property(PropertyInfo& property) const { + + if (property.name=="variable/name" && get_visual_script().is_valid()) { + Ref<VisualScript> vs = get_visual_script(); + List<StringName> vars; + vs->get_variable_list(&vars); + + String vhint; + for (List<StringName>::Element *E=vars.front();E;E=E->next()) { + if (vhint!=String()) + vhint+=","; + + vhint+=E->get().operator String(); + } + + property.hint=PROPERTY_HINT_ENUM; + property.hint_string=vhint; + } +} + +void VisualScriptVariableSet::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_variable","name"),&VisualScriptVariableSet::set_variable); + ObjectTypeDB::bind_method(_MD("get_variable"),&VisualScriptVariableSet::get_variable); + + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"variable/name"),_SCS("set_variable"),_SCS("get_variable")); + +} + +class VisualScriptNodeInstanceVariableSet : public VisualScriptNodeInstance { +public: + + VisualScriptVariableSet *node; + VisualScriptInstance *instance; + StringName variable; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (instance->set_variable(variable,*p_inputs[0])==false) { + + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("VariableSet not found in script: ")+"'"+String(variable)+"'"; + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptVariableSet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceVariableSet * instance = memnew(VisualScriptNodeInstanceVariableSet ); + instance->node=this; + instance->instance=p_instance; + instance->variable=variable; + return instance; +} +VisualScriptVariableSet::VisualScriptVariableSet() { + + +} + + + +////////////////////////////////////////// +////////////////CONSTANT////////////////// +////////////////////////////////////////// + +int VisualScriptConstant::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptConstant::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptConstant::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptConstant::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptConstant::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptConstant::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptConstant::get_output_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="get"; + pinfo.type=type; + return pinfo; +} + + +String VisualScriptConstant::get_caption() const { + + return "Constant"; +} + +String VisualScriptConstant::get_text() const { + + return String(value); +} + +void VisualScriptConstant::set_constant_type(Variant::Type p_type) { + + if (type==p_type) + return; + + type=p_type; + ports_changed_notify(); + Variant::CallError ce; + value=Variant::construct(type,NULL,0,ce); + _change_notify(); + +} + +Variant::Type VisualScriptConstant::get_constant_type() const{ + + return type; +} + +void VisualScriptConstant::set_constant_value(Variant p_value){ + + if (value==p_value) + return; + + value=p_value; + ports_changed_notify(); +} +Variant VisualScriptConstant::get_constant_value() const{ + + return value; +} + +void VisualScriptConstant::_validate_property(PropertyInfo& property) const { + + + if (property.name=="constant/value") { + property.type=type; + if (type==Variant::NIL) + property.usage=0; //do not save if nil + } +} + +void VisualScriptConstant::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_constant_type","type"),&VisualScriptConstant::set_constant_type); + ObjectTypeDB::bind_method(_MD("get_constant_type"),&VisualScriptConstant::get_constant_type); + + ObjectTypeDB::bind_method(_MD("set_constant_value","value"),&VisualScriptConstant::set_constant_value); + ObjectTypeDB::bind_method(_MD("get_constant_value"),&VisualScriptConstant::get_constant_value); + + String argt="Null"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + + ADD_PROPERTY(PropertyInfo(Variant::INT,"constant/type",PROPERTY_HINT_ENUM,argt),_SCS("set_constant_type"),_SCS("get_constant_type")); + ADD_PROPERTY(PropertyInfo(Variant::NIL,"constant/value"),_SCS("set_constant_value"),_SCS("get_constant_value")); + +} + +class VisualScriptNodeInstanceConstant : public VisualScriptNodeInstance { +public: + + Variant constant; + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0]=constant; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptConstant::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceConstant * instance = memnew(VisualScriptNodeInstanceConstant ); + instance->constant=value; + return instance; +} + +VisualScriptConstant::VisualScriptConstant() { + + type=Variant::NIL; + +} + +////////////////////////////////////////// +////////////////PRELOAD////////////////// +////////////////////////////////////////// + +int VisualScriptPreload::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptPreload::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptPreload::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptPreload::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptPreload::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptPreload::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptPreload::get_output_value_port_info(int p_idx) const{ + + PropertyInfo pinfo=PropertyInfo(Variant::OBJECT,"res"); + if (preload.is_valid()) { + pinfo.hint=PROPERTY_HINT_RESOURCE_TYPE; + pinfo.hint_string=preload->get_type(); + } + + return pinfo; +} + + +String VisualScriptPreload::get_caption() const { + + return "Preload"; +} + +String VisualScriptPreload::get_text() const { + + if (preload.is_valid()) { + if (preload->get_path().is_resource_file()) { + return preload->get_path(); + } else if (preload->get_name()!=String()) { + return preload->get_name(); + } else { + return preload->get_type(); + } + } else { + return "<empty>"; + } +} + + +void VisualScriptPreload::set_preload(const Ref<Resource>& p_preload){ + + if (preload==p_preload) + return; + + preload=p_preload; + ports_changed_notify(); +} +Ref<Resource> VisualScriptPreload::get_preload() const{ + + return preload; +} + + +void VisualScriptPreload::_bind_methods() { + + + ObjectTypeDB::bind_method(_MD("set_preload","resource"),&VisualScriptPreload::set_preload); + ObjectTypeDB::bind_method(_MD("get_preload"),&VisualScriptPreload::get_preload); + + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT,"resource",PROPERTY_HINT_RESOURCE_TYPE,"Resource"),_SCS("set_preload"),_SCS("get_preload")); + +} + +class VisualScriptNodeInstancePreload : public VisualScriptNodeInstance { +public: + + Ref<Resource> preload; + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0]=preload; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptPreload::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstancePreload * instance = memnew(VisualScriptNodeInstancePreload ); + instance->preload=preload; + return instance; +} + +VisualScriptPreload::VisualScriptPreload() { + +} + + + + +////////////////////////////////////////// +////////////////INDEX//////////////////// +////////////////////////////////////////// + +int VisualScriptIndexGet::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptIndexGet::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptIndexGet::get_input_value_port_count() const{ + + return 2; +} +int VisualScriptIndexGet::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptIndexGet::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptIndexGet::get_input_value_port_info(int p_idx) const{ + + if (p_idx==0) { + return PropertyInfo(Variant::NIL,"base"); + } else { + return PropertyInfo(Variant::NIL,"index"); + + } +} + +PropertyInfo VisualScriptIndexGet::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptIndexGet::get_caption() const { + + return "IndexGet"; +} + +String VisualScriptIndexGet::get_text() const { + + return String("get"); +} + + +class VisualScriptNodeInstanceIndexGet : public VisualScriptNodeInstance { +public: + + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + bool valid; + *p_outputs[0] = p_inputs[0]->get(*p_inputs[1],&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Invalid get: "+p_inputs[0]->get_construct_string(); + } + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptIndexGet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceIndexGet * instance = memnew(VisualScriptNodeInstanceIndexGet ); + return instance; +} +VisualScriptIndexGet::VisualScriptIndexGet() { + + + +} + +////////////////////////////////////////// +////////////////INDEXSET////////////////// +////////////////////////////////////////// + +int VisualScriptIndexSet::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptIndexSet::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptIndexSet::get_input_value_port_count() const{ + + return 3; +} +int VisualScriptIndexSet::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptIndexSet::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptIndexSet::get_input_value_port_info(int p_idx) const{ + + if (p_idx==0) { + return PropertyInfo(Variant::NIL,"base"); + } else if (p_idx==1){ + return PropertyInfo(Variant::NIL,"index"); + + } else { + return PropertyInfo(Variant::NIL,"value"); + + } +} + +PropertyInfo VisualScriptIndexSet::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptIndexSet::get_caption() const { + + return "IndexSet"; +} + +String VisualScriptIndexSet::get_text() const { + + return String("set"); +} + + +class VisualScriptNodeInstanceIndexSet : public VisualScriptNodeInstance { +public: + + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + bool valid; + *p_outputs[0]=*p_inputs[0]; + p_outputs[0]->set(*p_inputs[1],*p_inputs[2],&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Invalid set: "+p_inputs[1]->get_construct_string(); + } + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptIndexSet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceIndexSet * instance = memnew(VisualScriptNodeInstanceIndexSet ); + return instance; +} +VisualScriptIndexSet::VisualScriptIndexSet() { + + + +} + + +////////////////////////////////////////// +////////////////GLOBALCONSTANT/////////// +////////////////////////////////////////// + +int VisualScriptGlobalConstant::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptGlobalConstant::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptGlobalConstant::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptGlobalConstant::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptGlobalConstant::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptGlobalConstant::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptGlobalConstant::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::REAL,"value"); +} + + +String VisualScriptGlobalConstant::get_caption() const { + + return "GlobalConst"; +} + +String VisualScriptGlobalConstant::get_text() const { + + return GlobalConstants::get_global_constant_name(index); +} + +void VisualScriptGlobalConstant::set_global_constant(int p_which) { + + index=p_which; + _change_notify(); + ports_changed_notify(); +} + +int VisualScriptGlobalConstant::get_global_constant() { + return index; +} + + +class VisualScriptNodeInstanceGlobalConstant : public VisualScriptNodeInstance { +public: + + int index; + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + + *p_outputs[0] = GlobalConstants::get_global_constant_value(index); + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptGlobalConstant::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceGlobalConstant * instance = memnew(VisualScriptNodeInstanceGlobalConstant ); + instance->index=index; + return instance; +} + +void VisualScriptGlobalConstant::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_global_constant","index"),&VisualScriptGlobalConstant::set_global_constant); + ObjectTypeDB::bind_method(_MD("get_global_constant"),&VisualScriptGlobalConstant::get_global_constant); + + String cc; + + for(int i=0;i<GlobalConstants::get_global_constant_count();i++) { + + if (i>0) + cc+=","; + cc+=GlobalConstants::get_global_constant_name(i); + } + ADD_PROPERTY(PropertyInfo(Variant::INT,"constant",PROPERTY_HINT_ENUM,cc),_SCS("set_global_constant"),_SCS("get_global_constant")); +} + +VisualScriptGlobalConstant::VisualScriptGlobalConstant() { + + index=0; +} + +////////////////////////////////////////// +////////////////CLASSCONSTANT/////////// +////////////////////////////////////////// + +int VisualScriptClassConstant::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptClassConstant::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptClassConstant::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptClassConstant::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptClassConstant::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptClassConstant::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptClassConstant::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::INT,"value"); +} + + +String VisualScriptClassConstant::get_caption() const { + + return "ClassConst"; +} + +String VisualScriptClassConstant::get_text() const { + + return String(base_type)+"."+String(name); +} + +void VisualScriptClassConstant::set_class_constant(const StringName& p_which) { + + name=p_which; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptClassConstant::get_class_constant() { + return name; +} + + +void VisualScriptClassConstant::set_base_type(const StringName& p_which) { + + base_type=p_which; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptClassConstant::get_base_type() { + return base_type; +} + +class VisualScriptNodeInstanceClassConstant : public VisualScriptNodeInstance { +public: + + int value; + bool valid; + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (!valid) { + r_error_str="Invalid constant name, pick a valid class constant."; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + } + + *p_outputs[0] = value; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptClassConstant::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceClassConstant * instance = memnew(VisualScriptNodeInstanceClassConstant ); + instance->value=ObjectTypeDB::get_integer_constant(base_type,name,&instance->valid); + return instance; +} + +void VisualScriptClassConstant::_validate_property(PropertyInfo& property) const { + + if (property.name=="constant") { + + List<String> constants; + ObjectTypeDB::get_integer_constant_list(base_type,&constants,true); + + property.hint_string=""; + for(List<String>::Element *E=constants.front();E;E=E->next()) { + if (property.hint_string!=String()) { + property.hint_string+=","; + } + property.hint_string+=E->get(); + } + } +} + +void VisualScriptClassConstant::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_class_constant","name"),&VisualScriptClassConstant::set_class_constant); + ObjectTypeDB::bind_method(_MD("get_class_constant"),&VisualScriptClassConstant::get_class_constant); + + ObjectTypeDB::bind_method(_MD("set_base_type","name"),&VisualScriptClassConstant::set_base_type); + ObjectTypeDB::bind_method(_MD("get_base_type"),&VisualScriptClassConstant::get_base_type); + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"base_type",PROPERTY_HINT_TYPE_STRING,"Object"),_SCS("set_base_type"),_SCS("get_base_type")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"constant",PROPERTY_HINT_ENUM,""),_SCS("set_class_constant"),_SCS("get_class_constant")); +} + +VisualScriptClassConstant::VisualScriptClassConstant() { + + base_type="Object"; +} + + +////////////////////////////////////////// +////////////////BASICTYPECONSTANT/////////// +////////////////////////////////////////// + +int VisualScriptBasicTypeConstant::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptBasicTypeConstant::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptBasicTypeConstant::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptBasicTypeConstant::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptBasicTypeConstant::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptBasicTypeConstant::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptBasicTypeConstant::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::INT,"value"); +} + + +String VisualScriptBasicTypeConstant::get_caption() const { + + return "BasicConst"; +} + +String VisualScriptBasicTypeConstant::get_text() const { + + return Variant::get_type_name(type)+"."+String(name); +} + +void VisualScriptBasicTypeConstant::set_basic_type_constant(const StringName& p_which) { + + name=p_which; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptBasicTypeConstant::get_basic_type_constant() const { + return name; +} + + +void VisualScriptBasicTypeConstant::set_basic_type(Variant::Type p_which) { + + type=p_which; + _change_notify(); + ports_changed_notify(); +} + +Variant::Type VisualScriptBasicTypeConstant::get_basic_type() const { + return type; +} + +class VisualScriptNodeInstanceBasicTypeConstant : public VisualScriptNodeInstance { +public: + + int value; + bool valid; + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (!valid) { + r_error_str="Invalid constant name, pick a valid basic type constant."; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + } + + *p_outputs[0] = value; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptBasicTypeConstant::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceBasicTypeConstant * instance = memnew(VisualScriptNodeInstanceBasicTypeConstant ); + instance->value=Variant::get_numeric_constant_value(type,name,&instance->valid); + return instance; +} + +void VisualScriptBasicTypeConstant::_validate_property(PropertyInfo& property) const { + + if (property.name=="constant") { + + List<StringName> constants; + Variant::get_numeric_constants_for_type(type,&constants); + + if (constants.size()==0) { + property.usage=0; + return; + } + property.hint_string=""; + for(List<StringName>::Element *E=constants.front();E;E=E->next()) { + if (property.hint_string!=String()) { + property.hint_string+=","; + } + property.hint_string+=String(E->get()); + } + + } +} + +void VisualScriptBasicTypeConstant::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_basic_type","name"),&VisualScriptBasicTypeConstant::set_basic_type); + ObjectTypeDB::bind_method(_MD("get_basic_type"),&VisualScriptBasicTypeConstant::get_basic_type); + + ObjectTypeDB::bind_method(_MD("set_basic_type_constant","name"),&VisualScriptBasicTypeConstant::set_basic_type_constant); + ObjectTypeDB::bind_method(_MD("get_basic_type_constant"),&VisualScriptBasicTypeConstant::get_basic_type_constant); + + + String argt="Null"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + ADD_PROPERTY(PropertyInfo(Variant::INT,"basic_type",PROPERTY_HINT_ENUM,argt),_SCS("set_basic_type"),_SCS("get_basic_type")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"constant",PROPERTY_HINT_ENUM,""),_SCS("set_basic_type_constant"),_SCS("get_basic_type_constant")); +} + +VisualScriptBasicTypeConstant::VisualScriptBasicTypeConstant() { + + type=Variant::NIL; +} + + + +////////////////////////////////////////// +////////////////MATHCONSTANT/////////// +////////////////////////////////////////// + + +const char* VisualScriptMathConstant::const_name[MATH_CONSTANT_MAX]={ + "One", + "PI", + "PIx2", + "PI/2", + "E", + "Sqrt2", +}; + +double VisualScriptMathConstant::const_value[MATH_CONSTANT_MAX]={ + 1.0, + Math_PI, + Math_PI*2, + Math_PI*0.5, + 2.71828182845904523536, + Math::sqrt(2.0) +}; + + +int VisualScriptMathConstant::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptMathConstant::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptMathConstant::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptMathConstant::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptMathConstant::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptMathConstant::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptMathConstant::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::REAL,"value"); +} + + +String VisualScriptMathConstant::get_caption() const { + + return "MathConst"; +} + +String VisualScriptMathConstant::get_text() const { + + return const_name[constant]; +} + +void VisualScriptMathConstant::set_math_constant(MathConstant p_which) { + + constant=p_which; + _change_notify(); + ports_changed_notify(); +} + +VisualScriptMathConstant::MathConstant VisualScriptMathConstant::get_math_constant() { + return constant; +} + +class VisualScriptNodeInstanceMathConstant : public VisualScriptNodeInstance { +public: + + float value; + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0]=value; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptMathConstant::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceMathConstant * instance = memnew(VisualScriptNodeInstanceMathConstant ); + instance->value=const_value[constant]; + return instance; +} + + +void VisualScriptMathConstant::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_math_constant","which"),&VisualScriptMathConstant::set_math_constant); + ObjectTypeDB::bind_method(_MD("get_math_constant"),&VisualScriptMathConstant::get_math_constant); + + String cc; + + for(int i=0;i<MATH_CONSTANT_MAX;i++) { + + if (i>0) + cc+=","; + cc+=const_name[i]; + } + ADD_PROPERTY(PropertyInfo(Variant::INT,"constant",PROPERTY_HINT_ENUM,cc),_SCS("set_math_constant"),_SCS("get_math_constant")); +} + +VisualScriptMathConstant::VisualScriptMathConstant() { + + constant=MATH_CONSTANT_ONE; +} + + + +////////////////////////////////////////// +////////////////GLOBALSINGLETON/////////// +////////////////////////////////////////// + +int VisualScriptEngineSingleton::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptEngineSingleton::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptEngineSingleton::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptEngineSingleton::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptEngineSingleton::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptEngineSingleton::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptEngineSingleton::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::OBJECT,"instance"); +} + + +String VisualScriptEngineSingleton::get_caption() const { + + return "EngineSingleton"; +} + +String VisualScriptEngineSingleton::get_text() const { + + return singleton; +} + +void VisualScriptEngineSingleton::set_singleton(const String& p_string) { + + singleton=p_string; + + _change_notify(); + ports_changed_notify(); +} + +String VisualScriptEngineSingleton::get_singleton() { + return singleton; +} + + + +class VisualScriptNodeInstanceEngineSingleton : public VisualScriptNodeInstance { +public: + + Object* singleton; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0]=singleton; + return 0; + } + +}; + +VisualScriptNodeInstance* VisualScriptEngineSingleton::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceEngineSingleton * instance = memnew(VisualScriptNodeInstanceEngineSingleton ); + instance->singleton=Globals::get_singleton()->get_singleton_object(singleton); + return instance; +} + +VisualScriptEngineSingleton::TypeGuess VisualScriptEngineSingleton::guess_output_type(TypeGuess* p_inputs, int p_output) const { + + Object *obj=Globals::get_singleton()->get_singleton_object(singleton); + TypeGuess tg; + tg.type=Variant::OBJECT; + if (obj) { + tg.obj_type=obj->get_type(); + tg.script=obj->get_script(); + } + + return tg; +} + + +void VisualScriptEngineSingleton::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_singleton","name"),&VisualScriptEngineSingleton::set_singleton); + ObjectTypeDB::bind_method(_MD("get_singleton"),&VisualScriptEngineSingleton::get_singleton); + + String cc; + + List<Globals::Singleton> singletons; + + Globals::get_singleton()->get_singletons(&singletons); + + for (List<Globals::Singleton>::Element *E=singletons.front();E;E=E->next()) { + if (E->get().name=="VS" || E->get().name=="PS" || E->get().name=="PS2D" || E->get().name=="AS" || E->get().name=="TS" || E->get().name=="SS" || E->get().name=="SS2D") + continue; //skip these, too simple named + + if (cc!=String()) + cc+=","; + cc+=E->get().name; + } + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"constant",PROPERTY_HINT_ENUM,cc),_SCS("set_singleton"),_SCS("get_singleton")); +} + +VisualScriptEngineSingleton::VisualScriptEngineSingleton() { + + singleton=String(); +} + + + +////////////////////////////////////////// +////////////////GETNODE/////////// +////////////////////////////////////////// + +int VisualScriptSceneNode::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptSceneNode::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptSceneNode::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptSceneNode::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptSceneNode::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptSceneNode::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptSceneNode::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::OBJECT,"node"); +} + + +String VisualScriptSceneNode::get_caption() const { + + return "SceneNode"; +} + +String VisualScriptSceneNode::get_text() const { + + return path.simplified(); +} + +void VisualScriptSceneNode::set_node_path(const NodePath& p_path) { + + path=p_path; + _change_notify(); + ports_changed_notify(); +} + +NodePath VisualScriptSceneNode::get_node_path() { + return path; +} + + +class VisualScriptNodeInstanceSceneNode : public VisualScriptNodeInstance { +public: + + VisualScriptSceneNode *node; + VisualScriptInstance *instance; + NodePath path; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + Node* node = instance->get_owner_ptr()->cast_to<Node>(); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Base object is not a Node!"; + return 0; + } + + + + Node* another = node->get_node(path); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Path does not lead Node!"; + return 0; + } + + *p_outputs[0]=another; + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptSceneNode::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceSceneNode * instance = memnew(VisualScriptNodeInstanceSceneNode ); + instance->node=this; + instance->instance=p_instance; + instance->path=path; + return instance; +} + + + + +#ifdef TOOLS_ENABLED + +static Node* _find_script_node(Node* p_edited_scene,Node* p_current_node,const Ref<Script> &script) { + + if (p_edited_scene!=p_current_node && p_current_node->get_owner()!=p_edited_scene) + return NULL; + + Ref<Script> scr = p_current_node->get_script(); + + if (scr.is_valid() && scr==script) + return p_current_node; + + for(int i=0;i<p_current_node->get_child_count();i++) { + Node *n = _find_script_node(p_edited_scene,p_current_node->get_child(i),script); + if (n) + return n; + } + + return NULL; +} + +#endif + +VisualScriptSceneNode::TypeGuess VisualScriptSceneNode::guess_output_type(TypeGuess* p_inputs, int p_output) const { + + + VisualScriptSceneNode::TypeGuess tg; + tg.type=Variant::OBJECT; + tg.obj_type="Node"; + +#ifdef TOOLS_ENABLED + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return tg; + + MainLoop * main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) + return tg; + + SceneTree *scene_tree = main_loop->cast_to<SceneTree>(); + + if (!scene_tree) + return tg; + + Node *edited_scene = scene_tree->get_edited_scene_root(); + + if (!edited_scene) + return tg; + + Node* script_node = _find_script_node(edited_scene,edited_scene,script); + + if (!script_node) + return tg; + + Node* another = script_node->get_node(path); + + if (another) { + tg.obj_type=another->get_type(); + tg.script=another->get_script(); + } +#endif + return tg; + +} + + +void VisualScriptSceneNode::_validate_property(PropertyInfo& property) const { + +#ifdef TOOLS_ENABLED + if (property.name=="node_path") { + + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return; + + MainLoop * main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) + return; + + SceneTree *scene_tree = main_loop->cast_to<SceneTree>(); + + if (!scene_tree) + return; + + Node *edited_scene = scene_tree->get_edited_scene_root(); + + if (!edited_scene) + return; + + Node* script_node = _find_script_node(edited_scene,edited_scene,script); + + if (!script_node) + return; + + property.hint_string=script_node->get_path(); + } +#endif +} + +void VisualScriptSceneNode::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_node_path","path"),&VisualScriptSceneNode::set_node_path); + ObjectTypeDB::bind_method(_MD("get_node_path"),&VisualScriptSceneNode::get_node_path); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH,"node_path",PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE),_SCS("set_node_path"),_SCS("get_node_path")); +} + +VisualScriptSceneNode::VisualScriptSceneNode() { + + path=String("."); +} + + +////////////////////////////////////////// +////////////////SceneTree/////////// +////////////////////////////////////////// + +int VisualScriptSceneTree::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptSceneTree::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptSceneTree::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptSceneTree::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptSceneTree::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptSceneTree::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptSceneTree::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::OBJECT,"instance"); +} + + +String VisualScriptSceneTree::get_caption() const { + + return "SceneTree"; +} + +String VisualScriptSceneTree::get_text() const { + + return ""; +} + + +class VisualScriptNodeInstanceSceneTree : public VisualScriptNodeInstance { +public: + + VisualScriptSceneTree *node; + VisualScriptInstance *instance; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + Node* node = instance->get_owner_ptr()->cast_to<Node>(); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Base object is not a Node!"; + return 0; + } + + SceneTree* tree = node->get_tree(); + if (!tree) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Attempt to get SceneTree while node is not in the active tree."; + return 0; + } + + *p_outputs[0]=tree; + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptSceneTree::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceSceneTree * instance = memnew(VisualScriptNodeInstanceSceneTree ); + instance->node=this; + instance->instance=p_instance; + return instance; +} + +VisualScriptSceneTree::TypeGuess VisualScriptSceneTree::guess_output_type(TypeGuess* p_inputs, int p_output) const { + + TypeGuess tg; + tg.type=Variant::OBJECT; + tg.obj_type="SceneTree"; + return tg; +} + +void VisualScriptSceneTree::_validate_property(PropertyInfo& property) const { + +} + +void VisualScriptSceneTree::_bind_methods() { + +} + +VisualScriptSceneTree::VisualScriptSceneTree() { + +} + + +////////////////////////////////////////// +////////////////RESPATH/////////// +////////////////////////////////////////// + +int VisualScriptResourcePath::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptResourcePath::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptResourcePath::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptResourcePath::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptResourcePath::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptResourcePath::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptResourcePath::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::STRING,"path"); +} + + +String VisualScriptResourcePath::get_caption() const { + + return "ResourcePath"; +} + +String VisualScriptResourcePath::get_text() const { + + return path; +} + +void VisualScriptResourcePath::set_resource_path(const String& p_path) { + + path=p_path; + _change_notify(); + ports_changed_notify(); +} + +String VisualScriptResourcePath::get_resource_path() { + return path; +} + + +class VisualScriptNodeInstanceResourcePath : public VisualScriptNodeInstance { +public: + + String path; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0] = path; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptResourcePath::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceResourcePath * instance = memnew(VisualScriptNodeInstanceResourcePath ); + instance->path=path; + return instance; +} + + + +void VisualScriptResourcePath::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_resource_path","path"),&VisualScriptResourcePath::set_resource_path); + ObjectTypeDB::bind_method(_MD("get_resource_path"),&VisualScriptResourcePath::get_resource_path); + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"path",PROPERTY_HINT_FILE),_SCS("set_resource_path"),_SCS("get_resource_path")); +} + +VisualScriptResourcePath::VisualScriptResourcePath() { + + path=""; +} + + + +////////////////////////////////////////// +////////////////SELF/////////// +////////////////////////////////////////// + +int VisualScriptSelf::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptSelf::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptSelf::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptSelf::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptSelf::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptSelf::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptSelf::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::OBJECT,"instance"); +} + + +String VisualScriptSelf::get_caption() const { + + return "Self"; +} + +String VisualScriptSelf::get_text() const { + + if (get_visual_script().is_valid()) + return get_visual_script()->get_instance_base_type(); + else + return ""; +} + + +class VisualScriptNodeInstanceSelf : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0] = instance->get_owner_ptr(); + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptSelf::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceSelf * instance = memnew(VisualScriptNodeInstanceSelf ); + instance->instance=p_instance; + return instance; +} + +VisualScriptSelf::TypeGuess VisualScriptSelf::guess_output_type(TypeGuess* p_inputs, int p_output) const { + + VisualScriptSceneNode::TypeGuess tg; + tg.type=Variant::OBJECT; + tg.obj_type="Object"; + + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return tg; + + tg.obj_type=script->get_instance_base_type(); + tg.script=script; + + return tg; + + +} + + +void VisualScriptSelf::_bind_methods() { + +} + +VisualScriptSelf::VisualScriptSelf() { + + +} + +////////////////////////////////////////// +////////////////CUSTOM (SCRIPTED)/////////// +////////////////////////////////////////// + +int VisualScriptCustomNode::get_output_sequence_port_count() const { + + if (get_script_instance() && get_script_instance()->has_method("_get_output_sequence_port_count")) { + return get_script_instance()->call("_get_output_sequence_port_count"); + } + return 0; +} + +bool VisualScriptCustomNode::has_input_sequence_port() const{ + + if (get_script_instance() && get_script_instance()->has_method("_has_input_sequence_port")) { + return get_script_instance()->call("_has_input_sequence_port"); + } + return false; +} + +int VisualScriptCustomNode::get_input_value_port_count() const{ + + if (get_script_instance() && get_script_instance()->has_method("_get_input_value_port_count")) { + return get_script_instance()->call("_get_input_value_port_count"); + } + return 0; +} +int VisualScriptCustomNode::get_output_value_port_count() const{ + + if (get_script_instance() && get_script_instance()->has_method("_get_output_value_port_count")) { + return get_script_instance()->call("_get_output_value_port_count"); + } + return 0; +} + +String VisualScriptCustomNode::get_output_sequence_port_text(int p_port) const { + + if (get_script_instance() && get_script_instance()->has_method("_get_output_sequence_port_text")) { + return get_script_instance()->call("_get_output_sequence_port_text",p_port); + } + + return String(); +} + +PropertyInfo VisualScriptCustomNode::get_input_value_port_info(int p_idx) const{ + + PropertyInfo info; + if (get_script_instance() && get_script_instance()->has_method("_get_input_value_port_type")) { + info.type=Variant::Type(int(get_script_instance()->call("_get_input_value_port_type",p_idx))); + } + if (get_script_instance() && get_script_instance()->has_method("_get_input_value_port_name")) { + info.name=get_script_instance()->call("_get_input_value_port_name",p_idx); + } + return info; +} + +PropertyInfo VisualScriptCustomNode::get_output_value_port_info(int p_idx) const{ + + PropertyInfo info; + if (get_script_instance() && get_script_instance()->has_method("_get_output_value_port_type")) { + info.type=Variant::Type(int(get_script_instance()->call("_get_output_value_port_type",p_idx))); + } + if (get_script_instance() && get_script_instance()->has_method("_get_output_value_port_name")) { + info.name=get_script_instance()->call("_get_output_value_port_name",p_idx); + } + return info; +} + + +String VisualScriptCustomNode::get_caption() const { + + if (get_script_instance() && get_script_instance()->has_method("_get_caption")) { + return get_script_instance()->call("_get_caption"); + } + return "CustomNode"; +} + +String VisualScriptCustomNode::get_text() const { + + if (get_script_instance() && get_script_instance()->has_method("_get_text")) { + return get_script_instance()->call("_get_text"); + } + return ""; +} + +String VisualScriptCustomNode::get_category() const { + + if (get_script_instance() && get_script_instance()->has_method("_get_category")) { + return get_script_instance()->call("_get_category"); + } + return "custom"; +} + +class VisualScriptNodeInstanceCustomNode : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + VisualScriptCustomNode *node; + int in_count; + int out_count; + int work_mem_size; + + virtual int get_working_memory_size() const { return work_mem_size; } + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (node->get_script_instance()) { +#ifdef DEBUG_ENABLED + if (!node->get_script_instance()->has_method(VisualScriptLanguage::singleton->_step)) { + r_error_str=RTR("Custom node has no _step() method, can't process graph."); + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } +#endif + Array in_values(true); + Array out_values(true); + Array work_mem(true); + + in_values.resize(in_count); + + for(int i=0;i<in_count;i++) { + in_values[i]=p_inputs[i]; + } + + out_values.resize(in_count); + + work_mem.resize(work_mem_size); + + for(int i=0;i<work_mem_size;i++) { + work_mem[i]=p_working_mem[i]; + } + + int ret_out; + + Variant ret = node->get_script_instance()->call(VisualScriptLanguage::singleton->_step,in_values,out_values,p_start_mode,work_mem); + if (ret.get_type()==Variant::STRING) { + r_error_str=ret; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } else if (ret.is_num()) { + ret_out=ret; + } else { + r_error_str=RTR("Invalid return value from _step(), must be integer (seq out), or string (error)."); + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } + + for(int i=0;i<out_count;i++) { + if (i<out_values.size()) { + *p_outputs[i]=out_values[i]; + } + } + + for(int i=0;i<work_mem_size;i++) { + if (i<work_mem.size()) { + p_working_mem[i]=work_mem[i]; + } + } + + return ret_out; + + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptCustomNode::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceCustomNode * instance = memnew(VisualScriptNodeInstanceCustomNode ); + instance->instance=p_instance; + instance->in_count=get_input_value_port_count(); + instance->out_count=get_output_value_port_count(); + + + if (get_script_instance() && get_script_instance()->has_method("_get_working_memory_size")) { + instance->work_mem_size = get_script_instance()->call("_get_working_memory_size"); + } else { + instance->work_mem_size=0; + } + + return instance; +} + + + +void VisualScriptCustomNode::_bind_methods() { + + BIND_VMETHOD( MethodInfo(Variant::INT,"_get_output_sequence_port_count") ); + BIND_VMETHOD( MethodInfo(Variant::BOOL,"_has_input_sequence_port") ); + + BIND_VMETHOD( MethodInfo(Variant::STRING,"_get_output_sequence_port_text",PropertyInfo(Variant::INT,"idx")) ); + BIND_VMETHOD( MethodInfo(Variant::INT,"_get_input_value_port_count") ); + BIND_VMETHOD( MethodInfo(Variant::INT,"_get_output_value_port_count") ); + + BIND_VMETHOD( MethodInfo(Variant::INT,"_get_input_value_port_type",PropertyInfo(Variant::INT,"idx")) ); + BIND_VMETHOD( MethodInfo(Variant::STRING,"_get_input_value_port_name",PropertyInfo(Variant::INT,"idx")) ); + + BIND_VMETHOD( MethodInfo(Variant::INT,"_get_output_value_port_type",PropertyInfo(Variant::INT,"idx")) ); + BIND_VMETHOD( MethodInfo(Variant::STRING,"_get_output_value_port_name",PropertyInfo(Variant::INT,"idx")) ); + + BIND_VMETHOD( MethodInfo(Variant::STRING,"_get_caption") ); + BIND_VMETHOD( MethodInfo(Variant::STRING,"_get_text") ); + BIND_VMETHOD( MethodInfo(Variant::STRING,"_get_category") ); + + BIND_VMETHOD( MethodInfo(Variant::INT,"_get_working_memory_size") ); + BIND_VMETHOD( MethodInfo(Variant::NIL,"_step:Variant",PropertyInfo(Variant::ARRAY,"inputs"),PropertyInfo(Variant::ARRAY,"outputs"),PropertyInfo(Variant::INT,"start_mode"),PropertyInfo(Variant::ARRAY,"working_mem")) ); + + BIND_CONSTANT( START_MODE_BEGIN_SEQUENCE ); + BIND_CONSTANT( START_MODE_CONTINUE_SEQUENCE ); + BIND_CONSTANT( START_MODE_RESUME_YIELD ); + + BIND_CONSTANT( STEP_PUSH_STACK_BIT ); + BIND_CONSTANT( STEP_GO_BACK_BIT ); + BIND_CONSTANT( STEP_NO_ADVANCE_BIT ); + BIND_CONSTANT( STEP_EXIT_FUNCTION_BIT ); + BIND_CONSTANT( STEP_YIELD_BIT ); + +} + +VisualScriptCustomNode::VisualScriptCustomNode() { + + +} + +////////////////////////////////////////// +////////////////SUBCALL/////////// +////////////////////////////////////////// + +int VisualScriptSubCall::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptSubCall::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptSubCall::get_input_value_port_count() const{ + + Ref<Script> script = get_script(); + + if (script.is_valid() && script->has_method(VisualScriptLanguage::singleton->_subcall)) { + + MethodInfo mi = script->get_method_info(VisualScriptLanguage::singleton->_subcall); + return mi.arguments.size(); + } + + return 0; +} +int VisualScriptSubCall::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptSubCall::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptSubCall::get_input_value_port_info(int p_idx) const{ + + Ref<Script> script = get_script(); + if (script.is_valid() && script->has_method(VisualScriptLanguage::singleton->_subcall)) { + + MethodInfo mi = script->get_method_info(VisualScriptLanguage::singleton->_subcall); + return mi.arguments[p_idx]; + } + + return PropertyInfo(); +} + +PropertyInfo VisualScriptSubCall::get_output_value_port_info(int p_idx) const{ + + Ref<Script> script = get_script(); + if (script.is_valid() && script->has_method(VisualScriptLanguage::singleton->_subcall)) { + MethodInfo mi = script->get_method_info(VisualScriptLanguage::singleton->_subcall); + return mi.return_val; + } + return PropertyInfo(); +} + + +String VisualScriptSubCall::get_caption() const { + + return "SubCall"; +} + + +String VisualScriptSubCall::get_text() const { + + Ref<Script> script = get_script(); + if (script.is_valid()) { + if (script->get_name()!=String()) + return script->get_name(); + if (script->get_path().is_resource_file()) + return script->get_path().get_file(); + return script->get_type(); + } + return ""; +} + +String VisualScriptSubCall::get_category() const { + + return "custom"; +} + +class VisualScriptNodeInstanceSubCall : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + VisualScriptSubCall *subcall; + int input_args; + bool valid; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (!valid) { + r_error_str="Node requires a script with a _subcall(<args>) method to work."; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } + *p_outputs[0]=subcall->call(VisualScriptLanguage::singleton->_subcall,p_inputs,input_args,r_error_str); + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptSubCall::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceSubCall * instance = memnew(VisualScriptNodeInstanceSubCall ); + instance->instance=p_instance; + Ref<Script> script = get_script(); + if (script.is_valid() && script->has_method(VisualScriptLanguage::singleton->_subcall)) { + instance->valid=true; + instance->input_args=get_input_value_port_count(); + } else { + instance->valid=false; + } + return instance; +} + + + +void VisualScriptSubCall::_bind_methods() { + + BIND_VMETHOD( MethodInfo(Variant::NIL,"_subcall",PropertyInfo(Variant::NIL,"arguments:Variant")) ); + +} + +VisualScriptSubCall::VisualScriptSubCall() { + + +} + +////////////////////////////////////////// +////////////////Comment/////////// +////////////////////////////////////////// + +int VisualScriptComment::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptComment::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptComment::get_input_value_port_count() const{ + return 0; +} +int VisualScriptComment::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptComment::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptComment::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptComment::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptComment::get_caption() const { + + return title; +} + + +String VisualScriptComment::get_text() const { + + return description; +} + +void VisualScriptComment::set_title(const String& p_title) { + + + if (title==p_title) + return; + title=p_title; + ports_changed_notify(); +} + +String VisualScriptComment::get_title() const{ + + return title; +} + +void VisualScriptComment::set_description(const String& p_description){ + + if (description==p_description) + return; + description=p_description; + ports_changed_notify(); + +} +String VisualScriptComment::get_description() const{ + + return description; +} + +void VisualScriptComment::set_size(const Size2& p_size){ + + if (size==p_size) + return; + size=p_size; + ports_changed_notify(); + +} +Size2 VisualScriptComment::get_size() const{ + + return size; +} + + +String VisualScriptComment::get_category() const { + + return "data"; +} + +class VisualScriptNodeInstanceComment : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptComment::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceComment * instance = memnew(VisualScriptNodeInstanceComment ); + instance->instance=p_instance; + return instance; +} + + + +void VisualScriptComment::_bind_methods() { + + + ObjectTypeDB::bind_method(_MD("set_title","title"),&VisualScriptComment::set_title); + ObjectTypeDB::bind_method(_MD("get_title"),&VisualScriptComment::get_title); + + ObjectTypeDB::bind_method(_MD("set_description","description"),&VisualScriptComment::set_description); + ObjectTypeDB::bind_method(_MD("get_description"),&VisualScriptComment::get_description); + + ObjectTypeDB::bind_method(_MD("set_size","size"),&VisualScriptComment::set_size); + ObjectTypeDB::bind_method(_MD("get_size"),&VisualScriptComment::get_size); + + ADD_PROPERTY( PropertyInfo(Variant::STRING,"title"),_SCS("set_title"),_SCS("get_title")); + ADD_PROPERTY( PropertyInfo(Variant::STRING,"description",PROPERTY_HINT_MULTILINE_TEXT),_SCS("set_description"),_SCS("get_description")); + ADD_PROPERTY( PropertyInfo(Variant::VECTOR2,"size"),_SCS("set_size"),_SCS("get_size")); + +} + +VisualScriptComment::VisualScriptComment() { + + title="Comment"; + size=Size2(150,150); +} + + +////////////////////////////////////////// +////////////////Constructor/////////// +////////////////////////////////////////// + +int VisualScriptConstructor::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptConstructor::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptConstructor::get_input_value_port_count() const{ + return constructor.arguments.size(); +} +int VisualScriptConstructor::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptConstructor::get_output_sequence_port_text(int p_port) const { + + return ""; +} + +PropertyInfo VisualScriptConstructor::get_input_value_port_info(int p_idx) const{ + + return constructor.arguments[p_idx]; +} + +PropertyInfo VisualScriptConstructor::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(type,"value"); +} + + +String VisualScriptConstructor::get_caption() const { + + return "Construct"; +} + + +String VisualScriptConstructor::get_text() const { + + return "new "+Variant::get_type_name(type)+"()"; +} + + +String VisualScriptConstructor::get_category() const { + + return "functions"; +} + +void VisualScriptConstructor::set_constructor_type(Variant::Type p_type) { + + if (type==p_type) + return; + + type=p_type; + ports_changed_notify(); +} + +Variant::Type VisualScriptConstructor::get_constructor_type() const { + + return type; +} + +void VisualScriptConstructor::set_constructor(const Dictionary& p_info) { + + constructor=MethodInfo::from_dict(p_info); + ports_changed_notify(); +} + +Dictionary VisualScriptConstructor::get_constructor() const { + + return constructor; +} + + +class VisualScriptNodeInstanceConstructor : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + Variant::Type type; + int argcount; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + Variant::CallError ce; + *p_outputs[0]=Variant::construct(type,p_inputs,argcount,ce); + if (ce.error!=Variant::CallError::CALL_OK) { + r_error_str="Invalid arguments for constructor"; + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptConstructor::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceConstructor * instance = memnew(VisualScriptNodeInstanceConstructor ); + instance->instance=p_instance; + instance->type=type; + instance->argcount=constructor.arguments.size(); + return instance; +} + + + +void VisualScriptConstructor::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_constructor_type","type"),&VisualScriptConstructor::set_constructor_type); + ObjectTypeDB::bind_method(_MD("get_constructor_type"),&VisualScriptConstructor::get_constructor_type); + + ObjectTypeDB::bind_method(_MD("set_constructor","constructor"),&VisualScriptConstructor::set_constructor); + ObjectTypeDB::bind_method(_MD("get_constructor"),&VisualScriptConstructor::get_constructor); + + ADD_PROPERTY( PropertyInfo(Variant::INT,"type",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("set_constructor_type"),_SCS("get_constructor_type")); + ADD_PROPERTY( PropertyInfo(Variant::DICTIONARY,"constructor",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("set_constructor"),_SCS("get_constructor")); + +} + +VisualScriptConstructor::VisualScriptConstructor() { + + type=Variant::NIL; + +} + +static Map<String,Pair<Variant::Type,MethodInfo> > constructor_map; + +static Ref<VisualScriptNode> create_constructor_node(const String& p_name) { + + ERR_FAIL_COND_V(!constructor_map.has(p_name),Ref<VisualScriptNode>()); + + Ref<VisualScriptConstructor> vsc; + vsc.instance(); + vsc->set_constructor_type(constructor_map[p_name].first); + vsc->set_constructor(constructor_map[p_name].second); + + return vsc; +} + +////////////////////////////////////////// +////////////////LocalVar/////////// +////////////////////////////////////////// + +int VisualScriptLocalVar::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptLocalVar::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptLocalVar::get_input_value_port_count() const{ + return 0; +} +int VisualScriptLocalVar::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptLocalVar::get_output_sequence_port_text(int p_port) const { + + return ""; +} + +PropertyInfo VisualScriptLocalVar::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} +PropertyInfo VisualScriptLocalVar::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(type,"get"); +} + + +String VisualScriptLocalVar::get_caption() const { + + return "LocalVarGet"; +} + + +String VisualScriptLocalVar::get_text() const { + + return name; +} + + +String VisualScriptLocalVar::get_category() const { + + return "data"; +} + + +void VisualScriptLocalVar::set_var_name(const StringName& p_name) { + + if (name==p_name) + return; + + name=p_name; + ports_changed_notify(); + +} + +StringName VisualScriptLocalVar::get_var_name() const { + + return name; +} + +void VisualScriptLocalVar::set_var_type(Variant::Type p_type) { + + type=p_type; + ports_changed_notify(); +} + +Variant::Type VisualScriptLocalVar::get_var_type() const { + + return type; +} + + +class VisualScriptNodeInstanceLocalVar : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + StringName name; + + + virtual int get_working_memory_size() const { return 1; } + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0]=*p_working_mem; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptLocalVar::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceLocalVar * instance = memnew(VisualScriptNodeInstanceLocalVar ); + instance->instance=p_instance; + instance->name=name; + + return instance; +} + + + +void VisualScriptLocalVar::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_var_name","name"),&VisualScriptLocalVar::set_var_name); + ObjectTypeDB::bind_method(_MD("get_var_name"),&VisualScriptLocalVar::get_var_name); + + ObjectTypeDB::bind_method(_MD("set_var_type","type"),&VisualScriptLocalVar::set_var_type); + ObjectTypeDB::bind_method(_MD("get_var_type"),&VisualScriptLocalVar::get_var_type); + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + ADD_PROPERTY( PropertyInfo(Variant::STRING,"variable/name"),_SCS("set_var_name"),_SCS("get_var_name")); + ADD_PROPERTY( PropertyInfo(Variant::INT,"variable/type",PROPERTY_HINT_ENUM,argt),_SCS("set_var_type"),_SCS("get_var_type")); + + +} + +VisualScriptLocalVar::VisualScriptLocalVar() { + + name="new_local"; + type=Variant::NIL; + +} + +////////////////////////////////////////// +////////////////LocalVar/////////// +////////////////////////////////////////// + +int VisualScriptLocalVarSet::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptLocalVarSet::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptLocalVarSet::get_input_value_port_count() const{ + return 1; +} +int VisualScriptLocalVarSet::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptLocalVarSet::get_output_sequence_port_text(int p_port) const { + + return ""; +} + +PropertyInfo VisualScriptLocalVarSet::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(type,"set"); +} +PropertyInfo VisualScriptLocalVarSet::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(type,"get"); +} + + +String VisualScriptLocalVarSet::get_caption() const { + + return "LocalVarSet"; +} + + +String VisualScriptLocalVarSet::get_text() const { + + return name; +} + + +String VisualScriptLocalVarSet::get_category() const { + + return "data"; +} + + +void VisualScriptLocalVarSet::set_var_name(const StringName& p_name) { + + if (name==p_name) + return; + + name=p_name; + ports_changed_notify(); + +} + +StringName VisualScriptLocalVarSet::get_var_name() const { + + return name; +} + +void VisualScriptLocalVarSet::set_var_type(Variant::Type p_type) { + + type=p_type; + ports_changed_notify(); +} + +Variant::Type VisualScriptLocalVarSet::get_var_type() const { + + return type; +} + + +class VisualScriptNodeInstanceLocalVarSet : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + StringName name; + + + virtual int get_working_memory_size() const { return 1; } + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_working_mem=*p_inputs[0]; + *p_outputs[0]=*p_working_mem; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptLocalVarSet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceLocalVarSet * instance = memnew(VisualScriptNodeInstanceLocalVarSet ); + instance->instance=p_instance; + instance->name=name; + + return instance; +} + + + +void VisualScriptLocalVarSet::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_var_name","name"),&VisualScriptLocalVarSet::set_var_name); + ObjectTypeDB::bind_method(_MD("get_var_name"),&VisualScriptLocalVarSet::get_var_name); + + ObjectTypeDB::bind_method(_MD("set_var_type","type"),&VisualScriptLocalVarSet::set_var_type); + ObjectTypeDB::bind_method(_MD("get_var_type"),&VisualScriptLocalVarSet::get_var_type); + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + ADD_PROPERTY( PropertyInfo(Variant::STRING,"variable/name"),_SCS("set_var_name"),_SCS("get_var_name")); + ADD_PROPERTY( PropertyInfo(Variant::INT,"variable/type",PROPERTY_HINT_ENUM,argt),_SCS("set_var_type"),_SCS("get_var_type")); + + +} + +VisualScriptLocalVarSet::VisualScriptLocalVarSet() { + + name="new_local"; + type=Variant::NIL; + +} + + +////////////////////////////////////////// +////////////////LocalVar/////////// +////////////////////////////////////////// + +int VisualScriptInputAction::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptInputAction::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptInputAction::get_input_value_port_count() const{ + return 0; +} +int VisualScriptInputAction::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptInputAction::get_output_sequence_port_text(int p_port) const { + + return ""; +} + +PropertyInfo VisualScriptInputAction::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} +PropertyInfo VisualScriptInputAction::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::BOOL,"pressed"); +} + + +String VisualScriptInputAction::get_caption() const { + + + return "Action"; +} + + +String VisualScriptInputAction::get_text() const { + + switch(mode) { + case MODE_PRESSED: { + return name; + } break; + case MODE_RELEASED: { + return "not "+name; + } break; + case MODE_JUST_PRESSED: { + return String(name)+" "+TTR("just pressed"); + } break; + case MODE_JUST_RELEASED: { + return String(name)+" "+TTR("just released"); + } break; + } + + return String(); +} + + +String VisualScriptInputAction::get_category() const { + + return "data"; +} + + +void VisualScriptInputAction::set_action_name(const StringName& p_name) { + + if (name==p_name) + return; + + name=p_name; + ports_changed_notify(); + +} + +StringName VisualScriptInputAction::get_action_name() const { + + return name; +} + +void VisualScriptInputAction::set_action_mode(Mode p_mode) { + + if (mode==p_mode) + return; + + mode=p_mode; + ports_changed_notify(); + +} +VisualScriptInputAction::Mode VisualScriptInputAction::get_action_mode() const { + + return mode; +} + + +class VisualScriptNodeInstanceInputAction : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + StringName action; + VisualScriptInputAction::Mode mode; + + + virtual int get_working_memory_size() const { return 1; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + switch(mode) { + case VisualScriptInputAction::MODE_PRESSED: { + *p_outputs[0]=Input::get_singleton()->is_action_pressed(action); + } break; + case VisualScriptInputAction::MODE_RELEASED: { + *p_outputs[0]=!Input::get_singleton()->is_action_pressed(action); + } break; + case VisualScriptInputAction::MODE_JUST_PRESSED: { + *p_outputs[0]=Input::get_singleton()->is_action_just_pressed(action); + } break; + case VisualScriptInputAction:: MODE_JUST_RELEASED: { + *p_outputs[0]=Input::get_singleton()->is_action_just_released(action); + } break; + + } + + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptInputAction::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceInputAction * instance = memnew(VisualScriptNodeInstanceInputAction ); + instance->instance=p_instance; + instance->action=name; + instance->mode=mode; + + return instance; +} + +void VisualScriptInputAction::_validate_property(PropertyInfo& property) const { + + + if (property.name=="action") { + + property.hint=PROPERTY_HINT_ENUM; + String actions; + + List<PropertyInfo> pinfo; + Globals::get_singleton()->get_property_list(&pinfo); + Vector<String> al; + + for(List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + const PropertyInfo &pi=E->get(); + + if (!pi.name.begins_with("input/")) + continue; + + String name = pi.name.substr(pi.name.find("/")+1,pi.name.length()); + + + al.push_back(name); + } + + al.sort();; + + for(int i=0;i<al.size();i++) { + if (actions!=String()) + actions+=","; + actions+=al[i]; + } + + property.hint_string=actions; + } +} + + +void VisualScriptInputAction::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_action_name","name"),&VisualScriptInputAction::set_action_name); + ObjectTypeDB::bind_method(_MD("get_action_name"),&VisualScriptInputAction::get_action_name); + + ObjectTypeDB::bind_method(_MD("set_action_mode","mode"),&VisualScriptInputAction::set_action_mode); + ObjectTypeDB::bind_method(_MD("get_action_mode"),&VisualScriptInputAction::get_action_mode); + + ADD_PROPERTY( PropertyInfo(Variant::STRING,"action"),_SCS("set_action_name"),_SCS("get_action_name")); + ADD_PROPERTY( PropertyInfo(Variant::INT,"mode",PROPERTY_HINT_ENUM,"Pressed,Released,JustPressed,JustReleased"),_SCS("set_action_mode"),_SCS("get_action_mode")); + +} + +VisualScriptInputAction::VisualScriptInputAction() { + + name=""; + mode=MODE_PRESSED; + +} + +////////////////////////////////////////// +////////////////Constructor/////////// +////////////////////////////////////////// + +int VisualScriptDeconstruct::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptDeconstruct::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptDeconstruct::get_input_value_port_count() const{ + return 1; +} +int VisualScriptDeconstruct::get_output_value_port_count() const{ + + return elements.size(); +} + +String VisualScriptDeconstruct::get_output_sequence_port_text(int p_port) const { + + return ""; +} + +PropertyInfo VisualScriptDeconstruct::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(type,"value"); +} + +PropertyInfo VisualScriptDeconstruct::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(elements[p_idx].type,elements[p_idx].name); +} + + +String VisualScriptDeconstruct::get_caption() const { + + return "Deconstruct"; +} + + +String VisualScriptDeconstruct::get_text() const { + + return "from "+Variant::get_type_name(type)+":"; +} + + +String VisualScriptDeconstruct::get_category() const { + + return "functions"; +} + +void VisualScriptDeconstruct::_update_elements() { + + elements.clear();; + Variant v; + if (type==Variant::INPUT_EVENT) { + InputEvent ie; + ie.type=input_type; + v=ie; + } else { + Variant::CallError ce; + v = Variant::construct(type,NULL,0,ce); + } + + List<PropertyInfo> pinfo; + v.get_property_list(&pinfo); + + for (List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + + Element e; + e.name=E->get().name; + e.type=E->get().type; + elements.push_back(e); + } +} + +void VisualScriptDeconstruct::set_deconstruct_type(Variant::Type p_type) { + + if (type==p_type) + return; + + type=p_type; + _update_elements(); + ports_changed_notify(); + _change_notify(); //to make input appear/disappear +} + +Variant::Type VisualScriptDeconstruct::get_deconstruct_type() const { + + return type; +} + +void VisualScriptDeconstruct::set_deconstruct_input_type(InputEvent::Type p_input_type) { + + if (input_type==p_input_type) + return; + + input_type=p_input_type; + _update_elements(); + ports_changed_notify(); +} + +InputEvent::Type VisualScriptDeconstruct::get_deconstruct_input_type() const { + + return input_type; +} + +void VisualScriptDeconstruct::_set_elem_cache(const Array& p_elements) { + + ERR_FAIL_COND(p_elements.size()%2==1); + elements.resize(p_elements.size()/2); + for(int i=0;i<elements.size();i++) { + elements[i].name=p_elements[i*2+0]; + elements[i].type=Variant::Type(int(p_elements[i*2+1])); + } +} + +Array VisualScriptDeconstruct::_get_elem_cache() const { + + Array ret; + for(int i=0;i<elements.size();i++) { + ret.push_back(elements[i].name); + ret.push_back(elements[i].type); + } + return ret; +} + +class VisualScriptNodeInstanceDeconstruct : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + Vector<StringName> outputs; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + Variant in=*p_inputs[0]; + + for(int i=0;i<outputs.size();i++) { + bool valid; + *p_outputs[i]=in.get(outputs[i],&valid); + if (!valid) { + r_error_str="Can't obtain element '"+String(outputs[i])+"' from "+Variant::get_type_name(in.get_type()); + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } + + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptDeconstruct::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceDeconstruct * instance = memnew(VisualScriptNodeInstanceDeconstruct ); + instance->instance=p_instance; + instance->outputs.resize(elements.size()); + for(int i=0;i<elements.size();i++) { + instance->outputs[i]=elements[i].name; + } + + return instance; +} + + + +void VisualScriptDeconstruct::_validate_property(PropertyInfo& property) const { + + if (property.name=="input_type") { + if (type!=Variant::INPUT_EVENT) { + property.usage=0; + } + } +} + + +void VisualScriptDeconstruct::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_deconstruct_type","type"),&VisualScriptDeconstruct::set_deconstruct_type); + ObjectTypeDB::bind_method(_MD("get_deconstruct_type"),&VisualScriptDeconstruct::get_deconstruct_type); + + ObjectTypeDB::bind_method(_MD("set_deconstruct_input_type","input_type"),&VisualScriptDeconstruct::set_deconstruct_input_type); + ObjectTypeDB::bind_method(_MD("get_deconstruct_input_type"),&VisualScriptDeconstruct::get_deconstruct_input_type); + + ObjectTypeDB::bind_method(_MD("_set_elem_cache","_cache"),&VisualScriptDeconstruct::_set_elem_cache); + ObjectTypeDB::bind_method(_MD("_get_elem_cache"),&VisualScriptDeconstruct::_get_elem_cache); + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + String iet="None,Key,MouseMotion,MouseButton,JoystickMotion,JoystickButton,ScreenTouch,ScreenDrag,Action"; + + ADD_PROPERTY( PropertyInfo(Variant::INT,"type",PROPERTY_HINT_ENUM,argt),_SCS("set_deconstruct_type"),_SCS("get_deconstruct_type")); + ADD_PROPERTY( PropertyInfo(Variant::INT,"input_type",PROPERTY_HINT_ENUM,iet),_SCS("set_deconstruct_input_type"),_SCS("get_deconstruct_input_type")); + ADD_PROPERTY( PropertyInfo(Variant::ARRAY,"elem_cache",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("_set_elem_cache"),_SCS("_get_elem_cache")); + +} + +VisualScriptDeconstruct::VisualScriptDeconstruct() { + + type=Variant::NIL; + input_type=InputEvent::NONE; + +} + + +void register_visual_script_nodes() { + + VisualScriptLanguage::singleton->add_register_func("data/set_variable",create_node_generic<VisualScriptVariableSet>); + VisualScriptLanguage::singleton->add_register_func("data/get_variable",create_node_generic<VisualScriptVariableGet>); + VisualScriptLanguage::singleton->add_register_func("data/engine_singleton",create_node_generic<VisualScriptEngineSingleton>); + VisualScriptLanguage::singleton->add_register_func("data/scene_node",create_node_generic<VisualScriptSceneNode>); + VisualScriptLanguage::singleton->add_register_func("data/scene_tree",create_node_generic<VisualScriptSceneTree>); + VisualScriptLanguage::singleton->add_register_func("data/resource_path",create_node_generic<VisualScriptResourcePath>); + VisualScriptLanguage::singleton->add_register_func("data/self",create_node_generic<VisualScriptSelf>); + VisualScriptLanguage::singleton->add_register_func("data/comment",create_node_generic<VisualScriptComment>); + VisualScriptLanguage::singleton->add_register_func("data/get_local_variable",create_node_generic<VisualScriptLocalVar>); + VisualScriptLanguage::singleton->add_register_func("data/set_local_variable",create_node_generic<VisualScriptLocalVarSet>); + VisualScriptLanguage::singleton->add_register_func("data/preload",create_node_generic<VisualScriptPreload>); + VisualScriptLanguage::singleton->add_register_func("data/action",create_node_generic<VisualScriptInputAction>); + + VisualScriptLanguage::singleton->add_register_func("constants/constant",create_node_generic<VisualScriptConstant>); + VisualScriptLanguage::singleton->add_register_func("constants/math_constant",create_node_generic<VisualScriptMathConstant>); + VisualScriptLanguage::singleton->add_register_func("constants/class_constant",create_node_generic<VisualScriptClassConstant>); + VisualScriptLanguage::singleton->add_register_func("constants/global_constant",create_node_generic<VisualScriptGlobalConstant>); + VisualScriptLanguage::singleton->add_register_func("constants/basic_type_constant",create_node_generic<VisualScriptBasicTypeConstant>); + + VisualScriptLanguage::singleton->add_register_func("custom/custom_node",create_node_generic<VisualScriptCustomNode>); + VisualScriptLanguage::singleton->add_register_func("custom/sub_call",create_node_generic<VisualScriptSubCall>); + + VisualScriptLanguage::singleton->add_register_func("index/get_index",create_node_generic<VisualScriptIndexGet>); + VisualScriptLanguage::singleton->add_register_func("index/set_index",create_node_generic<VisualScriptIndexSet>); + + + VisualScriptLanguage::singleton->add_register_func("operators/compare/equal",create_op_node<Variant::OP_EQUAL>); + VisualScriptLanguage::singleton->add_register_func("operators/compare/not_equal",create_op_node<Variant::OP_NOT_EQUAL>); + VisualScriptLanguage::singleton->add_register_func("operators/compare/less",create_op_node<Variant::OP_LESS>); + VisualScriptLanguage::singleton->add_register_func("operators/compare/less_equal",create_op_node<Variant::OP_LESS_EQUAL>); + VisualScriptLanguage::singleton->add_register_func("operators/compare/greater",create_op_node<Variant::OP_GREATER>); + VisualScriptLanguage::singleton->add_register_func("operators/compare/greater_equal",create_op_node<Variant::OP_GREATER_EQUAL>); + //mathematic + VisualScriptLanguage::singleton->add_register_func("operators/math/add",create_op_node<Variant::OP_ADD>); + VisualScriptLanguage::singleton->add_register_func("operators/math/subtract",create_op_node<Variant::OP_SUBSTRACT>); + VisualScriptLanguage::singleton->add_register_func("operators/math/multiply",create_op_node<Variant::OP_MULTIPLY>); + VisualScriptLanguage::singleton->add_register_func("operators/math/divide",create_op_node<Variant::OP_DIVIDE>); + VisualScriptLanguage::singleton->add_register_func("operators/math/negate",create_op_node<Variant::OP_NEGATE>); + VisualScriptLanguage::singleton->add_register_func("operators/math/remainder",create_op_node<Variant::OP_MODULE>); + VisualScriptLanguage::singleton->add_register_func("operators/math/string_concat",create_op_node<Variant::OP_STRING_CONCAT>); + //bitwise + VisualScriptLanguage::singleton->add_register_func("operators/bitwise/shift_left",create_op_node<Variant::OP_SHIFT_LEFT>); + VisualScriptLanguage::singleton->add_register_func("operators/bitwise/shift_right",create_op_node<Variant::OP_SHIFT_RIGHT>); + VisualScriptLanguage::singleton->add_register_func("operators/bitwise/bit_and",create_op_node<Variant::OP_BIT_AND>); + VisualScriptLanguage::singleton->add_register_func("operators/bitwise/bit_or",create_op_node<Variant::OP_BIT_OR>); + VisualScriptLanguage::singleton->add_register_func("operators/bitwise/bit_xor",create_op_node<Variant::OP_BIT_XOR>); + VisualScriptLanguage::singleton->add_register_func("operators/bitwise/bit_negate",create_op_node<Variant::OP_BIT_NEGATE>); + //logic + VisualScriptLanguage::singleton->add_register_func("operators/logic/and",create_op_node<Variant::OP_AND>); + VisualScriptLanguage::singleton->add_register_func("operators/logic/or",create_op_node<Variant::OP_OR>); + VisualScriptLanguage::singleton->add_register_func("operators/logic/xor",create_op_node<Variant::OP_XOR>); + VisualScriptLanguage::singleton->add_register_func("operators/logic/not",create_op_node<Variant::OP_NOT>); + VisualScriptLanguage::singleton->add_register_func("operators/logic/in",create_op_node<Variant::OP_IN>); + + VisualScriptLanguage::singleton->add_register_func("functions/deconstruct",create_node_generic<VisualScriptDeconstruct>); + + for(int i=1;i<Variant::VARIANT_MAX;i++) { + + List<MethodInfo> constructors; + Variant::get_constructor_list(Variant::Type(i),&constructors); + + for(List<MethodInfo>::Element *E=constructors.front();E;E=E->next()) { + + if (E->get().arguments.size()>0) { + + + String name = "functions/constructors/"+Variant::get_type_name(Variant::Type(i))+" ( "; + for(int j=0;j<E->get().arguments.size();j++) { + if (j>0) + name+=", "; + if (E->get().arguments.size()==1) + name+=Variant::get_type_name(E->get().arguments[j].type); + else + name+=E->get().arguments[j].name; + } + name+=") "; + + VisualScriptLanguage::singleton->add_register_func(name,create_constructor_node); + Pair<Variant::Type,MethodInfo> pair; + pair.first=Variant::Type(i); + pair.second=E->get(); + constructor_map[name]=pair; + } + } + } +} + + + +void unregister_visual_script_nodes() { + + constructor_map.clear(); +} + diff --git a/modules/visual_script/visual_script_nodes.h b/modules/visual_script/visual_script_nodes.h new file mode 100644 index 0000000000..94eeadca57 --- /dev/null +++ b/modules/visual_script/visual_script_nodes.h @@ -0,0 +1,1068 @@ +#ifndef VISUAL_SCRIPT_NODES_H +#define VISUAL_SCRIPT_NODES_H + +#include "visual_script.h" + +class VisualScriptFunction : public VisualScriptNode { + + OBJ_TYPE(VisualScriptFunction,VisualScriptNode) + + + struct Argument { + String name; + Variant::Type type; + }; + + Vector<Argument> arguments; + + bool stack_less; + int stack_size; + ScriptInstance::RPCMode rpc_mode; + + +protected: + + bool _set(const StringName& p_name, const Variant& p_value); + bool _get(const StringName& p_name,Variant &r_ret) const; + void _get_property_list( List<PropertyInfo> *p_list) const; + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + void add_argument(Variant::Type p_type,const String& p_name,int p_index=-1); + void set_argument_type(int p_argidx,Variant::Type p_type); + Variant::Type get_argument_type(int p_argidx) const; + void set_argument_name(int p_argidx,const String& p_name); + String get_argument_name(int p_argidx) const; + void remove_argument(int p_argidx); + int get_argument_count() const; + + + void set_stack_less(bool p_enable); + bool is_stack_less() const; + + void set_stack_size(int p_size); + int get_stack_size() const; + + void set_rpc_mode(ScriptInstance::RPCMode p_mode); + ScriptInstance::RPCMode get_rpc_mode() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptFunction(); +}; + + +class VisualScriptOperator : public VisualScriptNode { + + OBJ_TYPE(VisualScriptOperator,VisualScriptNode) + + + Variant::Type typed; + Variant::Operator op; +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "operators"; } + + void set_operator(Variant::Operator p_op); + Variant::Operator get_operator() const; + + void set_typed(Variant::Type p_op); + Variant::Type get_typed() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptOperator(); +}; + + +class VisualScriptVariableGet : public VisualScriptNode { + + OBJ_TYPE(VisualScriptVariableGet,VisualScriptNode) + + + StringName variable; +protected: + + virtual void _validate_property(PropertyInfo& property) const; + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + void set_variable(StringName p_var); + StringName get_variable() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptVariableGet(); +}; + + +class VisualScriptVariableSet : public VisualScriptNode { + + OBJ_TYPE(VisualScriptVariableSet,VisualScriptNode) + + + StringName variable; +protected: + + virtual void _validate_property(PropertyInfo& property) const; + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + void set_variable(StringName p_var); + StringName get_variable() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptVariableSet(); +}; + + +class VisualScriptConstant : public VisualScriptNode { + + OBJ_TYPE(VisualScriptConstant,VisualScriptNode) + + + Variant::Type type; + Variant value; +protected: + virtual void _validate_property(PropertyInfo& property) const; + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "constants"; } + + void set_constant_type(Variant::Type p_type); + Variant::Type get_constant_type() const; + + void set_constant_value(Variant p_value); + Variant get_constant_value() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptConstant(); +}; + + + +class VisualScriptPreload : public VisualScriptNode { + + OBJ_TYPE(VisualScriptPreload,VisualScriptNode) + + + Ref<Resource> preload; +protected: + + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + void set_preload(const Ref<Resource>& p_value); + Ref<Resource> get_preload() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptPreload(); +}; + +class VisualScriptIndexGet : public VisualScriptNode { + + OBJ_TYPE(VisualScriptIndexGet,VisualScriptNode) + + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "operators"; } + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptIndexGet(); +}; + + +class VisualScriptIndexSet : public VisualScriptNode { + + OBJ_TYPE(VisualScriptIndexSet,VisualScriptNode) + + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "operators"; } + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptIndexSet(); +}; + + + +class VisualScriptGlobalConstant : public VisualScriptNode { + + OBJ_TYPE(VisualScriptGlobalConstant,VisualScriptNode) + + int index; + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "constants"; } + + void set_global_constant(int p_which); + int get_global_constant(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptGlobalConstant(); +}; + + +class VisualScriptClassConstant : public VisualScriptNode { + + OBJ_TYPE(VisualScriptClassConstant,VisualScriptNode) + + StringName base_type; + StringName name; +protected: + static void _bind_methods(); + virtual void _validate_property(PropertyInfo& property) const; + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "constants"; } + + void set_class_constant(const StringName& p_which); + StringName get_class_constant(); + + void set_base_type(const StringName& p_which); + StringName get_base_type(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptClassConstant(); +}; + +class VisualScriptBasicTypeConstant : public VisualScriptNode { + + OBJ_TYPE(VisualScriptBasicTypeConstant,VisualScriptNode) + + Variant::Type type; + StringName name; +protected: + static void _bind_methods(); + virtual void _validate_property(PropertyInfo& property) const; + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "constants"; } + + void set_basic_type_constant(const StringName& p_which); + StringName get_basic_type_constant() const; + + void set_basic_type(Variant::Type p_which); + Variant::Type get_basic_type() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptBasicTypeConstant(); +}; + + + +class VisualScriptMathConstant : public VisualScriptNode { + + OBJ_TYPE(VisualScriptMathConstant,VisualScriptNode) +public: + + enum MathConstant { + MATH_CONSTANT_ONE, + MATH_CONSTANT_PI, + MATH_CONSTANT_2PI, + MATH_CONSTANT_HALF_PI, + MATH_CONSTANT_E, + MATH_CONSTANT_SQRT2, + MATH_CONSTANT_MAX, + }; + +private: + static const char* const_name[MATH_CONSTANT_MAX]; + static double const_value[MATH_CONSTANT_MAX]; + MathConstant constant; +protected: + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "constants"; } + + void set_math_constant(MathConstant p_which); + MathConstant get_math_constant(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptMathConstant(); +}; + +VARIANT_ENUM_CAST( VisualScriptMathConstant::MathConstant ) + +class VisualScriptEngineSingleton : public VisualScriptNode { + + OBJ_TYPE(VisualScriptEngineSingleton,VisualScriptNode) + + String singleton; + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + void set_singleton(const String &p_string); + String get_singleton(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + + VisualScriptEngineSingleton(); +}; + + + + +class VisualScriptSceneNode : public VisualScriptNode { + + OBJ_TYPE(VisualScriptSceneNode,VisualScriptNode) + + NodePath path; +protected: + virtual void _validate_property(PropertyInfo& property) const; + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + void set_node_path(const NodePath &p_path); + NodePath get_node_path(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + VisualScriptSceneNode(); +}; + + + + +class VisualScriptSceneTree : public VisualScriptNode { + + OBJ_TYPE(VisualScriptSceneTree,VisualScriptNode) + + +protected: + virtual void _validate_property(PropertyInfo& property) const; + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + VisualScriptSceneTree(); +}; + + + +class VisualScriptResourcePath : public VisualScriptNode { + + OBJ_TYPE(VisualScriptResourcePath,VisualScriptNode) + + String path; +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + void set_resource_path(const String &p_path); + String get_resource_path(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptResourcePath(); +}; + + +class VisualScriptSelf : public VisualScriptNode { + + OBJ_TYPE(VisualScriptSelf,VisualScriptNode) + + +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + VisualScriptSelf(); +}; + + +class VisualScriptCustomNode: public VisualScriptNode { + + OBJ_TYPE(VisualScriptCustomNode,VisualScriptNode) + + +protected: + + virtual bool _use_builtin_script() const { return true; } + + static void _bind_methods(); +public: + + enum StartMode { //replicated for step + START_MODE_BEGIN_SEQUENCE, + START_MODE_CONTINUE_SEQUENCE, + START_MODE_RESUME_YIELD + }; + + enum { //replicated for step + STEP_SHIFT=1<<24, + STEP_MASK=STEP_SHIFT-1, + STEP_PUSH_STACK_BIT=STEP_SHIFT, //push bit to stack + STEP_GO_BACK_BIT=STEP_SHIFT<<1, //go back to previous node + STEP_NO_ADVANCE_BIT=STEP_SHIFT<<2, //do not advance past this node + STEP_EXIT_FUNCTION_BIT=STEP_SHIFT<<3, //return from function + STEP_YIELD_BIT=STEP_SHIFT<<4, //yield (will find VisualScriptFunctionState state in first working memory) + }; + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptCustomNode(); +}; + +class VisualScriptSubCall: public VisualScriptNode { + + OBJ_TYPE(VisualScriptSubCall,VisualScriptNode) + + +protected: + + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptSubCall(); +}; + +class VisualScriptComment: public VisualScriptNode { + + OBJ_TYPE(VisualScriptComment,VisualScriptNode) + + + String title; + String description; + Size2 size; +protected: + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + void set_title(const String& p_title); + String get_title() const; + + void set_description(const String& p_description); + String get_description() const; + + void set_size(const Size2& p_size); + Size2 get_size() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptComment(); +}; + +class VisualScriptConstructor: public VisualScriptNode { + + OBJ_TYPE(VisualScriptConstructor,VisualScriptNode) + + + Variant::Type type; + MethodInfo constructor; + +protected: + + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + void set_constructor_type(Variant::Type p_type); + Variant::Type get_constructor_type() const; + + void set_constructor(const Dictionary& p_info); + Dictionary get_constructor() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptConstructor(); +}; + + + + +class VisualScriptLocalVar: public VisualScriptNode { + + OBJ_TYPE(VisualScriptLocalVar,VisualScriptNode) + + StringName name; + Variant::Type type; + +protected: + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + void set_var_name(const StringName& p_name); + StringName get_var_name() const; + + void set_var_type(Variant::Type p_type); + Variant::Type get_var_type() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptLocalVar(); +}; + +class VisualScriptLocalVarSet: public VisualScriptNode { + + OBJ_TYPE(VisualScriptLocalVarSet,VisualScriptNode) + + StringName name; + Variant::Type type; + +protected: + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + void set_var_name(const StringName& p_name); + StringName get_var_name() const; + + void set_var_type(Variant::Type p_type); + Variant::Type get_var_type() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptLocalVarSet(); +}; + + + +class VisualScriptInputAction: public VisualScriptNode { + + OBJ_TYPE(VisualScriptInputAction,VisualScriptNode) +public: + enum Mode { + MODE_PRESSED, + MODE_RELEASED, + MODE_JUST_PRESSED, + MODE_JUST_RELEASED, + }; + + StringName name; + Mode mode; + +protected: + + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + void set_action_name(const StringName& p_name); + StringName get_action_name() const; + + void set_action_mode(Mode p_mode); + Mode get_action_mode() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptInputAction(); +}; + +VARIANT_ENUM_CAST( VisualScriptInputAction::Mode ) + +class VisualScriptDeconstruct: public VisualScriptNode { + + OBJ_TYPE(VisualScriptDeconstruct,VisualScriptNode) + + + struct Element { + StringName name; + Variant::Type type; + }; + + + Vector<Element> elements; + + void _update_elements(); + Variant::Type type; + InputEvent::Type input_type; + + void _set_elem_cache(const Array& p_elements); + Array _get_elem_cache() const; + + virtual void _validate_property(PropertyInfo& property) const; + +protected: + + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + void set_deconstruct_type(Variant::Type p_type); + Variant::Type get_deconstruct_type() const; + + void set_deconstruct_input_type(InputEvent::Type p_input_type); + InputEvent::Type get_deconstruct_input_type() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptDeconstruct(); +}; + + +void register_visual_script_nodes(); +void unregister_visual_script_nodes(); + +#endif // VISUAL_SCRIPT_NODES_H diff --git a/modules/visual_script/visual_script_yield_nodes.cpp b/modules/visual_script/visual_script_yield_nodes.cpp new file mode 100644 index 0000000000..221c46b6fd --- /dev/null +++ b/modules/visual_script/visual_script_yield_nodes.cpp @@ -0,0 +1,625 @@ +#include "visual_script_yield_nodes.h" +#include "scene/main/scene_main_loop.h" +#include "os/os.h" +#include "scene/main/node.h" +#include "visual_script_nodes.h" + +////////////////////////////////////////// +////////////////YIELD/////////// +////////////////////////////////////////// + +int VisualScriptYield::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptYield::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptYield::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptYield::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptYield::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptYield::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptYield::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptYield::get_caption() const { + + return yield_mode==YIELD_RETURN?"Yield":"Wait"; +} + +String VisualScriptYield::get_text() const { + + switch (yield_mode) { + case YIELD_RETURN: return ""; break; + case YIELD_FRAME: return "Next Frame"; break; + case YIELD_FIXED_FRAME: return "Next Fixed Frame"; break; + case YIELD_WAIT: return rtos(wait_time)+" sec(s)"; break; + } + + return String(); +} + + +class VisualScriptNodeInstanceYield : public VisualScriptNodeInstance { +public: + + VisualScriptYield::YieldMode mode; + float wait_time; + + virtual int get_working_memory_size() const { return 1; } //yield needs at least 1 + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return false; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_start_mode==START_MODE_RESUME_YIELD) { + return 0; //resuming yield + } else { + //yield + + + SceneTree *tree = OS::get_singleton()->get_main_loop()->cast_to<SceneTree>(); + if (!tree) { + r_error_str="Main Loop is not SceneTree"; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } + + Ref<VisualScriptFunctionState> state; + state.instance(); + + int ret = STEP_YIELD_BIT; + switch(mode) { + + case VisualScriptYield::YIELD_RETURN: ret=STEP_EXIT_FUNCTION_BIT; break; //return the yield + case VisualScriptYield::YIELD_FRAME: state->connect_to_signal(tree,"idle_frame",Array()); break; + case VisualScriptYield::YIELD_FIXED_FRAME: state->connect_to_signal(tree,"fixed_frame",Array()); break; + case VisualScriptYield::YIELD_WAIT: state->connect_to_signal(tree->create_timer(wait_time).ptr(),"timeout",Array()); break; + + } + + *p_working_mem=state; + + return ret; + } + } + +}; + +VisualScriptNodeInstance* VisualScriptYield::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceYield * instance = memnew(VisualScriptNodeInstanceYield ); + //instance->instance=p_instance; + instance->mode=yield_mode; + instance->wait_time=wait_time; + return instance; +} + +void VisualScriptYield::set_yield_mode(YieldMode p_mode) { + + if (yield_mode==p_mode) + return; + yield_mode=p_mode; + ports_changed_notify(); + _change_notify(); +} + +VisualScriptYield::YieldMode VisualScriptYield::get_yield_mode(){ + + return yield_mode; +} + +void VisualScriptYield::set_wait_time(float p_time) { + + if (wait_time==p_time) + return; + wait_time=p_time; + ports_changed_notify(); + +} + +float VisualScriptYield::get_wait_time(){ + + return wait_time; +} + + +void VisualScriptYield::_validate_property(PropertyInfo& property) const { + + + if (property.name=="wait_time") { + if (yield_mode!=YIELD_WAIT) { + property.usage=0; + } + } +} + +void VisualScriptYield::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_yield_mode","mode"),&VisualScriptYield::set_yield_mode); + ObjectTypeDB::bind_method(_MD("get_yield_mode"),&VisualScriptYield::get_yield_mode); + + ObjectTypeDB::bind_method(_MD("set_wait_time","sec"),&VisualScriptYield::set_wait_time); + ObjectTypeDB::bind_method(_MD("get_wait_time"),&VisualScriptYield::get_wait_time); + + ADD_PROPERTY(PropertyInfo(Variant::INT,"mode",PROPERTY_HINT_ENUM,"Frame,FixedFrame,Time",PROPERTY_USAGE_NOEDITOR),_SCS("set_yield_mode"),_SCS("get_yield_mode")); + ADD_PROPERTY(PropertyInfo(Variant::REAL,"wait_time"),_SCS("set_wait_time"),_SCS("get_wait_time")); + + + BIND_CONSTANT( YIELD_FRAME ); + BIND_CONSTANT( YIELD_FIXED_FRAME ); + BIND_CONSTANT( YIELD_WAIT ); + +} + +VisualScriptYield::VisualScriptYield() { + + yield_mode=YIELD_FRAME; + wait_time=1; + +} + + +template<VisualScriptYield::YieldMode MODE> +static Ref<VisualScriptNode> create_yield_node(const String& p_name) { + + Ref<VisualScriptYield> node; + node.instance(); + node->set_yield_mode(MODE); + return node; +} + +/////////////////////////////////////////////////// +////////////////YIELD SIGNAL////////////////////// +////////////////////////////////////////////////// + +int VisualScriptYieldSignal::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptYieldSignal::has_input_sequence_port() const{ + + return true; +} +#ifdef TOOLS_ENABLED + +static Node* _find_script_node(Node* p_edited_scene,Node* p_current_node,const Ref<Script> &script) { + + if (p_edited_scene!=p_current_node && p_current_node->get_owner()!=p_edited_scene) + return NULL; + + Ref<Script> scr = p_current_node->get_script(); + + if (scr.is_valid() && scr==script) + return p_current_node; + + for(int i=0;i<p_current_node->get_child_count();i++) { + Node *n = _find_script_node(p_edited_scene,p_current_node->get_child(i),script); + if (n) + return n; + } + + return NULL; +} + +#endif +Node *VisualScriptYieldSignal::_get_base_node() const { + +#ifdef TOOLS_ENABLED + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return NULL; + + MainLoop * main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) + return NULL; + + SceneTree *scene_tree = main_loop->cast_to<SceneTree>(); + + if (!scene_tree) + return NULL; + + Node *edited_scene = scene_tree->get_edited_scene_root(); + + if (!edited_scene) + return NULL; + + Node* script_node = _find_script_node(edited_scene,edited_scene,script); + + if (!script_node) + return NULL; + + if (!script_node->has_node(base_path)) + return NULL; + + Node *path_to = script_node->get_node(base_path); + + return path_to; +#else + + return NULL; +#endif +} + +StringName VisualScriptYieldSignal::_get_base_type() const { + + if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) + return get_visual_script()->get_instance_base_type(); + else if (call_mode==CALL_MODE_NODE_PATH && get_visual_script().is_valid()) { + Node *path = _get_base_node(); + if (path) + return path->get_type(); + + } + + return base_type; +} + +int VisualScriptYieldSignal::get_input_value_port_count() const{ + + if (call_mode==CALL_MODE_INSTANCE) + return 1; + else + return 0; + +} +int VisualScriptYieldSignal::get_output_value_port_count() const{ + + + MethodInfo sr; + + if (!ObjectTypeDB::get_signal(_get_base_type(),signal,&sr)) + return 0; + + return sr.arguments.size(); + +} + +String VisualScriptYieldSignal::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptYieldSignal::get_input_value_port_info(int p_idx) const{ + + if (call_mode==CALL_MODE_INSTANCE) + return PropertyInfo(Variant::OBJECT,"instance"); + else + return PropertyInfo(); + +} + +PropertyInfo VisualScriptYieldSignal::get_output_value_port_info(int p_idx) const{ + + MethodInfo sr; + + if (!ObjectTypeDB::get_signal(_get_base_type(),signal,&sr)) + return PropertyInfo(); //no signal + ERR_FAIL_INDEX_V(p_idx,sr.arguments.size(),PropertyInfo()); + return sr.arguments[p_idx]; + +} + + +String VisualScriptYieldSignal::get_caption() const { + + static const char*cname[3]= { + "WaitSignal", + "WaitNodeSignal", + "WaitInstanceSigna;", + }; + + return cname[call_mode]; +} + +String VisualScriptYieldSignal::get_text() const { + + if (call_mode==CALL_MODE_SELF) + return " "+String(signal)+"()"; + else + return " "+_get_base_type()+"."+String(signal)+"()"; + +} + + +void VisualScriptYieldSignal::set_base_type(const StringName& p_type) { + + if (base_type==p_type) + return; + + base_type=p_type; + + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptYieldSignal::get_base_type() const{ + + return base_type; +} + +void VisualScriptYieldSignal::set_signal(const StringName& p_type){ + + if (signal==p_type) + return; + + signal=p_type; + + _change_notify(); + ports_changed_notify(); +} +StringName VisualScriptYieldSignal::get_signal() const { + + + return signal; +} + +void VisualScriptYieldSignal::set_base_path(const NodePath& p_type) { + + if (base_path==p_type) + return; + + base_path=p_type; + + _change_notify(); + ports_changed_notify(); +} + +NodePath VisualScriptYieldSignal::get_base_path() const { + + return base_path; +} + + +void VisualScriptYieldSignal::set_call_mode(CallMode p_mode) { + + if (call_mode==p_mode) + return; + + call_mode=p_mode; + + _change_notify(); + ports_changed_notify(); + +} + +VisualScriptYieldSignal::CallMode VisualScriptYieldSignal::get_call_mode() const { + + return call_mode; +} + + +void VisualScriptYieldSignal::_validate_property(PropertyInfo& property) const { + + if (property.name=="signal/base_type") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=PROPERTY_USAGE_NOEDITOR; + } + } + + + if (property.name=="signal/node_path") { + if (call_mode!=CALL_MODE_NODE_PATH) { + property.usage=0; + } else { + + Node *bnode = _get_base_node(); + if (bnode) { + property.hint_string=bnode->get_path(); //convert to loong string + } else { + + } + } + } + + if (property.name=="signal/signal") { + property.hint=PROPERTY_HINT_ENUM; + + + List<MethodInfo> methods; + + ObjectTypeDB::get_signal_list(_get_base_type(),&methods); + + List<String> mstring; + for (List<MethodInfo>::Element *E=methods.front();E;E=E->next()) { + if (E->get().name.begins_with("_")) + continue; + mstring.push_back(E->get().name.get_slice(":",0)); + } + + mstring.sort(); + + String ml; + for (List<String>::Element *E=mstring.front();E;E=E->next()) { + + if (ml!=String()) + ml+=","; + ml+=E->get(); + } + + property.hint_string=ml; + } + + +} + + +void VisualScriptYieldSignal::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_base_type","base_type"),&VisualScriptYieldSignal::set_base_type); + ObjectTypeDB::bind_method(_MD("get_base_type"),&VisualScriptYieldSignal::get_base_type); + + ObjectTypeDB::bind_method(_MD("set_signal","signal"),&VisualScriptYieldSignal::set_signal); + ObjectTypeDB::bind_method(_MD("get_signal"),&VisualScriptYieldSignal::get_signal); + + ObjectTypeDB::bind_method(_MD("set_call_mode","mode"),&VisualScriptYieldSignal::set_call_mode); + ObjectTypeDB::bind_method(_MD("get_call_mode"),&VisualScriptYieldSignal::get_call_mode); + + ObjectTypeDB::bind_method(_MD("set_base_path","base_path"),&VisualScriptYieldSignal::set_base_path); + ObjectTypeDB::bind_method(_MD("get_base_path"),&VisualScriptYieldSignal::get_base_path); + + + + String bt; + for(int i=0;i<Variant::VARIANT_MAX;i++) { + if (i>0) + bt+=","; + + bt+=Variant::get_type_name(Variant::Type(i)); + } + + ADD_PROPERTY(PropertyInfo(Variant::INT,"signal/call_mode",PROPERTY_HINT_ENUM,"Self,Node Path,Instance"),_SCS("set_call_mode"),_SCS("get_call_mode")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"signal/base_type",PROPERTY_HINT_TYPE_STRING,"Object"),_SCS("set_base_type"),_SCS("get_base_type")); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH,"signal/node_path",PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE),_SCS("set_base_path"),_SCS("get_base_path")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"signal/signal"),_SCS("set_signal"),_SCS("get_signal")); + + + BIND_CONSTANT( CALL_MODE_SELF ); + BIND_CONSTANT( CALL_MODE_NODE_PATH); + BIND_CONSTANT( CALL_MODE_INSTANCE); + +} + +class VisualScriptNodeInstanceYieldSignal : public VisualScriptNodeInstance { +public: + + + VisualScriptYieldSignal::CallMode call_mode; + NodePath node_path; + int output_args; + StringName signal; + + VisualScriptYieldSignal *node; + VisualScriptInstance *instance; + + + + virtual int get_working_memory_size() const { return 1; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_start_mode==START_MODE_RESUME_YIELD) { + return 0; //resuming yield + } else { + //yield + + Object * object; + + switch(call_mode) { + + case VisualScriptYieldSignal::CALL_MODE_SELF: { + + object=instance->get_owner_ptr(); + + } break; + case VisualScriptYieldSignal::CALL_MODE_NODE_PATH: { + + Node* node = instance->get_owner_ptr()->cast_to<Node>(); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Base object is not a Node!"; + return 0; + } + + Node* another = node->get_node(node_path); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Path does not lead Node!"; + return 0; + } + + object=another; + + } break; + case VisualScriptYieldSignal::CALL_MODE_INSTANCE: { + + object = *p_inputs[0]; + if (!object) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Supplied instance input is null."; + return 0; + + } + + } break; + + } + + Ref<VisualScriptFunctionState> state; + state.instance(); + + state->connect_to_signal(object,signal,Array()); + + *p_working_mem=state; + + return STEP_YIELD_BIT; + } + + + } + + +}; + +VisualScriptNodeInstance* VisualScriptYieldSignal::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceYieldSignal * instance = memnew(VisualScriptNodeInstanceYieldSignal ); + instance->node=this; + instance->instance=p_instance; + instance->signal=signal; + instance->call_mode=call_mode; + instance->node_path=base_path; + instance->output_args = get_output_value_port_count(); + return instance; +} +VisualScriptYieldSignal::VisualScriptYieldSignal() { + + call_mode=CALL_MODE_SELF; + base_type="Object"; + +} + +template<VisualScriptYieldSignal::CallMode cmode> +static Ref<VisualScriptNode> create_yield_signal_node(const String& p_name) { + + Ref<VisualScriptYieldSignal> node; + node.instance(); + node->set_call_mode(cmode); + return node; +} + +void register_visual_script_yield_nodes() { + + VisualScriptLanguage::singleton->add_register_func("functions/wait/wait_frame",create_yield_node<VisualScriptYield::YIELD_FRAME>); + VisualScriptLanguage::singleton->add_register_func("functions/wait/wait_fixed_frame",create_yield_node<VisualScriptYield::YIELD_FIXED_FRAME>); + VisualScriptLanguage::singleton->add_register_func("functions/wait/wait_time",create_yield_node<VisualScriptYield::YIELD_WAIT>); + + + VisualScriptLanguage::singleton->add_register_func("functions/yield",create_yield_node<VisualScriptYield::YIELD_RETURN>); + VisualScriptLanguage::singleton->add_register_func("functions/yield_signal",create_node_generic<VisualScriptYieldSignal>); + +} diff --git a/modules/visual_script/visual_script_yield_nodes.h b/modules/visual_script/visual_script_yield_nodes.h new file mode 100644 index 0000000000..ae7f8c15c1 --- /dev/null +++ b/modules/visual_script/visual_script_yield_nodes.h @@ -0,0 +1,128 @@ +#ifndef VISUAL_SCRIPT_YIELD_NODES_H +#define VISUAL_SCRIPT_YIELD_NODES_H + +#include "visual_script.h" + +class VisualScriptYield : public VisualScriptNode { + + OBJ_TYPE(VisualScriptYield,VisualScriptNode) +public: + + enum YieldMode { + YIELD_RETURN, + YIELD_FRAME, + YIELD_FIXED_FRAME, + YIELD_WAIT + + }; +private: + + YieldMode yield_mode; + float wait_time; + + +protected: + + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_yield_mode(YieldMode p_mode); + YieldMode get_yield_mode(); + + void set_wait_time(float p_time); + float get_wait_time(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptYield(); +}; +VARIANT_ENUM_CAST( VisualScriptYield::YieldMode ) + +class VisualScriptYieldSignal : public VisualScriptNode { + + OBJ_TYPE(VisualScriptYieldSignal,VisualScriptNode) +public: + enum CallMode { + CALL_MODE_SELF, + CALL_MODE_NODE_PATH, + CALL_MODE_INSTANCE, + + }; +private: + + CallMode call_mode; + StringName base_type; + NodePath base_path; + StringName signal; + + Node *_get_base_node() const; + StringName _get_base_type() const; + + +protected: + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_base_type(const StringName& p_type); + StringName get_base_type() const; + + void set_signal(const StringName& p_type); + StringName get_signal() const; + + void set_base_path(const NodePath& p_type); + NodePath get_base_path() const; + + void set_call_mode(CallMode p_mode); + CallMode get_call_mode() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptYieldSignal(); +}; + +VARIANT_ENUM_CAST(VisualScriptYieldSignal::CallMode ); + +void register_visual_script_yield_nodes(); + +#endif // VISUAL_SCRIPT_YIELD_NODES_H |