Skip to content

Instantly share code, notes, and snippets.

@reduz
Last active February 6, 2024 23:55
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save reduz/9635c731f0592d7e526367c6063b8f8f to your computer and use it in GitHub Desktop.
Save reduz/9635c731f0592d7e526367c6063b8f8f to your computer and use it in GitHub Desktop.
Godot source code in 2002

This is a walkthrough to the Godot codebase over time. I can't publish full repositories, but I will put here the interfaces of some APIs including the year they were created and how they looked like at the time.

2002, Larvotor

This was the first version of the engine as such. It was later on used to create Regnum Oline. This is source code where I am sure I can still put legally here. Here are some class interfaces.

Mesh class:

// mesh.h
class Mesh { SCRIPT_EXPORT

	vector<Material*>   materials;

	Mesh *parent; //in case that this was inherited, parent will be the parent mesh, otherwise NULL

	void copy_from(const Mesh &p_src,bool p_reference);
	Skeleton skeleton; // If unused, it just has no bones ( get_bone_count() == 0 )

public:

	const Mesh& operator=(const Mesh &p_src);

	SCRIPT_BIND_BEGIN

	Material* add_material();
	PolyArray *add_material_with_polyarray();
	int get_material_count() const;
	Material* get_material(int p_idx) const;

	Skeleton& get_skeleton();
	void process_skeleton();

	void clear();

	Mesh(const Mesh &p_src,bool p_reference=false);
	Mesh();
	virtual ~Mesh();
};
SCRIPT_BIND_END

You may wonder, what is SCRIPT_BIND_BEGIN and SCRIPT_BIND_END? This is from Tolua++, Ariel Manzur's automatic lua binder. It was integrated to SCons (yes we used it this far back), to generate binder code automatically.

As you can see, the resource system did not exist. mesh was just that. STL was used for templates. PolyArray contains the mesh data. PolyArray is what Godot now considers a Surface.

// poly_array.h

class Material; ///< Needed for declaring friend, so only a material can instance this. Maybe the ideal way to do it is subclassing, but this way it is more readable.

SCRIPT_BIND_BEGIN;

class PolyArray {
public:

	enum GeometryFormat {

		POINTS,
		LINES,
		LINE_STRIP,
		LINE_LOOP,
		TRIANGLES,
		TRIANGLE_STRIP,
		TRIANGLE_FAN,
		QUADS,
		QUAD_STRIP
	};

SCRIPT_BIND_END; // the typedefs are on the pkg

        /* IMPORTANT NOTICE - USE THESE INSTEAD OF DEFINING YOUR OWN AND COPYING - MAKES LATER CHANGES EASIER */

	typedef vector< Vector3Df,AlignedAllocator<Vector3Df> > VertexArray;
	typedef vector< Uint16,AlignedAllocator<Uint16> > IndexArray;
	typedef vector< Vector3Df,AlignedAllocator<Vector3Df> > NormalArray;
	typedef vector< RGBA_Color,AlignedAllocator<RGBA_Color> > ColorArray;
	typedef vector< TexCoord2D,AlignedAllocator<TexCoord2D> > TexCoordArray;
	typedef vector< TexCoordArray > TexCoordList;

SCRIPT_BIND_BEGIN;

	struct WeightsPerVertex {

		enum {
			MAX_WEIGHTS=3
		};

		char amount;
		Skeleton::BoneWeight weights[MAX_WEIGHTS];

		WeightsPerVertex() { amount = 0; };
	};
SCRIPT_BIND_END;

	typedef vector< WeightsPerVertex > BoneWeightArray;

private:

	struct SharedArrays { ///< Algorithmically, if one of these is assigned (!NULL), the respective local one is ignored

		const VertexArray *vertex_array;
		const IndexArray *index_array;
		const NormalArray *normal_array;
		const ColorArray *color_array;

		const TexCoordList *texcoord_list;
		const BoneWeightArray *bone_weight_array;

	} shared_arrays;

	struct LocalArrays { ///< Every mesh has local data, if any of them is unused, will remain empty

		VertexArray vertex_array;
		IndexArray index_array;
		NormalArray normal_array;
		ColorArray color_array;

		TexCoordList texcoord_list;
		BoneWeightArray bone_weight_array;

		const VertexArray *base_vertex_array;  ///<used for bones and other modifiers that take parent data and transform to local
		const NormalArray *base_normal_array; ///<used for bones and other modifiers that take parent data and transform to local

	} local_arrays;

	struct Geometry {

		float point_size;
		float line_width;
		GeometryFormat geometry_format;
		bool global_color_enabled; ///< NOTE: Alpha blending here or in the vertex colors needs to be enabled at Material level
		RGBA_Color global_color; ///< The global color is ignored if per vertex colors exist
		bool uses_shared_arrays; ///< if it is not part of a parent mesn, this will go true!
	} geometry;

	struct Cache { ///< This is an internal cache
		bool has_alpha_needs_recomputing;
		bool has_alpha;
	} cache;

	const TexCoordList& get_texcoord_list() const; //for internal use

public:

SCRIPT_BIND_BEGIN;

/* RETRIEVING THE ARRAYS - READ ONLY */

	const VertexArray& get_vertex_array() const;
	const IndexArray& get_index_array() const;
	const NormalArray& get_normal_array()  const;
	const ColorArray& get_color_array()  const;
	const TexCoordArray* get_texcoord_array(int p_idx)  const;
	const BoneWeightArray& get_bone_weight_array()  const;

