A closer look at the Changelog

Freeform discussion about anything related to modding Transcendence.
User avatar
alterecco
Fleet Officer
Fleet Officer
Posts: 1658
Joined: Wed Jan 14, 2009 3:08 am
Location: Previously enslaved by the Iocrym

Hi George,

My eyes accidentally fell on a line in the changelogs that I had not seen previously, and it cause me to go over the rc's log, line by line. There seem to be some real interesting things I have missed out, and have some questions to.

* (block) accepts initializers for locals.

This sounds very promising, but I could not find any (obvious) example in the xml. Would you mind giving an example of it's use?

* (obj*) calls are checked for invalid obj param.

Does this mean the game does not crash when given an invalid spaceObject? If not, what happens then. Does the functions simply return Nil? The reason I ask is that I have used it quite a lot in some mods. An obj* function wrapped in an isError prevented the crash, but the isError evaluated to True, so everything could be handled gracefully. Do you have any idea if this will be affected, and any suggestions as to how this can be handled now?

* Added extra parameter to (find) to find an entry in a list.

This might just be a bit unclear. Are you referring to the optional keyindex at the end of find?

* Added (objSuspend) and (objResume).

These are two very nice functions I will be looking at using. A question though: Does a suspended object show up in sysFindObjects? That might be a bit problematic, as you could end up targeting a currently non-existing object.

* Added (objGetDataField).

Is this comparable to typGetDataField, just with a spaceObject as target?

* % prefix on lambda variables prevent evaluation.

This one has me very intrigued. I could not find any reference in the source, and I must admit I have not tested it yet, but am I right in assuming that (dbgLog %(add 1 1)) logs "(add 1 1)" not "1". Or is it (lambda (%var) ...)?
If it is one of those, then yay! :D

* Added <OnLoad> event for objects.

Again, i have not tested it, and can find no example in the source. Is this called on every game client start? Very interesting.

* (sysCreateShip) takes an override as its last parameter (instead of controller).

This one is not completely clear to me either. fncHelp still lists the old syntax (sysCreateShip classID pos sovereignID [controller]) -> ship, and there is no obvious example in the source. Again, it sounds very interesting :)

* (shpCreateShip) takes <ShipTable> as UNID.

This is a spelling mistake I think. Should be sysCreateShip. Perhaps the docstring for the function should be updated to reflect the new syntax?

* <OnCreate> for stations is now a direct child (not a child of <Events>) to match <OnCreate> for <Ship>.

This does not match the source. After a quick look I could only see OnCreate as a child of Events. Am I getting it wrong somehow? In either case, I think the best thing to do is have it as a child of Events.

* Added (objIsAngryAt).

This is a candidate for best function name of the year :D
Is angry some property other than enemy? If so, how is it set?



All in all, it is great looking at the changelog. So many bugs fixed, so many inconsistencies ironed out, and so many new toys to play with :D

I know I could test myself to conclusions on some of the answers above, but I was hoping to "hear it from the horse's mouth" :D

Thanks in advance!
speedofsquid
Commonwealth Pilot
Commonwealth Pilot
Posts: 55
Joined: Wed Aug 27, 2008 6:30 pm
Location: USA

alterecco wrote:* (block) accepts initializers for locals.

This sounds very promising, but I could not find any (obvious) example in the xml. Would you mind giving an example of it's use?
I saw this in StKatherines.xml (ll. 2218-9):

Code: Select all

(block ((status (itmGetData theItem "status"))
		(expireTime (itmGetData theItem "expireTime")))
User avatar
alterecco
Fleet Officer
Fleet Officer
Posts: 1658
Joined: Wed Jan 14, 2009 3:08 am
Location: Previously enslaved by the Iocrym

speedofsquid wrote:I saw this in StKatherines.xml (ll. 2218-9):
**snip**
Yes, that would be it! Thanks a lot speedofsquid!
george moromisato
Developer
Developer
Posts: 2998
Joined: Thu Jul 24, 2003 9:53 pm
Contact:

Some answers:

* (block) accepts initializers for locals.

Code: Select all

(block ((a 1) (b 7) (c (add 7 20)))
   (dbgOutput a)
   (dbgOutput b)
   (dbgOutput c)
   )
Note that evaluations of the initializers ocur outside the scope of the block, which means you can't refer to other local variables (e.g., in initializing c you can't refer to a or b).

