Request for Comment: cslib_class

cslib_object it is. ‘Class’ was always a bit pretentious…

I’ve got some time this weekend so maybe I can do it over the next couple of days.

2 Likes

I’ve created a new file and testfile on branch_objects but I’m not able to push the new branch. Looks like I need some sort of permission set?

Stephens-iMac:cslib stephen$ git push origin branch_objects
Username for ‘https://github.com’: StephenXHart
Password for ‘https://StephenXHart@github.com’:
remote: Permission to ChoicescriptIDE/cslib.git denied to StephenXHart.
fatal: unable to access ‘GitHub - ChoicescriptIDE/cslib’: The requested URL returned error: 403

Note: I’m not an habitual git user so it’s quite possible I have stuffed up somewhere.

If you go the repo page, in the top right you can “fork” it. This will make a copy of the repo under your profile, which you’ll be able to commit to. Once you’re happy with your commit(s), you can then use the UI to create a pull request of the differences between the two repos, allowing you to merge into the original repository (subject to review and approval).

Feel free to PM me if you’re still having trouble.

Got it, thanks. Pull request submitted.

(My entire career has been CVS or Subversion. I’m still a git noob.)

1 Like

Hmm, I don’t see it. What’s your Github username? You might have pulled against your local repo, rather than the parent (forked) repo.

EDIT: Ignore me, I’m being blind :flushed:

EDIT2: I’ve made some comments :blush:

1 Like

The only limitation I see in this kind of implementation is that we cannot create and destroy objects dynamically, since ChoiceScript does not allow variables to be created during runtime. That is, if I’m not mistaken, we have to know up front how many dragons we’re going to have in the game, or at least the maximum we’re going to allow to exist.

For example, in the case of my current project, I needed several instances of a given “object” with eleven fields. Since I don’t really know how many will be needed (it depends on how the game goes each time) I estimated as many as possible and decided to create ten instances, which immediately forced me to have 110 *create commands in startup.txt (and more because there are other auxiliary variables). The part that is very similar to this approach is that I also created an additional generic “instance”, with the same eleven fields, to fetch the information needed at a given time.

On the other hand, for inventory management, I preferred a different, simpler approach that I have already shared in this topic, which is list-based. In that approach, the “object” is an item with two fields, description and amount, but that could be extended, although limited of course by the maximum size a string can have in ChoiceScript, of which I am not aware. The advantage is that it is very easy to use even for those without much programming knowledge.

I think an abstraction layer could exist over such an implementation of lists to make an object-based approach possible and allow different types of “objects” with specific fields. I don’t know if that would bring too much value for the authors, but it would be a lot of fun to implement. :slight_smile:

1 Like

The lack of dynamic variables is one of the real pains of ChoiceScript. I don’t think there is any way around defining everything up front. Sounds like you came to pretty much the same conclusion I did :slight_smile:
Had a look at your inventory system and it looks pretty cool. I’m guessing that when the inventory gets too big it will be a bit slow, given it involves manipulating strings. Performance might be the issue.
cslib_object has been added to cslib (GitHub - ChoicescriptIDE/cslib) if you are interested in the implementation.

2 Likes

Regarding performance issues with a string-based approach, I have that same perception. I tried to do some testing, but I got this error in my inventory module: “visited this line too many times (1000)”. I still haven’t found a way to override this rule in order to proceed with the tests.

EDIT: just found the *looplimit command!

An example of the kind of commands I would imagine in such an approach would be:

*gosub_scene objects class "dragon" "name,hp,color"
*gosub_scene objects new "dragon" "currentdragon" "John,50,blue"
*gosub_scene objects set "currentdragon" "hp" "30"
*gosub_scene objects get "currentdragon" "name" "mydragonname"

In any case, I will be available to help with the implementation of cslib_class, perhaps with any tests that you need to be done.

2 Likes

Help in any form: be it thoughts, comments or patches is very welcome! Unfortunately we agreed to keep cslib to vanilla CS, so we can’t use *script to create objects dynamically.