	const VertexArray& get_base_vertex_array() const;
	const NormalArray& get_base_normal_array() const;

/* RETRIEVING THE ARRAYS - READ WRITE (voids cache if exists) */
	///< Depending on what was cached, using these methods will force a recache of wathever was modified. If you only want to read the data, it is BEST to use the ones above instead.
	VertexArray& get_vertex_array_writable();
	IndexArray& get_index_array_writable();
	NormalArray& get_normal_array_writable();
	ColorArray& get_color_array_writable();
	TexCoordArray* get_texcoord_array_writable(int p_idx);
	BoneWeightArray& get_bone_weight_array_writable();

	int get_texcoord_array_count() const;
	void add_texcoord_array();

/* CONFIGURING THE GEOMETRY */

	//get
	float get_point_size() const;
	float get_line_width() const;
	GeometryFormat get_geometry_format() const;
	bool is_global_color_enabled() const; ///< NOTE: Alpha blending here or in the vertex colors needs to be enabled at Material level
	const RGBA_Color& get_global_color() const; ///< The global color is ignored if per vertex colors exist

	//set
	void set_point_size(float p_point_size);
	void set_line_width(float p_line_width);
	void set_geometry_format(GeometryFormat p_geometry_format);
	void set_global_color_enabled(bool p_global_color_enabled); ///< NOTE: Alpha blending here or in the vertex colors needs to be enabled at Material level
	void set_global_color(const RGBA_Color& p_global_color); ///< The global color is ignored if per vertex colors exist

	bool has_alpha() const; ///<checks wether there is somethign needing alpha blend in here

SCRIPT_BIND_END;

protected: ///< This way, the programmer cant instance it or destroy it accidentally!
friend class Material;

	SigC::Signal1<void,PolyArray*> changed; ///<data modified, needs recache. This is not in use, and whenever gets implemented, will probably change!

	void copy_from(const PolyArray &p_polyarray, bool p_reference=false);
	PolyArray(const PolyArray& r_value, bool p_reference=false);
	const PolyArray& operator=(const PolyArray& r_value);
	PolyArray();
	~PolyArray();

}; SCRIPT_EXPORT;

};

Here you can appreciate the very OpenGL 1.1 code. All in host memory, GPUs only had RAM for framebufers and textures, but arrays were introduced later on.

Following is the Skeleton class, for animated skeletons: Again you can see some similarity to Skeleton node, it kinda works the same.

//skeleton.h
class Skeleton;

class Bone { SCRIPT_EXPORT


	string name; ///< Not only used for debug, but for matching with animation channels
	struct Matrix {
		Matrix4D initial; ///< Initial matrix for this bone - position/rotation (since there is no bone scaling (even if should work, I wonder how useful it can be :)
		Matrix4D initial_global_inverse;
		Matrix4D custom; ///< Either user or animation player will change this one
		Matrix4D final; ///< on skeleton process() pass, these are computed
		Matrix4D vertex_transform; ///< used to transform the vertex
	} matrix;

	int parent_bone_index;

protected:
friend class Skeleton;

	Bone(const Matrix4D& p_initial_matrix,int p_parent_bone_idx,string p_name, Matrix4D& p_global_inverse);
	void process_final_matrix(const Matrix4D &p_parent_bone_matrix);
	Bone();
	~Bone();
public:

SCRIPT_BIND_BEGIN

	enum {
		HAS_NO_PARENT_INDEX=-1
	};

	const Matrix4D &get_initial_matrix() const;


	Matrix4D& get_custom_matrix();
	void set_custom_matrix(const Matrix4D& p_matrix);

	int get_parent_bone_index() const;

	const Matrix4D& get_final_matrix() const;

	const Matrix4D& get_vertex_transformation_matrix() const;

	string get_name() const;

};

class Skeleton {
public:

	struct BoneWeight {

		int bone_idx; /* Index to the skeleton bone */
		float weight; /* Weight to this bone, summing the weight to all bones must result in 1 */
		//Vector3Df source; /* Bone initial position */
		BoneWeight() {};
		~BoneWeight() {};
	};
SCRIPT_BIND_END

private:


	vector<Bone*> bone_list;
	vector<float> fast_transform_array;

public:

	const Skeleton& operator=(const Skeleton& p_skeleton);
	const float * get_fast_transform_array();

SCRIPT_BIND_BEGIN

	void clear();

	void add_bone(const Matrix4D&p_initial_matrix,int p_parent_bone_idx,string p_name);
	int get_bone_count() const;
	Bone * get_bone(int p_idx) const;

	void process();


	Skeleton();
	~Skeleton();

};
SCRIPT_BIND_END
class Skeleton;

class Bone { SCRIPT_EXPORT


	string name; ///< Not only used for debug, but for matching with animation channels
	struct Matrix {
		Matrix4D initial; ///< Initial matrix for this bone - position/rotation (since there is no bone scaling (even if should work, I wonder how useful it can be :)
		Matrix4D initial_global_inverse;
		Matrix4D custom; ///< Either user or animation player will change this one
		Matrix4D final; ///< on skeleton process() pass, these are computed
		Matrix4D vertex_transform; ///< used to transform the vertex
	} matrix;

	int parent_bone_index;

protected:
friend class Skeleton;

	Bone(const Matrix4D& p_initial_matrix,int p_parent_bone_idx,string p_name, Matrix4D& p_global_inverse);
	void process_final_matrix(const Matrix4D &p_parent_bone_matrix);
	Bone();
	~Bone();
public:

SCRIPT_BIND_BEGIN