This is really only useful in certain cases for clarity.

* (obj*) calls are checked for invalid obj param.

Today, yes, it returns Nil instead of an error (and instead of crashing). Note also, that most functions accept a Nil object parameter and don't crash (they do nothing).

* Added extra parameter to (find) to find an entry in a list.

Yes, this is just the optional keyindex at the end.

* Added (objSuspend) and (objResume).

Yes, these are included in sysFindObjects and probably lots of other places where they shouldn't be. Right now, these functions are only used in the Battle Arena, which is a controlled environment. I think I will need to change that in future versions, so don't rely on the current behavior.

* Added (objGetDataField).

Yes, this is the same as typGetDataField--it just saves having to get the UNID first.

* % prefix on lambda variables prevent evaluation.

Code: Select all

(setq myFunc (lambda (%var)
   (dbgOutput (eval var)
   )

(myFunc (add 7 10))
Note that, unfortunately, this doesn't turn the variable into a closure, meaning that you can't access local variables:

Code: Select all

(block (a b)
   (setq a 7)
   (setq b 10)

   (myFunc (add a b)) -> "Undefined variable 'a'"
   )
* Added <OnLoad> event for objects.

Yes, called on all objects in the system when the system is loaded (the system can be loaded either because the player restored a game or because the player entered a previously visited system).

* (sysCreateShip) takes an override as its last parameter (instead of controller).

Check out the function comTrafficControl in Commonwealth.xml. It creates new ships with an override so that they follow the traffic patterns.

When creating ship AI, there are three major methods:

1. Create a new class that has events that control behavior (e.g., Arco)
2. Create a base class that has events that control behavior and create classes that inherit from the base (e.g., Wingmen and autons)
3. Create an override that has events that control behavior and add the override to existing objects.

* (shpCreateShip) takes <ShipTable> as UNID.

You're right that this is a typo--should be (sysCreateShip).

* <OnCreate> for stations is now a direct child (not a child of <Events>) to match <OnCreate> for <Ship>.

This is a poorly worded change. Look at how the arms dealers in the Battle Arena are created (BattleArena.xml, line ~2373). In the <SystemType> element, when placing a <Station> it is possible to add a child <OnCreate> element to the station element.

* Added (objIsAngryAt).

Most of the time, this is equivalent to objIsEnemy. But for stations that have blackisted you, this will return True, even if the station is not normally an enemy.
User avatar
alterecco
Fleet Officer
Fleet Officer
Posts: 1658
Joined: Wed Jan 14, 2009 3:08 am
Location: Previously enslaved by the Iocrym

alterecco wrote: * % prefix on lambda variables prevent evaluation.

This one has me very intrigued. I could not find any reference in the source, and I must admit I have not tested it yet, but am I right in assuming that (dbgLog %(add 1 1)) logs "(add 1 1)" not "1". Or is it (lambda (%var) ...)?
If it is one of those, then yay! :D
This seems to be the syntax. Now I am just wondering at the application. Why not just pass it in as a string or literal?

Code: Select all

(setq test (lambda (%var) (dbgOutput %var)))
(test (add 1 1)) -> "(add 1 1)"
george moromisato
Developer
Developer
Posts: 2998
Joined: Thu Jul 24, 2003 9:53 pm
Contact:

alterecco wrote:Why not just pass it in as a string or literal?
I wanted to allow user-defined versions of functions like (loop).

For example:

Code: Select all

(setq myLoop (lambda (loops %code)
   (for i 1 loops
      (eval code)
      )
   ))

(myLoop 10
   (dbgOutput "I'm going to say this ten times")
   )
In the future, I will probably make this syntax also generate a closure so that any function is bound to the local scope. Like this:

Code: Select all

(block (a)
   ; This code won't work today, but it should in the future.
   (setq a "this")
   (myLoop 10
      (dbgOutput "I'm going to say " a " ten times")
      )
   )
For now, though, it just helps a tiny bit with syntax, but isn't as useful as it could be.

BTW, if you wanted to do the above today, you need to use a lambda expression:

Code: Select all

; No need for % or eval.
(setq myLoop (lambda (loops code)
   (for i 1 loops
      (code)
      )
   ))

(block (a)
   ; This code should work today (though I haven't tested it).
   (setq a "this")
   (myLoop 10
      (lambda Nil (dbgOutput "I'm going to say " a " ten times"))
      )
   )
User avatar
alterecco
Fleet Officer
Fleet Officer
Posts: 1658
Joined: Wed Jan 14, 2009 3:08 am
Location: Previously enslaved by the Iocrym

Great. This is turning out to be really useful. I have already rewritten my map, select, detect and reduce functions to use this. Super cool. Can't wait till they are closures also. :D

There is one functions I ran into some problems with, and that is a recursive function. Let me post the original here:

Code: Select all

(setq dsf_ListSort (lambda (lst fun)
    (block (len)
        (setq len (count lst))
        (if (leq len 1)
            lst ;; lst is 0 or 1, so is already sorted
            (block (half)
                (setq half (divide len 2))
                (dsf_ListMerge
                    (dsf_ListSort (subset lst half) fun)
                    (dsf_ListSort (subset lst 0 half) fun)
                    fun
                )
            )
        )
    )
))

;; Merge two lists together using fun for comparison
;; Primarily used by dsf_ListSort
(setq dsf_ListMerge (lambda (front back fun)
    (switch
        (not front)
            back
        (not back)
            front
        (if (fun (item front 0) (item back 0))
            (append (list (item front 0)) (dsf_ListMerge (subset front 1) back fun))
            (append (list (item back 0)) (dsf_ListMerge front (subset back 1) fun))
        )
    )
))
Now, this sorts a list, and works great. Eg.:
(dsf_ListSort '(9 5 8 1) (lambda (a b) (ls a b))) -> (1 5 8 9)

So far so good. Now, if you look at this thread you can see a syntax Taben came up with that is working very well (faster than passing a lambda in some cases)

Converting the sort functions however is not possible. Let me show what I tried:

Code: Select all

(setq dsf_ListSort (lambda (lst %a %b %exp)
    (block (len (fun (eval (list lambda (list %a %b) %exp))))
        (setq len (count lst))
        (dbgLog fun)
        (if (leq len 1)
            lst ;; lst is 0 or 1, so is already sorted
            (block (half)
                (setq half (divide len 2))
                (dsf_ListMerge
                    (dsf_ListSort (subset lst half) %a %b %exp)
                    (dsf_ListSort (subset lst 0 half) %a %b %exp)
                    fun
                )
            )
        )
    )
))
Now, as you can probably imagine, this blows up. When passing %a %b %exp to dsf_ListSort, they get passed as their literal values, not their evaluated values. So, in essence, there is no way to make a recursive function of this type. I tried (link) and (eval), all with the same result.

In the end the problem is solved by making a wrapper function

Code: Select all

(setq dsf_ListSort (lambda (lst %a %b %exp)
    (dsf__ListSort_ lst (eval (list lambda (list %a %b) %exp)))
))
However, this seems a little bit hackish.

So, should we leave it at that, or perhaps have a way of passing the evaluated expression to the lambda? Perhaps %%var or something?

Either way it is a great new tool. Thanks a lot for it :)
george moromisato
Developer
Developer
Posts: 2998
Joined: Thu Jul 24, 2003 9:53 pm
Contact:

I'm not sure I totally get it, but one thing to point out is that the % is a modifier on the variable declaration, not a part of the variable itself. Thus:

Code: Select all

(setq test (lambda (%a)
   (dbgOutput a)   ; <- Note that we use 'a' not '%a'
   ))
I don't know if that's the problem or if I'm misunderstanding something (I would have thought that you'd get an error or something).
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

george moromisato wrote:

Code: Select all

(setq test (lambda (%a)
   (dbgOutput a)   ; <- Note that we use 'a' not '%a'
   ))
That code gives the error no binding for symbol a.
Crying is not a proper retort!
User avatar
alterecco
Fleet Officer
Fleet Officer
Posts: 1658
Joined: Wed Jan 14, 2009 3:08 am
Location: Previously enslaved by the Iocrym

I should have noted this yesterday, but a '%var' argument is definitely not available inside the function as 'var', but as '%var'. Aside from that, I think my example was a bad one, so I will write a simpler one up here. As said, the problem is with self recursive functions

Code: Select all

(setq recurse (lambda (%a stop) (block Nil
    (dbgOutput %a)
    (if (not stop) (recurse %a 'stop))
)))

(recurse (add 1 2)) -> prints
(add 1 2)
%a
I hope that example serves to demonstrate the problem a bit better. It is impossible to pass %a's literal value into the same function again.
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

now that I think about it some more I like that % always has the same behavior. With a wrapper you can pass the non evaluated stuff so that isn't a problem (unless you don't like wrappers).

As for a solution I don't know, I don't want any syntax to have to look at the calling function.
Crying is not a proper retort!
User avatar
alterecco
Fleet Officer
Fleet Officer
Posts: 1658
Joined: Wed Jan 14, 2009 3:08 am
Location: Previously enslaved by the Iocrym

After having gone over things with Betel on irc, we have come up with a syntax proposal.

Code: Select all

;; using Georges syntax ('a', not '%a' inside the lambdas)
(setq f (lambda (%a) (dbgLog a)))
(f (add 1 1)) -> (add 1 1)

;; this is how it currently works
(setq f (lambda (%a) (f2 a)))
(setq f2 (lambda (%a) (dbgLog a)))
(f (add 1 1)) -> a

;; this is the suggestion for an alternative syntax
(setq f (lambda (%a) (f2 %a)))
(setq f2 (lambda (%a) (dbgLog a)))
(f (add 1 1)) -> (add 1 1)
The difference between the two last examples lie in the call to 'f2' inside 'f'.

I wonder what your comment to this would be, George. Is it realistic, or too much of an edge case to spend time on? Like Betel said, the above can be accomplished using wrapper functions, so it is not a showstopper.
george moromisato
Developer
Developer
Posts: 2998
Joined: Thu Jul 24, 2003 9:53 pm
Contact:

Sorry, I was definitely wrong about the syntax.

And now I understand the problem that you're having.

One possible solution is to introduce new syntax to force evaluation. Common LISP uses a comma (though in different context):

Code: Select all

(setq f (lambda (%a) (dbgOutput %a)))
(setq f2 (lambda (%a) (f ,%a))) ; the comma means evaluate %a even though f doesn't evaluate its arg
A much more long term solution is to replace the % syntax with a true macro syntax (again, following Common LISP).

In either case, I'd rather not make any changes right now for fear of destabilizing 1.0.
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

I would like macros but not as a replacement for % as % allows for delayed evaluation making some functions faster that would otherwise be.
Crying is not a proper retort!
george moromisato
Developer
Developer
Posts: 2998
Joined: Thu Jul 24, 2003 9:53 pm
Contact:

Betelgeuse wrote:I would like macros but not as a replacement for % as % allows for delayed evaluation making some functions faster that would otherwise be.
I'm cool with that.

Although if I understand macros correctly, you should be able to accomplish delayed evaluation with macros also.
Post Reply