I have an open PR up for “create_array”: Add *create_array, *temp_array, *delete_array by CareyJWilliams · Pull Request #136 · dfabulich/choicescript · GitHub

Which I feel would significantly reduce the pain of pre-defined arrays and objects :crossed_fingers:

2 Likes

It would be great to have these commands available!

1 Like

If you could find a way to do this without the performance hit, it would be awesome. Class/Set/Get we effectively have but ‘new’ is the killer.

A slightly kludgy alternative would be to follow the approach of cslib_array which (I think - I haven’t used it) predefines some empty slots, enabling your usual push/pop/etc (up to the size limit defined by the number of slots).

You could predefine say, 24 dragon slots, and then use ‘new’ to fill one of these slots. You still have the upfront work in startup.txt but you don’t have to actually have real values until run time.

2 Likes

Notes on performance testing on a string-based approach using the inventory module:

The inventory management module was poorly optimized and I have been improving it. Then I wrote a routine that created 100 things in it (would be 100 instances of the same class, with two fields, name and amount) followed by 100 searches for random things (half of those non-existent, forcing to search the entire base string). Note that when creating a thing in the inventory, the program checks if it already exists, so it always goes through the entire base string.

image

This whole execution took between 17 and 20 seconds. In a real-time transactional system, it would be an eternity. In this use case, given that an item (object) is added or checked from time to time, it would be unnoticeable to the reader. My only concern is not to reach the maximum length of a string.

But it should be noted that these times are measured on the server that is on my machine. Also, the elapsed time is exponential (when adding the 20th unique item, the previous 19 are checked, but when adding the 1000th unique item, the other 999 items are checked, which takes much longer). Creating 1000 unique items in the same base string (i.e. 1000 objects of the same class) took almost nine minutes.

The question is: do you really want 1000 dragons? Maybe.

This would be easily done with the *create_array command :slight_smile:

Objects tend to have the opposite problem to inventories - there is a smaller number of objects with a greater number of fields, say 20 dragons each with 10 fields (colour, hit points, treasure, etc). In some ways this might be easier in that you only have to check for uniqueness 20 times.

Have you done any experiments with fixed-length fields? I imagine it would speed things up a lot if you could jump along the string by a fixed amount when doing your search.

1 Like

That is an excellent idea! Although it would force the writer to declare the maximum length of each field beforehand, it would remove the performance hurdle.
I will try some experiments along these lines.

Here’s a summary of what I have already implemented. To define a class, all we need is this:

*create class_dragon "name,hp,color,alignment"

The “class_” prefix is required. The default maximum length of each field is 20. If another value is needed, we can declare it this way:

*create class_dragon "name:35,hp:3,color,alignment"

The class base string will be used to control the class parameters as well as to store all instances. My perception is that this way is the easiest to use for the end user.

However, we need to create some variables to store the instances we fetch, like this:

*create currentdragon_name ""
*create currentdragon_hp 0
*create currentdragon_color ""
*create currentdragon_alignment ""

To create a new instance, this is the syntax:

*gosub_scene objects new "dragon" "John,50,blue,lawful evil"
*gosub_scene objects new "dragon" "Trevor,70,green,chaotic good"

Now we have two dragons, ready to be summoned.

If we want the “currentdragon” variable set to be filled at the same time, we can use:

*gosub_scene objects new "dragon" "John,50,blue,lawful evil" "currentdragon"

currentdragon_name == “John”
currentdragon_hp == 50
currentdragon_color == “blue”
currentdragon_alignment == “lawful evil”

We can, of course, have more than one instance active at a given time, we just need more variables…

*create myotherdragon_name ""
*create myotherdragon_hp 0
*create myotherdragon_color ""
*create myotherdragon_alignment ""

To fetch an instance, we use:

*gosub_scene objects get "dragon" "name" "Trevor" "myotherdragon"

This will fetch the first dragon named Trevor that has been stored and puts it into the “myotherdragon” variable set:

myotherdragon_name == “Trevor”
myotherdragon_hp == 70
myotherdragon_color == “green”
myotherdragon_alignment == “chaotic good”

Now they can fight each other.

Right now I am working on other methods, one to change the value of an instance’s field, another to destroy an instance, and a third to randomly return an instance.

Much of what I wrote above is compatible with the behavior you intended for cslib_class, but since I am working with a different approach for storing the instances I had to write the code from scratch.

I’ll share the code later today or tomorrow, once I have the remaining methods completed. But I wanted, for now, to know what you think about the direction this is taking. My idea is to make it as simple as possible for anyone who wants to use it.

1 Like

This is awesome.

I particularly like the way you can instantiate your data somewhere other than startup.txt - that file can get pretty crowded.

I also like that you can have more than one active object.

How do you handle it if someone tries to “set” a value too long for the field? When you instantiate it can just bug out but dynamically do you think you should bug or just truncate?

I’ll be really curious to find out how much performance is improved by the fixed-length fields.

1 Like

I like this a lot too, using strings definitely gives a lot of flexibility, albeit with a performance tradeoff.

Some specific thoughts…

I like asking the user to specify the name of the global store, and it’s a pattern already used in parts of cslib. Definitely a fan over expecting people to create a specifically named set of globals.

Something we’re missing in @StephenHart 's cslib_object is the ability to reference by IDs, which would be a lot more efficient (at least if you’ve got fixed widths in string land). Not to replace the field version, but having that as well would be good.

*gosub_scene objects new “dragon” “Trevor,70,green,chaotic good”

Do these have to be in order? Because it’s a pain to back-reference for complicated classes. A more verbose but flexible way we’ve looked at (but haven’t yet implemented) in @StephenHart 's solution is to use key-value pair parameters:

*gosub_scene objects new "dragon" "alignment" "chaotic good" "hp" 70 "name" "Trevor" "color" "green"

Where the order doesn’t matter, and for a *set routine you can specify as many or as few fields as you like.

You could do that in a string as well, but with parameters you don’t have the effort and performance hit of parsing the string.

1 Like

Right now I am throwing a *bug with a specific message. Truncating is also a good possibility, but the user may not realize the error and have a harder time noticing when the game misbehaves.

Me too! I haven’t actually done performance testing yet, but the fact that the fields are fixed-length is great. I’ve already implemented the method to return a random instance and it’s very straightforward to go to that specific position in the string and fetch the corresponding block.

I had already figured that out from the discussion and I’m thinking about it. It might make sense to have an ID field - after all we are building a database! If there is a use case that justifies it, it won’t be hard to implement. EDIT: Just implemented: get_by_index and get_by_field methods. The index is the position of the instance in the string.

I confess that I didn’t think about it. Right now the values have to be in order (although they may be empty) but you have a point.

However, if we have ten fields, we need twenty parameters for those. And I don’t know how many fields the class will have beforehand (that’s the cost of flexibility) so I wouldn’t know what to put in the *parms statement. With proper testing, I think I prefer this format:

*gosub_scene objects new "dragon" "name:Olivia,alignment:true neutral,color:orange,hp:250"

But I also thought of another possibility:

*set yetanotherdragon_name "Olivia"
*set yetanotherdragon_hp 250
*set yetanotherdragon_color "orange"
*set yetanotherdragon_alignment "true neutral"

*gosub_scene objects new "dragon" "yetanotherdragon"

Or both! What do you think?

Probably some choices/changes will eventually be made when we experience it in practice.

P.S.: One thing that will be quite difficult to implement with this approach is the inheritance relationship.

I very much agree: logic errors are much harder to detect and debug than well written runtime errors.

:+1:

If you leave *params empty you can have an arbitrary number of arguments, named param_1, param_2 etc. You can check the number of arguments with param_count.

Definitely better than nothing, but string parsing in CS is slow and expensive, even by CS standards!

This one feels quite verbose, but as you say, may be worth trying different ones in the field before jumping to conclusions!

I think that was always ambitious :')

1 Like