	enum {
		HAS_NO_PARENT_INDEX=-1
	};

	const Matrix4D &get_initial_matrix() const;


	Matrix4D& get_custom_matrix();
	void set_custom_matrix(const Matrix4D& p_matrix);

	int get_parent_bone_index() const;

	const Matrix4D& get_final_matrix() const;

	const Matrix4D& get_vertex_transformation_matrix() const;

	string get_name() const;

};

class Skeleton {
public:

	struct BoneWeight {

		int bone_idx; /* Index to the skeleton bone */
		float weight; /* Weight to this bone, summing the weight to all bones must result in 1 */
		//Vector3Df source; /* Bone initial position */
		BoneWeight() {};
		~BoneWeight() {};
	};
SCRIPT_BIND_END

private:


	vector<Bone*> bone_list;
	vector<float> fast_transform_array;

public:

	const Skeleton& operator=(const Skeleton& p_skeleton);
	const float * get_fast_transform_array();

SCRIPT_BIND_BEGIN

	void clear();

	void add_bone(const Matrix4D&p_initial_matrix,int p_parent_bone_idx,string p_name);
	int get_bone_count() const;
	Bone * get_bone(int p_idx) const;

	void process();


	Skeleton();
	~Skeleton();

};
SCRIPT_BIND_END
class Skeleton;

class Bone { SCRIPT_EXPORT


	string name; ///< Not only used for debug, but for matching with animation channels
	struct Matrix {
		Matrix4D initial; ///< Initial matrix for this bone - position/rotation (since there is no bone scaling (even if should work, I wonder how useful it can be :)
		Matrix4D initial_global_inverse;
		Matrix4D custom; ///< Either user or animation player will change this one
		Matrix4D final; ///< on skeleton process() pass, these are computed
		Matrix4D vertex_transform; ///< used to transform the vertex
	} matrix;

	int parent_bone_index;

protected:
friend class Skeleton;

	Bone(const Matrix4D& p_initial_matrix,int p_parent_bone_idx,string p_name, Matrix4D& p_global_inverse);
	void process_final_matrix(const Matrix4D &p_parent_bone_matrix);
	Bone();
	~Bone();
public:

SCRIPT_BIND_BEGIN

	enum {
		HAS_NO_PARENT_INDEX=-1
	};

	const Matrix4D &get_initial_matrix() const;


	Matrix4D& get_custom_matrix();
	void set_custom_matrix(const Matrix4D& p_matrix);

	int get_parent_bone_index() const;

	const Matrix4D& get_final_matrix() const;

	const Matrix4D& get_vertex_transformation_matrix() const;

	string get_name() const;

};

class Skeleton {
public:

	struct BoneWeight {

		int bone_idx; /* Index to the skeleton bone */
		float weight; /* Weight to this bone, summing the weight to all bones must result in 1 */
		//Vector3Df source; /* Bone initial position */
		BoneWeight() {};
		~BoneWeight() {};
	};
SCRIPT_BIND_END

private:


	vector<Bone*> bone_list;
	vector<float> fast_transform_array;

public:

	const Skeleton& operator=(const Skeleton& p_skeleton);
	const float * get_fast_transform_array();

SCRIPT_BIND_BEGIN

	void clear();

	void add_bone(const Matrix4D&p_initial_matrix,int p_parent_bone_idx,string p_name);
	int get_bone_count() const;
	Bone * get_bone(int p_idx) const;

	void process();


	Skeleton();
	~Skeleton();

};
SCRIPT_BIND_END
class Skeleton;

class Bone { SCRIPT_EXPORT


	string name; ///< Not only used for debug, but for matching with animation channels
	struct Matrix {
		Matrix4D initial; ///< Initial matrix for this bone - position/rotation (since there is no bone scaling (even if should work, I wonder how useful it can be :)
		Matrix4D initial_global_inverse;
		Matrix4D custom; ///< Either user or animation player will change this one
		Matrix4D final; ///< on skeleton process() pass, these are computed
		Matrix4D vertex_transform; ///< used to transform the vertex
	} matrix;

	int parent_bone_index;

protected:
friend class Skeleton;

	Bone(const Matrix4D& p_initial_matrix,int p_parent_bone_idx,string p_name, Matrix4D& p_global_inverse);
	void process_final_matrix(const Matrix4D &p_parent_bone_matrix);
	Bone();
	~Bone();
public:

SCRIPT_BIND_BEGIN

	enum {
		HAS_NO_PARENT_INDEX=-1
	};

	const Matrix4D &get_initial_matrix() const;


	Matrix4D& get_custom_matrix();
	void set_custom_matrix(const Matrix4D& p_matrix);

	int get_parent_bone_index() const;

	const Matrix4D& get_final_matrix() const;

	const Matrix4D& get_vertex_transformation_matrix() const;

	string get_name() const;

};

class Skeleton {
public:

	struct BoneWeight {

		int bone_idx; /* Index to the skeleton bone */
		float weight; /* Weight to this bone, summing the weight to all bones must result in 1 */
		//Vector3Df source; /* Bone initial position */
		BoneWeight() {};
		~BoneWeight() {};
	};
SCRIPT_BIND_END

private:


	vector<Bone*> bone_list;
	vector<float> fast_transform_array;

public:

	const Skeleton& operator=(const Skeleton& p_skeleton);
	const float * get_fast_transform_array();

SCRIPT_BIND_BEGIN

	void clear();

	void add_bone(const Matrix4D&p_initial_matrix,int p_parent_bone_idx,string p_name);
	int get_bone_count() const;
	Bone * get_bone(int p_idx) const;

	void process();


	Skeleton();
	~Skeleton();

};
SCRIPT_BIND_END

On to the more core part of the engine, here it is DynamicDataStruct, which is kind of a Variant precursor. Its aim was the same, serialization and communication. For those that say that Godot is designed around GDScript, this kind of philosophy predated the language by a decade.

// dynamic_data_struct.h
SCRIPT_BIND_BEGIN
typedef vector<int> vector_int;
typedef vector<string> vector_string;
typedef vector<float> vector_float;
typedef VectorPointer<DDS> vector_dds;
typedef int data_size;

/**
 * @class DDS
 * @brief Dynamic data struct, for storing variables and other miscelaneous things!
 *
 * Responsibilities:
 *
 * Collaborations:
 *
 * Interface:
 *
 *
 */

class DDS {
public:


	enum Data_Types {

		T_INT,
		T_FLOAT,
		T_STRING,
		T_POINTER,
		T_DATA,
		T_INT_ARRAY,
		T_STRING_ARRAY,
		T_FLOAT_ARRAY,
		T_DDS,
		T_DDS_ARRAY,
		T_MAX
	};
SCRIPT_BIND_END
protected:

	class Data_Node {

	public:
		Data_Types type;

		union {

			int int_data;
			void * data_ptr;
			float float_data;
			DDS* dynamic_data_struct;

		};

		vector<string> string_array;
		vector<int> int_array;
		vector<float> float_array;

		VectorPointer<DDS> dds_array;


		//not in union.
		string string_data;
		int raw_data_size;

		const Data_Node& operator=(const Data_Node &rvalue);

		void copy_from(const DDS::Data_Node *rvalue);

		// copy constructor
		Data_Node(const Data_Node& rvalue);
		~Data_Node();
		Data_Node();
	};


public:

	typedef hash_map<string,Data_Node,String_Hash> Data_Node_List;


protected:
	Data_Node_List nodes;

private:
	// helpers for load_from_string
	bool load_pair(string& p_dds, int p_begin, int p_end);
	string get_trim(string& p_str, int p_begin, int p_end);

public:

	typedef vector<int> vector_int;
	typedef vector<string> vector_string;
	typedef vector<float> vector_float;
	typedef VectorPointer<DDS> vector_dds;

	Data_Node_List::const_iterator get_iterator_begin();
	Data_Node_List::const_iterator get_iterator_end();

	const DDS& operator=(const DDS &rvalue);

SCRIPT_BIND_BEGIN
	void copy_from(const DDS *rvalue);

	void set_int_var(string p_var,int p_value);
	void set_str_var(string p_var,string p_value);
	void set_pointer_var(string p_var,void *p_ptr);
	void set_data_var(string p_var,void *p_ptr, int p_size);
	void set_float_var(string p_var, float p_value);

	vector_int& set_int_array_var(string p_var,const vector_int& p_array = vector_int());
	vector_string& set_str_array_var(string p_var,const vector_string &p_array = vector_string());
	vector_float& set_float_array_var(string p_var, const vector_float &p_array = vector_float());

	DDS& set_dds_var(string p_var, const DDS& p_dds = DDS());
	vector_dds& set_dds_array_var(string p_var, const vector_dds& p_dds_array = vector_dds());

	DDS& get_dds_var(string p_var);
	vector_dds& get_dds_array_var(string p_var);

	int get_int_var(string p_var);
	float get_float_var(string p_var);
	string get_str_var(string p_var);
	void* get_pointer_var(string p_var);

	const vector_int& get_int_array_var(string p_var);
	const vector_string& get_str_array_var(string p_var);
	const vector_float& get_float_array_var(string p_var);

	void* get_data_var(string p_var);
	void write_data_var(string p_var,void *p_data_ptr);

	int get_data_var_size(string p_var);

	// returns false on error
	bool load_from_string(string p_msg);

	void delete_var(string p_var);

	bool is_var(string p_var);
	bool is_var(string p_var, Data_Types p_type);

	int get_var_count();
	Data_Types get_var_type(string p_var);

	string get_var_name(int p_index);
	Data_Types get_var_type(int p_index);

	void clear();

	// copy constructor
	DDS(const DDS& rvalue);
	DDS(string p_value);

	DDS();
	~DDS();
};
SCRIPT_BIND_END

On to more core Godot classes, here is the Ref<> class! This still exists today in Godot codebase 22 years later as the RefCounted class.

// ref.h
template <class T_RefData>
class Ref {
protected:

	struct InstanceData {

		int refcount;
		T_RefData* ptr;

	};
	InstanceData *shared_instance;


	void clear_reference();
	void create_from_instance(T_RefData *p_instance);

public:


	/// Copy from another Ref:
	inline Ref<T_RefData>& operator=(const Ref<T_RefData>& p_rvalue_ref);


	template <class T_AllowedCast>
	inline Ref<T_RefData>& operator=(const Ref<T_AllowedCast>& p_src_ref){

		T_RefData * check=p_src_ref.operator->();
		//if compiler fails in the above line, you are not assigning to a base type :)
		//It's a safety valve, but i dont know how to do it cleaner.

		if (check==0) //even if this should never happen, it will avoid compiler warnings
			return *this;

		*this=(Ref<T_RefData>&)p_src_ref;
		//printf("using this other one then?\n");

		return *this;
	}

	template <class T_AllowedCast>
	inline bool cast_dynamic(const Ref<T_AllowedCast>& p_src_ref) {

		T_RefData * check=dynamic_cast<T_RefData*>(p_src_ref.operator->());
		if (!check)
			return true; //cast failed
		//dynamic cast successful!
		*this=(Ref<T_RefData>&)p_src_ref; //It's ugly but works fine and it's safe!

		return false;
	}


	//pointer operator override
	inline T_RefData* operator->() const;
	inline T_RefData& operator*() const;
	inline operator bool() const;
	inline bool operator==(const Ref<T_RefData>& src) const;
	inline bool operator!=(const Ref<T_RefData>& src) const;


	inline bool is_empty() const; //true if null
	inline void clear(); //unreference

	inline void manage(T_RefData *p_ref); //manage a pointer

	inline int get_refcount();

	/* Constructors */

	inline Ref(); //for creating a new one

	explicit inline Ref(T_RefData* p_shared_instance);
	inline Ref(const Ref<T_RefData>& p_src_ref);

	template <class T_AllowedCast>
	inline Ref(const Ref<T_AllowedCast>& p_src_ref) {

		shared_instance=0;
		T_RefData * check=p_src_ref.operator->();
		//if compiler fails in the above line, you are not assigning to a base type :)
		//It's a safety valve, but i dont know how to do it cleaner.

		if (check==0) //even if this should never happen, it will avoid compiler warnings
			return ;

		*this=(Ref<T_RefData>&)p_src_ref;
		//printf("Assigning, now refcount is %i\n",get_refcount());

	}

	inline ~Ref(); //for destroying/dereferencing

};

More core classes, this is BitMap, a precursor to the Image class. Yes back then palletes were a thing. I think the open sourced version of Godot still had some pallete support.

// bitmap.h

class BitMap {
public:

	enum PixelFormat {

		BITS_NONE,
		BITS_8_GRAYSCALE,
		BITS_8_PALETTE,
		BITS_15, //5,5,5
		BITS_16, //5,5,5,1 extra bit for alpha
		BITS_24,
		BITS_32,
  		BITS_MAX
	};

	static const int pixel_size[BITS_MAX];

	int get_data_size();

	PixelFormat pixel_format;
	unsigned width;
	unsigned height;
	int get_pixel_size() { return pixel_size[pixel_format]; }

	Uint8 *data;

	void blit_from(BitMap *p_bitmap,int p_x=0,int p_y=0);

	void create_scale_from(int p_width,int p_height, BitMap *p_bitmap);
	void initialize_form_params(PixelFormat p_format, int p_w, int p_h);

	void set_black();
	void clear();
	bool is_empty();

	// @todo invert_x :)
	void invert_y();

	BitMap& operator=(BitMap& rvalue);

	BitMap();
	~BitMap();
};

And here a class that is favorite of everyone that also remains today in Godot, though with 22 years of changes, InputEvent:

#include "typedefs.h"
#include "p2d.h"


struct InputModifierStatus {

	bool shift;
	bool alt;
	bool control;
	bool meta; //< windows/mac key

	bool operator==(const InputModifierStatus& rvalue) {

		return ( (shift==rvalue.shift) && (alt==rvalue.alt) && (control==rvalue.control) );
	}

};



struct InputEvent {

	enum Type {
		KEY,
		MOUSE_MOTION,
		MOUSE_BUTTON,
		JOYSTICK_MOTION,
		JOYSTICK_BUTTON
	};

	Type type;
	int recursion_count;


	virtual ~InputEvent();

protected:
	InputEvent();
};




struct InputEventKey : public InputEvent {

	bool pressed; // otherwise release

	Uint16 scancode;
	Uint16 unicode; //unicode

	InputModifierStatus mod;

	bool echo; // if this is an echo key
};


struct InputEventMouse : public InputEvent {

	Point2D pos;
	Point2D global;
	bool drag_mode;

};

struct InputEventMouseButton : public InputEventMouse {

	Uint8 button_index;
	bool pressed; //otherwise released
	bool doubleclick; //last even less than doubleclick time
};

struct InputEventMouseMotion : public InputEventMouse {

	Uint8 button_state_bits; //each bit is a button
	Point2D relative;

};

struct InputEventJoystickMotion : public InputEvent {

	Uint8 axis;
	int axis_value;
};

struct InputEventJoystickButton : public InputEvent {

	Uint8 button_index;
};

Moving to the higher level side of things, here is the precursor to the Object class (though did kinda the same things as Node too). My engines always used signals since the early days, so initially libsigc++ was used.

A lot of the things here are preserved today in Godot one way or another, 22 years later:

  • Messages can be sent (via call() or call_deferred() functions)
  • ENTITY_TAG is kind of similar to GDCLASS
  • queue_free() was called suicide().
// entity.h

#define ENTITY_TAG \
public:\
	static Entity* create_this(); \
	Message input_message(Message p_msg, bool direct); \
    string get_type(); \
    protected:\
	int get_callback_offset(); \
	void initialize_internal(); \
    private:\


#define __callbacks
#define MESSAGE_FORMAT(x,y)

class Entity : public SigC::Object { SCRIPT_EXPORT
public:

	struct CallbackInfo {

		int hash_id;
		DDSFormat* format;

		CallbackInfo() {

			format = NULL;
		};
	};

	typedef list<string> MethodList;

private:
	/*Internal Message Handlers List*/

	MessagePoster *message_poster;

protected:

	typedef hash_map<string,CallbackInfo,String_Hash> CallbackList;
	CallbackList callback_list;

	//TRY TO NOT USE THIS, USE SEND_MESSAGE INSTEAD!
	void output_message(Message &p_msg);

	Addr info;

	/* Message Callback Methods*/
	void register_callback(int p_hid, string p_name, string p_format);

	void fill_message(string& p_type, const Addr& p_destinatary, Message& p_msg, const Addr& p_spoof);

	Entity();

	virtual Message input_message_internal(Message &p_msg, bool direct);
	virtual void initialize_internal();

	virtual int get_callback_offset();

	bool validate_message(Message& p_msg, DDSFormat* p_format);

public:
	/*Message Sending Methods */
	void send_message(string type, const Addr &p_destinatary, Message p_msg = Message(), const Addr &p_spoof = Addr());
	void send_message(string type, const Addr &p_destinatary, string p_string_msg, const Addr &p_spoof = Addr());
	void send_message(Message p_msg);
	Message send_direct_message(string type, const Addr &p_destinatary, Message p_msg = Message(), const Addr &p_spoof = Addr());
	Message send_direct_message(string type, const Addr &p_destinatary, string p_string_msg, const Addr &p_spoof = Addr());

	SCRIPT_BIND_BEGIN;
	#if 0
	void send_message(string type, const Addr &p_destinatary);
	void send_message(string type, const Addr &p_destinatary, Message p_msg);
	void send_message(string type, const Addr &p_destinatary, string p_string_msg);
	void send_message(Message p_msg);
	Message send_direct_message(string type, const Addr &p_destinatary);
	Message send_direct_message(string type, const Addr &p_destinatary, Message p_msg);
	Message send_direct_message(string type, const Addr &p_destinatary, string p_string_msg);
	#endif
	SCRIPT_BIND_END;
public:

	SigC::Signal1<void,Addr> suicided;

	virtual string get_description();

	void set_info(const Addr& p_info);
	void set_info(string p_name,int p_instance); // it goes here to avoid writing it in all constructors.

	void set_message_poster(MessagePoster *p_message_poster);
	/*basic message IO*/
	virtual Message input_message(Message p_msg, bool direct)=0;

	Message constructor_real(Message p_msg); // this calls initialize_internal
	virtual Message constructor(Message p_msg); ///< **IMPORTANT** do _not_ use the class constructor for initialization stuff that requieres sending messages, it WONT work. - use this instead!
	virtual void destructor(); ///< **IMPORTANT** do _not_ use the class destructor for deinitialization stuff that requieres sending messages, it WONT work. - use this instead!

	SCRIPT_BIND_BEGIN;
	string get_name() { return info.name; }
	int get_instance() { return info.instance; }
	void suicide();
	SCRIPT_BIND_END;


	MethodList get_method_list();

	virtual ~Entity();

}; SCRIPT_EXPORT;

There was the EntityManager, which is kind of simliar to todays SceneTree.

// entity_manager.h
/**
  *@author
  */

class EntityDebugger;

class EntityManager : public SigC::Object {
	//message poster
public:

	struct Instance_List {

		typedef hash_map<int,Entity*> Entity_Instances;
		Entity_Instances instance;
	};

	typedef hash_map<string,Instance_List,String_Hash> Entity_List;

	enum {

		AUTOMATIC_ID_ASSIGN=-1
	};

private:

friend class EntityDebugger; /*ONLY AVIABLE FOR ENTITY DEBUGGER!! */

	static EntityManager *entity_manager_instance;
	MessagePoster *msg_poster;
	typedef hash_map<string,Entity* (*)(void),String_Hash> Entity_Constructor_List;


	//constructor list!
	Entity_Constructor_List entity_constructor_list;

	Entity *get_dead_entity(const Addr& p_addr);


	Entity_List entity_list;
	Entity_List dead_entity_list;

 	void entity_commited_suicide_callback(Addr p_addr);
	void kill_entity(const Addr& p_addr, bool make_zombie = 1);

	int automatic_id_assign;

public:


	static EntityManager *get_singleton_instance();

	bool has_entity_type(string p_type);
	/*constructor*/
	void register_constructor(string p_name,Entity* (*p_constructor)(void));

	/*initialize/deinitialize*/
	void initialize_entity(const Addr& p_addr,Message p_msg=Message(NULL));
	/* returns -1 on failure */
	int initialize_entity_as_custom_type(const Addr& p_addr, string p_type="",Message p_msg=Message(NULL), Entity* p_existing = NULL);
	bool register_initialized_entity(const Addr& p_addr, Entity* p_entity, Message &p_msg);
	void destroy_entity(const Addr& addr);
	/*retrieve*/
	Entity *get_entity(const Addr& p_addr);


	void set_message_poster(MessagePoster *p_msg_poster);
	EntityManager();
	~EntityManager();
};

Okay so now lets move to GUI code!

Here is the Widget class, which is the precursor to today's Control. You will see that a lot still is in common, including many signal names!

// widget.h
SCRIPT_BIND_BEGIN;
class Widget : public SigC::Object {
public:

	typedef list<Widget*> List;

	enum FocusPolicy { ///< Determines when does widget receive input focus (keyboard/joystick/etc)
                FOCUS_NONE, ///< Cant receive focus
		FOCUS_INTERNAL, ///< Widget decides when to get focus
		FOCUS_ON_CLICK ///< Widget decides, but also automatically when clicked
	};

	enum LayerPolicy { ///< feel free to use custom values, these are provided as reference
		LAYER_BOTTOM=-10,
		LAYER_LOW=-5,
		LAYER_NORMAL=0,
		LAYER_HIGH=5,
		LAYER_TOP=20
	};


SCRIPT_BIND_END;

private:

        WidgetPrivate *p;
	Widget(); //cannot instance this!

        void add_child(Widget *p_child);
        void remove_child(Widget *p_child);
	void resort_child(Widget *p_widget,bool p_begin=true);
	void visibility_changed_notify(bool p_visible);

/*****************************
	EVENTS & SIGNALS:
*****************************/

private: /* Internal events */

	void mouse_button(const InputEventMouseButton& p_button);
	void mouse_motion(const InputEventMouseMotion& p_motion);
	void key_press(const InputEventKey& p_event);
	void key_release(const InputEventKey& p_event);

protected: /* Overridable events */

	virtual void paint_override();
	virtual bool input_override(const InputEvent* p_event);
	virtual void resize_override();
	virtual void mouse_enter_override();
	virtual void mouse_leave_override();
	virtual void mouse_button_override(const InputEventMouseButton& p_button);
	virtual void mouse_motion_override(const InputEventMouseMotion& p_motion);

	virtual void focus_in_override();
	virtual void focus_out_override();

	virtual void key_press_override(const InputEventKey& p_event);
	virtual void key_release_override(const InputEventKey& p_event);


protected: /* InputEvents only started from the Event Manager */
friend class EventManager;

	bool input(InputEvent* p_event);
	void mouse_enter();
	void mouse_leave();
	void focus_in();
	void focus_out();
	void paint();

	void kill_notify(); ///< EventManager and other widgets use this to kill their childs, mainly to confirm the safe deletion of it.

	Widget * find_subwidget_at_pos(const Point2D& p_pos);

	/* Besides Widget, these can only be set by the event manager! */
	void set_event_manager(EventManager *p_manager);
	void set_painter(Painter *p_painter);

public: /* Signals */

SCRIPT_BIND_BEGIN;

	tolua_readonly SigC::Signal0<void> paint_signal;
	tolua_readonly SigC::Signal1<bool,const InputEvent*> input_signal;
	tolua_readonly SigC::Signal0<void> resize_signal;
	tolua_readonly SigC::Signal0<void> minimum_size_changed_signal;
	tolua_readonly SigC::Signal0<void> mouse_enter_signal;
	tolua_readonly SigC::Signal0<void> mouse_leave_signal;
	tolua_readonly SigC::Signal0<void> focus_in_signal;
	tolua_readonly SigC::Signal0<void> focus_out_signal;

	tolua_readonly SigC::Signal1<void,const InputEventMouseButton&> mouse_button_signal;
	tolua_readonly SigC::Signal1<void,const InputEventMouseMotion&> mouse_motion_signal;
	tolua_readonly SigC::Signal1<void,const InputEventKey&> key_press_signal;
	tolua_readonly SigC::Signal1<void,const InputEventKey&> key_release_signal;

SCRIPT_BIND_END;
/*****************************
	RESIZE HOOKS:
*****************************/

protected: ///< Automatic resizing hooks

	void resize_childs();
	void resize_painter();
	void update_minimum_size();
	void update_painter();
	void layout_changed_callback();

friend class Layout;

	void set_size(const Size2D& p_size); ///< Only GUI::Widget and GUI::Layout are allowed to change this, users must resort to request_size()
	void set_pos(const Point2D& p_size); ///< Only GUI::Widget and GUI::Layout are allowed to change this, users must resort to request_pos()
	int compute_min_width();
	int compute_min_height();
	void set_layout_private_data(LayoutPrivateData *p_data); ///< Layouts set their private data on the widget, for more resizing hints!
	LayoutPrivateData *get_layout_private_data();
	const Size2D& get_minimum_own_size() const;

protected:
	/* Overridable function add_child! */
	virtual void add_child_override(Widget *p_child);
	virtual void configure_properties_override(); ///< Write the code that configures widget properties here

	const WidgetPropertyManager *get_active_property_manager() const; ///< Request the property manager
	const WidgetPropertyList *get_widget_property_list(string p_name) const; ///< Request property data for a widget type
	void set_skip_input_events(bool p_skip); ///< Warning! the behavior of this is hard to explain, it causes a widget to be skipped on the search-for-the-widget-under-mouse-to-send-the-event-to process. It gets skipped if it detects itself as the end of that search, but the childs are allways searched. Usually you shouldnt set this unless your widget does not perform drawing (like a container -hbox/table/etc-). It's only real use is to avoid them to capture events on a widget overlapping.
public:

/*****************************
	PUBLIC INTERFACE:
*****************************/

	/* Childs */

	virtual void get_childs_list_iterators(List::iterator *p_begin,List::iterator *p_end); ///< return iterators to child widgets
	virtual void get_visible_childs_list_iterators(List::iterator *p_begin,List::iterator *p_end); ///< return iterators to inside-this-widget-area child widgets. Mostly usually for when you do viewports

SCRIPT_BIND_BEGIN;
	int get_child_count();

	/* Binds */

	void set_layout(Layout *p_layout);
	void set_layout_border(const Border2D& p_border);
	Layout *get_layout();

	Painter *get_painter();

	EventManager *get_event_manager();

	void set_property_manager(WidgetPropertyManager *p_panager);
	const WidgetPropertyManager *get_property_manager() const;
	void configure_properties(); ///< Asks the widegets (and childs) to reconfigure its properties from the property lists


	Widget * get_parent() const;
	void unparent();
	void reparent(Widget *p_parent);

	/* Allocation */

        void set_resize_rules(const ResizeRules& p_rules);
	const ResizeRules& get_resize_rules() const;
	ResizeRules& get_resize_rules_writable();

	void set_layer_policy(int p_policy);
	int get_layer_policy() const;
	void raise();
	void lower();

	void grab_modal();
	void release_modal();

	void request_size(const Size2D& p_size);
	const Size2D& get_size() const;
	Size2D get_requested_size();
	bool has_requested_size();

	void set_minimum_own_size(const Size2D& p_size);
	Size2D get_minimum_size() const;

	void request_pos(const Point2D& p_pos);
	Point2D get_requested_pos();
	bool has_requested_pos();
	const Point2D& get_pos() const;

	Point2D get_global_pos() const; ///< get widget position in screen coordinates

	void set_area_rules(ResizeRules::Policy p_h_policy,ResizeRules::Policy p_v_policy);
	void set_h_area_rule(ResizeRules::Policy p_h_policy);
	void set_v_area_rule(ResizeRules::Policy p_v_policy);

	void set_fill_rules(ResizeRules::Policy p_h_policy,ResizeRules::Policy p_v_policy);
	void set_h_fill_rule(ResizeRules::Policy p_h_policy);
	void set_v_fill_rule(ResizeRules::Policy p_v_policy);

	void set_align(ResizeRules::Align p_h_align,ResizeRules::Align p_v_align);
	void set_h_align(ResizeRules::Align p_h_align);
	void set_v_align(ResizeRules::Align p_v_align);

	/* Flags */

	void set_focus_policy(FocusPolicy p_policy);
	FocusPolicy get_focus_policy() const;
	void grab_focus();

	void set_visible(bool p_visible); ///< change widget visibility
	bool is_visible() const;

	void set_clipping(bool p_clipping);
	bool is_clipping();

	void set_enabled(bool p_enabled); ///< change widget ability to set or receive events
	bool is_enabled() const;

        bool has_focus();

	/* Interface */
	void paint_request(); ///< requests repaint, which will happen at the end of the frame. so call this as many times as you want
	void kill(); ///<Use this to delete a widget. The widget will be put on a to-kill-list and will be removed on the next frame

	virtual string get_type();

	Widget(Widget* p_parent);
	~Widget(); ///< It is usually NOT a good idea to kill a widget by deleting it, since there may be events pending (this is a C++ limitation). This makes it hard for widgets killing themselves. Unless you _ultimately_ need to kill a widget, use kill() instead.
};
SCRIPT_BIND_END;
};

So how was a button implemented? It's surprisingly similar to todays Button! Here is the code: Note also how another familiar name shows up, StyleBox. Those were there since the beginning of times too.

/// button.h
namespace GUI {
/**
@author Juan Linietsky
*/

class ToggleButton;

class Button : public Widget { SCRIPT_EXPORT

private:
	struct Vars {

		bool toggle;
		bool pushed;
		bool pushing;
		bool draw_pressed;
		bool cursor_over;
		int press_offset;
                Border2D layout_border;

	} vars;

	struct Styles {
		StyleBox normal;
		StyleBox over;
		StyleBox pressed;
		StyleBox disabled;
	} style;

	void mouse_enter_override();
	void mouse_leave_override();
	void mouse_button_override(const InputEventMouseButton& p_button);
	void mouse_motion_override(const InputEventMouseMotion& p_motion);
	string get_type();

	void paint_override();
	void set_draw_pressed(bool p_pressed);

	Label *label;

	void configure_properties_override();

friend class ToggleButton;
	virtual void toggled_notify_override();
	void set_toggled_internal(bool p_toggled);

protected:
	virtual void click_override();

SCRIPT_BIND_BEGIN;
public:
	tolua_readonly SigC::Signal0<void> click_signal;

	void set_text(const string& p_text);

	void set_style_normal(const StyleBox& p_style);
	void set_style_over(const StyleBox& p_style);
	void set_style_pressed(const StyleBox& p_style);
	void set_style_disabled(const StyleBox& p_style);

	const StyleBox& get_style_normal();
	const StyleBox& get_style_over();
	const StyleBox& get_style_pressed();
	const StyleBox& get_style_disabled();

	bool is_pushed();

	Button(Widget *p_parent,const string &p_text="", bool p_toggle=false);

	~Button();

};



class ToggleButton : public Button {
SCRIPT_BIND_END;

	void toggled_notify_override();
protected:

	virtual void toggled_override(bool p_pushed);

public:
	SCRIPT_BIND_BEGIN
	tolua_readonly SigC::Signal1<void,bool> toggled_signal;
	void set_toggled(bool p_toggled);
	ToggleButton(Widget *p_parent,const string &p_text="");

};
SCRIPT_BIND_END;

};

Okay here is as far as we get today, hoping you enjoyed this trip back to 2002.

BONUS: Some images of how it looked like:

disabled engine_desktop smallfont engine_map_editor gfx_selector2 itworks larvotor psycho_cube world regnum

@EGAMatsu
Copy link

EGAMatsu commented Feb 6, 2024

Would it ever be possible for a early build of the engine (without the project code for this game) to be made available?
I've been interested in developing under a prototype Unity/Godot version for a experiment for awhile, it'd be kinda neat if it could be available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment